216 lines
8.0 KiB
PHP
216 lines
8.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\OrderChannel;
|
|
use App\Enums\OrderStatus;
|
|
use App\Enums\PaymentMethod;
|
|
use App\Models\Expense;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Purchase;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Inertia;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __invoke()
|
|
{
|
|
$today = Carbon::today();
|
|
$yesterday = Carbon::yesterday();
|
|
|
|
$stats = [
|
|
'total_sales' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->count()),
|
|
'total_revenue' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('total')),
|
|
'total_expenses' => $this->getStats($today, $yesterday, fn ($date) => Expense::whereDate('created_at', $date)->sum('amount')),
|
|
'cogs' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('cogs')),
|
|
'total_discount' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('discount')),
|
|
'total_purchases' => $this->getStats($today, $yesterday, fn ($date) => Purchase::whereDate('created_at', $date)->sum('total')),
|
|
'products_sold' => $this->getStats($today, $yesterday, fn ($date) => OrderItem::whereHas('order', fn ($q) => $q->whereDate('created_at', $date))->sum('qty')),
|
|
'total_customers' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->distinct('customer_name')->count('customer_name')),
|
|
];
|
|
|
|
$stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']);
|
|
$stats['gross_profit'] = $this->calculateDiff($stats['total_revenue'], $stats['cogs']);
|
|
$stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']);
|
|
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
|
|
|
$isSqlite = DB::getDriverName() === 'sqlite';
|
|
$hourSelect = $isSqlite ? "CAST(strftime('%H', created_at) AS INTEGER)" : 'HOUR(created_at)';
|
|
|
|
// Get sales by hour
|
|
$todayOrders = DB::table('orders')
|
|
->whereDate('created_at', $today)
|
|
->select(
|
|
DB::raw("$hourSelect as hour"),
|
|
DB::raw('COUNT(*) as total')
|
|
)
|
|
->groupBy(DB::raw($hourSelect))
|
|
->get();
|
|
|
|
$yesterdayOrders = DB::table('orders')
|
|
->whereDate('created_at', $yesterday)
|
|
->select(
|
|
DB::raw("$hourSelect as hour"),
|
|
DB::raw('COUNT(*) as total')
|
|
)
|
|
->groupBy(DB::raw($hourSelect))
|
|
->get();
|
|
|
|
$salesByHour = collect(range(0, 23))->map(function ($hour) use ($todayOrders, $yesterdayOrders) {
|
|
$todayFound = $todayOrders->firstWhere('hour', $hour);
|
|
$yesterdayFound = $yesterdayOrders->firstWhere('hour', $hour);
|
|
|
|
return [
|
|
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT).':00',
|
|
'today' => $todayFound ? (int) $todayFound->total : 0,
|
|
'yesterday' => $yesterdayFound ? (int) $yesterdayFound->total : 0,
|
|
];
|
|
});
|
|
|
|
$paymentMethods = DB::table('orders')
|
|
->select('payment_method as name', DB::raw('COUNT(*) as total'))
|
|
->whereDate('created_at', $today)
|
|
->groupBy('payment_method')
|
|
->get()
|
|
->map(function ($item) {
|
|
if ($item->name) {
|
|
$item->name = PaymentMethod::tryFrom($item->name)?->label() ?? $item->name;
|
|
}
|
|
|
|
return $item;
|
|
});
|
|
|
|
$orderStatuses = DB::table('orders')
|
|
->select('order_status as name', DB::raw('COUNT(*) as total'))
|
|
->whereDate('created_at', $today)
|
|
->groupBy('order_status')
|
|
->get()
|
|
->map(function ($item) {
|
|
if ($item->name) {
|
|
$item->name = OrderStatus::tryFrom($item->name)?->label() ?? $item->name;
|
|
}
|
|
|
|
return $item;
|
|
});
|
|
|
|
$orderChannels = DB::table('orders')
|
|
->select('order_channel as name', DB::raw('COUNT(*) as total'))
|
|
->whereDate('created_at', $today)
|
|
->groupBy('order_channel')
|
|
->get()
|
|
->map(function ($item) {
|
|
if ($item->name) {
|
|
$item->name = OrderChannel::tryFrom($item->name)?->label() ?? $item->name;
|
|
}
|
|
|
|
return $item;
|
|
});
|
|
|
|
$topProducts = DB::table('order_items')
|
|
->join('products', 'order_items.product_id', '=', 'products.id')
|
|
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
|
->select('products.name as name', DB::raw('SUM(order_items.qty) as total'))
|
|
->whereDate('orders.created_at', $today)
|
|
->groupBy('products.name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get();
|
|
|
|
$topCustomers = DB::table('orders')
|
|
->select('customer_name as name', DB::raw('SUM(total) as total'))
|
|
->whereDate('created_at', $today)
|
|
->whereNotNull('customer_name')
|
|
->groupBy('customer_name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get();
|
|
|
|
/** @var User $user */
|
|
$user = auth()->user();
|
|
if ($user->hasRole('Admin')) {
|
|
unset(
|
|
$stats['cogs'],
|
|
$stats['aov'],
|
|
$stats['gross_profit'],
|
|
$stats['net_profit'],
|
|
$stats['profit_margin'],
|
|
$stats['total_purchases']
|
|
);
|
|
}
|
|
|
|
return Inertia::render('dashboard', [
|
|
'stats' => $stats,
|
|
'salesByHour' => $salesByHour,
|
|
'paymentMethods' => $paymentMethods,
|
|
'orderStatuses' => $orderStatuses,
|
|
'orderChannels' => $orderChannels,
|
|
'topProducts' => $topProducts,
|
|
'topCustomers' => $topCustomers,
|
|
]);
|
|
}
|
|
|
|
private function getStats($today, $yesterday, $callback): array
|
|
{
|
|
$todayVal = $callback($today);
|
|
$yesterdayVal = $callback($yesterday);
|
|
|
|
$diff = $todayVal - $yesterdayVal;
|
|
$change = $this->calculatePercentageChange($todayVal, $yesterdayVal);
|
|
|
|
return [
|
|
'value' => $todayVal,
|
|
'yesterday' => $yesterdayVal,
|
|
'diff' => $diff,
|
|
'change' => $change,
|
|
];
|
|
}
|
|
|
|
private function calculatePercentageChange($current, $previous): ?string
|
|
{
|
|
if ($previous == 0) {
|
|
return $current == 0 ? 0 : null;
|
|
}
|
|
|
|
return round((($current - $previous) / abs($previous)) * 100, 2);
|
|
}
|
|
|
|
private function calculateAov($revenue, $sales)
|
|
{
|
|
$todayVal = $sales['value'] > 0 ? $revenue['value'] / $sales['value'] : 0;
|
|
$yesterdayVal = $sales['yesterday'] > 0 ? $revenue['yesterday'] / $sales['yesterday'] : 0;
|
|
|
|
return [
|
|
'value' => $todayVal,
|
|
'yesterday' => $yesterdayVal,
|
|
'change' => $this->calculatePercentageChange($todayVal, $yesterdayVal),
|
|
];
|
|
}
|
|
|
|
private function calculateDiff($a, $b): array
|
|
{
|
|
$todayVal = $a['value'] - $b['value'];
|
|
$yesterdayVal = $a['yesterday'] - $b['yesterday'];
|
|
|
|
return [
|
|
'value' => $todayVal,
|
|
'yesterday' => $yesterdayVal,
|
|
'change' => $this->calculatePercentageChange($todayVal, $yesterdayVal),
|
|
];
|
|
}
|
|
|
|
private function calculateMargin($profit, $revenue): array
|
|
{
|
|
$todayVal = $revenue['value'] > 0 ? ($profit['value'] / $revenue['value']) * 100 : 0;
|
|
$yesterdayVal = $revenue['yesterday'] > 0 ? ($profit['yesterday'] / $revenue['yesterday']) * 100 : 0;
|
|
|
|
return [
|
|
'value' => round($todayVal, 2),
|
|
'yesterday' => round($yesterdayVal, 2),
|
|
'change' => $this->calculatePercentageChange($todayVal, $yesterdayVal),
|
|
];
|
|
}
|
|
}
|