|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * SiteBase |
| 5 | + * PHP Version 8.3 |
| 6 | + * |
| 7 | + * @category CMS / Framework |
| 8 | + * @package Degami\Sitebase |
| 9 | + * @author Mirko De Grandis <degami@github.com> |
| 10 | + * @license MIT https://opensource.org/licenses/mit-license.php |
| 11 | + * @link https://github.com/degami/sitebase |
| 12 | + */ |
| 13 | + |
| 14 | +namespace App\Base\Controllers\Admin\Commerce; |
| 15 | + |
| 16 | +use App\Base\Abstracts\Controllers\AdminPage; |
| 17 | +use App\Base\Interfaces\Model\PhysicalProductInterface; |
| 18 | +use DateInterval; |
| 19 | +use DateTime; |
| 20 | + |
| 21 | +/** |
| 22 | + * "Dashboard" Admin Page |
| 23 | + */ |
| 24 | +class Dashboard extends AdminPage |
| 25 | +{ |
| 26 | + /** |
| 27 | + * @var string page title |
| 28 | + */ |
| 29 | + protected ?string $page_title = 'Commerce Dashboard'; |
| 30 | + |
| 31 | + /** |
| 32 | + * {@inheritdoc} |
| 33 | + */ |
| 34 | + public function getTemplateName(): string |
| 35 | + { |
| 36 | + return 'commerce/dashboard'; |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * {@inheritdoc} |
| 41 | + */ |
| 42 | + public static function getAccessPermission(): string |
| 43 | + { |
| 44 | + return 'administer_orders'; |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * {@inheritdoc} |
| 49 | + */ |
| 50 | + public static function getAdminPageLink(): ?array |
| 51 | + { |
| 52 | + return [ |
| 53 | + 'permission_name' => '', |
| 54 | + 'route_name' => static::getPageRouteName(), |
| 55 | + 'icon' => 'bar-chart', |
| 56 | + 'text' => 'Dashboard', |
| 57 | + 'section' => 'commerce', |
| 58 | + 'order' => 0, |
| 59 | + ]; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * {@inheritdoc} |
| 64 | + */ |
| 65 | + public function getTemplateData(): array |
| 66 | + { |
| 67 | + $now = new DateTime(); |
| 68 | + |
| 69 | + $this->template_data = [ |
| 70 | + 'lifetime' => $this->collectData(clone $now, null), |
| 71 | + 'last_year' => $this->collectData(clone $now, (clone $now)->sub(DateInterval::createFromDateString('1 year'))), |
| 72 | + 'last_month' => $this->collectData(clone $now, (clone $now)->sub(DateInterval::createFromDateString('1 month'))), |
| 73 | + 'last_week' => $this->collectData(clone $now, (clone $now)->sub(DateInterval::createFromDateString('1 week'))), |
| 74 | + ]; |
| 75 | + |
| 76 | + return $this->template_data; |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * Returns sales data for a given date range |
| 81 | + */ |
| 82 | + protected function collectData(DateTime $to, ?DateTime $from = null): array |
| 83 | + { |
| 84 | + $args = []; |
| 85 | + $where = " WHERE 1"; |
| 86 | + |
| 87 | + if ($from) { |
| 88 | + $where .= ' AND o.created_at >= :from'; |
| 89 | + $args['from'] = $from->format('Y-m-d 00:00:00'); |
| 90 | + } |
| 91 | + |
| 92 | + if ($to) { |
| 93 | + $where .= ' AND o.created_at <= :to'; |
| 94 | + $args['to'] = $to->format('Y-m-d 23:59:59'); |
| 95 | + } |
| 96 | + |
| 97 | + // === 1️⃣ Statistiche generali (solo tabella `order`) === |
| 98 | + $qOrders = " |
| 99 | + SELECT |
| 100 | + COUNT(o.id) AS total_sales, |
| 101 | + SUM(o.admin_total_incl_tax) AS total_income, |
| 102 | + SUM(o.discount_amount) AS total_discounts, |
| 103 | + SUM(o.tax_amount) AS total_tax, |
| 104 | + MAX(o.admin_currency_code) AS admin_currency_code |
| 105 | + FROM `order` o |
| 106 | + $where |
| 107 | + "; |
| 108 | + |
| 109 | + $stmt1 = $this->getPdo()->prepare($qOrders); |
| 110 | + $stmt1->execute($args); |
| 111 | + $orders = $stmt1->fetch(\PDO::FETCH_ASSOC) ?: []; |
| 112 | + |
| 113 | + // === 2️⃣ Totale prodotti venduti === |
| 114 | + $qItems = " |
| 115 | + SELECT SUM(oi.quantity) AS total_products |
| 116 | + FROM `order_item` oi |
| 117 | + INNER JOIN `order` o ON o.id = oi.order_id |
| 118 | + $where |
| 119 | + "; |
| 120 | + |
| 121 | + $stmt2 = $this->getPdo()->prepare($qItems); |
| 122 | + $stmt2->execute($args); |
| 123 | + $items = $stmt2->fetch(\PDO::FETCH_ASSOC) ?: []; |
| 124 | + |
| 125 | + // === 3️⃣ Prodotti più venduti (TOP 10) === |
| 126 | + $qTop = " |
| 127 | + SELECT |
| 128 | + CONCAT(oi.product_class, ':', oi.product_id) AS product_key, |
| 129 | + SUM(oi.quantity) AS total_qty |
| 130 | + FROM `order` o |
| 131 | + INNER JOIN `order_item` oi ON oi.order_id = o.id |
| 132 | + $where |
| 133 | + GROUP BY product_key |
| 134 | + ORDER BY total_qty DESC |
| 135 | + LIMIT 5 |
| 136 | + "; |
| 137 | + |
| 138 | + $stmt3 = $this->getPdo()->prepare($qTop); |
| 139 | + $stmt3->execute($args); |
| 140 | + |
| 141 | + $most_sold = []; |
| 142 | + foreach ($stmt3->fetchAll(\PDO::FETCH_ASSOC) as $row) { |
| 143 | + try { |
| 144 | + $p = explode(':', $row['product_key']); |
| 145 | + $product = $this->containerCall([$p[0], 'load'], ['id' => $p[1]]); |
| 146 | + $label = method_exists($product, 'getSku') |
| 147 | + ? ($product->getSku() ?: $product->getName()) |
| 148 | + : $product->getName(); |
| 149 | + $stock = ($product instanceof PhysicalProductInterface) ? |
| 150 | + $product->getProductStock()->getCurrentQuantity() : |
| 151 | + $this->getUtils()->translate('unlimited', locale: $this->getCurrentLocale()); |
| 152 | + } catch (\Exception $e) { |
| 153 | + $label = 'n/a'; |
| 154 | + $stock = 'n/a'; |
| 155 | + } |
| 156 | + |
| 157 | + $most_sold[] = [ |
| 158 | + 'product' => $label, |
| 159 | + 'total_qty' => (int) $row['total_qty'], |
| 160 | + 'stock' => $stock, |
| 161 | + ]; |
| 162 | + } |
| 163 | + |
| 164 | + // === 4️⃣ Metodo di pagamento più usato === |
| 165 | + $qPay = " |
| 166 | + SELECT op.payment_method, COUNT(*) AS total |
| 167 | + FROM `order_payment` op |
| 168 | + INNER JOIN `order` o ON o.id = op.order_id |
| 169 | + $where |
| 170 | + GROUP BY op.payment_method |
| 171 | + ORDER BY total DESC |
| 172 | + LIMIT 1 |
| 173 | + "; |
| 174 | + |
| 175 | + $stmt4 = $this->getPdo()->prepare($qPay); |
| 176 | + $stmt4->execute($args); |
| 177 | + $payment = $stmt4->fetch(\PDO::FETCH_ASSOC); |
| 178 | + |
| 179 | + // === 5️⃣ Calcoli e formattazione === |
| 180 | + $utils = $this->getUtils(); |
| 181 | + $currency = $orders['admin_currency_code'] ?? 'EUR'; |
| 182 | + |
| 183 | + $total_sales = (int) ($orders['total_sales'] ?? 0); |
| 184 | + $total_income = (float) ($orders['total_income'] ?? 0); |
| 185 | + $total_tax = (float) ($orders['total_tax'] ?? 0); |
| 186 | + $total_discount = (float) ($orders['total_discounts'] ?? 0); |
| 187 | + $total_products = (int) ($items['total_products'] ?? 0); |
| 188 | + |
| 189 | + $aov = $total_sales > 0 ? $total_income / $total_sales : 0; |
| 190 | + |
| 191 | + return [ |
| 192 | + 'total_sales' => $total_sales, |
| 193 | + 'total_income' => $utils->formatPrice($total_income, $currency), |
| 194 | + 'total_tax' => $utils->formatPrice($total_tax, $currency), |
| 195 | + 'total_discount' => $utils->formatPrice($total_discount, $currency), |
| 196 | + 'total_products' => $total_products, |
| 197 | + 'average_order' => $utils->formatPrice($aov, $currency), |
| 198 | + 'most_sold' => $most_sold, |
| 199 | + 'top_payment' => $payment['payment_method'] ?? 'n/a', |
| 200 | + ]; |
| 201 | + } |
| 202 | +} |
0 commit comments