169 lines
6.3 KiB
PHP
169 lines
6.3 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 Carbon\Carbon;
|
|
use Inertia\Inertia;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __invoke()
|
|
{
|
|
$today = Carbon::today();
|
|
$yesterday = Carbon::yesterday();
|
|
|
|
$stats = [
|
|
'total_penjualan' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->count()),
|
|
'total_pendapatan' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('total')),
|
|
'total_pengeluaran' => $this->getStats($today, $yesterday, fn ($date) => Expense::whereDate('created_at', $date)->sum('amount')),
|
|
'hpp' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('hpp')),
|
|
'total_diskon' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->sum('discount')),
|
|
'total_pembelian' => $this->getStats($today, $yesterday, fn ($date) => Purchase::whereDate('created_at', $date)->sum('total')),
|
|
'produk_terjual' => $this->getStats($today, $yesterday, fn ($date) => OrderItem::whereHas('order', fn ($q) => $q->whereDate('created_at', $date))->sum('qty')),
|
|
'total_pelanggan' => $this->getStats($today, $yesterday, fn ($date) => Order::whereDate('created_at', $date)->distinct('customer_name')->count('customer_name')),
|
|
];
|
|
|
|
// Complex stats
|
|
$stats['aov'] = $this->calculateAov($stats['total_pendapatan'], $stats['total_penjualan']);
|
|
$stats['laba_kotor'] = $this->calculateDiff($stats['total_pendapatan'], $stats['hpp']);
|
|
$stats['laba_bersih'] = $this->calculateDiff($stats['laba_kotor'], $stats['total_pengeluaran']);
|
|
$stats['profit_margin'] = $this->calculateMargin($stats['laba_bersih'], $stats['total_pendapatan']);
|
|
|
|
// Chart Data
|
|
$stats['charts'] = [
|
|
'jam_sibuk' => $this->getJamSibuk($today, $yesterday),
|
|
'order_by_payment' => $this->getOrderByEnum($today, 'payment_method', PaymentMethod::class),
|
|
'order_by_status' => $this->getOrderByEnum($today, 'order_status', OrderStatus::class),
|
|
'order_by_channel' => $this->getOrderByEnum($today, 'order_channel', OrderChannel::class),
|
|
'top_products' => $this->getTopProducts($today),
|
|
];
|
|
|
|
return Inertia::render('dashboard', [
|
|
'stats' => $stats,
|
|
]);
|
|
}
|
|
|
|
private function getJamSibuk($today, $yesterday)
|
|
{
|
|
$todayData = Order::whereDate('created_at', $today)
|
|
->selectRaw('HOUR(created_at) as hour, count(*) as count')
|
|
->groupBy('hour')
|
|
->pluck('count', 'hour')
|
|
->toArray();
|
|
|
|
$yesterdayData = Order::whereDate('created_at', $yesterday)
|
|
->selectRaw('HOUR(created_at) as hour, count(*) as count')
|
|
->groupBy('hour')
|
|
->pluck('count', 'hour')
|
|
->toArray();
|
|
|
|
$chartData = [];
|
|
for ($i = 0; $i < 24; $i++) {
|
|
$chartData[] = [
|
|
'hour' => sprintf('%02d:00', $i),
|
|
'today' => $todayData[$i] ?? 0,
|
|
'yesterday' => $yesterdayData[$i] ?? 0,
|
|
];
|
|
}
|
|
|
|
return $chartData;
|
|
}
|
|
|
|
private function getOrderByEnum($date, $column, $enumClass)
|
|
{
|
|
$data = Order::whereDate('created_at', $date)
|
|
->selectRaw("$column, count(*) as count")
|
|
->groupBy($column)
|
|
->get();
|
|
|
|
return $data->map(function ($item) use ($column, $enumClass) {
|
|
$enumValue = $item->$column;
|
|
$label = $enumValue instanceof $enumClass ? $enumValue->label() : $enumValue;
|
|
|
|
return [
|
|
'name' => $label,
|
|
'value' => $item->count,
|
|
];
|
|
});
|
|
}
|
|
|
|
private function getTopProducts($date)
|
|
{
|
|
return OrderItem::whereHas('order', fn ($q) => $q->whereDate('created_at', $date))
|
|
->with('product:id,name')
|
|
->selectRaw('product_id, sum(qty) as total_qty')
|
|
->groupBy('product_id')
|
|
->orderByDesc('total_qty')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($item) => [
|
|
'name' => $item->product->name ?? 'Unknown',
|
|
'qty' => (int) $item->total_qty,
|
|
]);
|
|
}
|
|
|
|
private function getStats($today, $yesterday, $callback)
|
|
{
|
|
$todayVal = $callback($today);
|
|
$yesterdayVal = $callback($yesterday);
|
|
|
|
return [
|
|
'value' => $todayVal,
|
|
'yesterday' => $yesterdayVal,
|
|
'change' => $this->calculatePercentageChange($todayVal, $yesterdayVal),
|
|
];
|
|
}
|
|
|
|
private function calculatePercentageChange($current, $previous)
|
|
{
|
|
if ($previous == 0) {
|
|
return $current > 0 ? 100 : 0;
|
|
}
|
|
|
|
return round((($current - $previous) / $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)
|
|
{
|
|
$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)
|
|
{
|
|
$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),
|
|
];
|
|
}
|
|
}
|