267 lines
9.6 KiB
PHP
267 lines
9.6 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\Payroll;
|
|
use App\Models\Purchase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Inertia;
|
|
|
|
class AnalysisController extends Controller
|
|
{
|
|
public function __invoke()
|
|
{
|
|
$stats = [
|
|
'total_sales' => $this->getStats(fn () => Order::count()),
|
|
'total_revenue' => $this->getStats(fn () => Order::sum('total')),
|
|
'total_expenses' => $this->getStats(fn () => Expense::sum('amount')),
|
|
'total_payrolls' => $this->getStats(fn () => Payroll::sum('total_salary')),
|
|
'cogs' => $this->getStats(fn () => Order::sum('hpp')),
|
|
'total_discount' => $this->getStats(fn () => Order::sum('discount')),
|
|
'total_purchases' => $this->getStats(fn () => Purchase::sum('total')),
|
|
'products_sold' => $this->getStats(fn () => OrderItem::sum('qty')),
|
|
'total_customers' => $this->getStats(fn () => Order::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'] = [
|
|
'value' => $stats['gross_profit']['value'] - $stats['total_expenses']['value'] - $stats['total_payrolls']['value'],
|
|
'yesterday' => null,
|
|
'change' => null,
|
|
];
|
|
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
|
|
|
// Revenue vs Purchases per month (this year)
|
|
$revenueByMonth = DB::table('orders')
|
|
->select(
|
|
DB::raw('MONTH(created_at) as month'),
|
|
DB::raw('SUM(total) as total')
|
|
)
|
|
->groupBy(DB::raw('MONTH(created_at)'))
|
|
->get();
|
|
|
|
$purchasesByMonth = DB::table('purchases')
|
|
->select(
|
|
DB::raw('MONTH(created_at) as month'),
|
|
DB::raw('SUM(total) as total')
|
|
)
|
|
->groupBy(DB::raw('MONTH(created_at)'))
|
|
->get();
|
|
|
|
$monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
|
|
|
$salesByMonth = collect(range(1, 12))->map(function ($month) use ($revenueByMonth, $purchasesByMonth, $monthNames) {
|
|
$revenueFound = $revenueByMonth->firstWhere('month', $month);
|
|
$purchasesFound = $purchasesByMonth->firstWhere('month', $month);
|
|
|
|
return [
|
|
'month' => $monthNames[$month - 1],
|
|
'sales' => $revenueFound ? (float) $revenueFound->total : 0,
|
|
'purchase' => $purchasesFound ? (float) $purchasesFound->total : 0,
|
|
];
|
|
});
|
|
|
|
$paymentMethods = DB::table('orders')
|
|
->select('payment_method as name', DB::raw('COUNT(*) as total'))
|
|
->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'))
|
|
->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'))
|
|
->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')
|
|
->select('products.name as name', DB::raw('SUM(order_items.qty) as total'))
|
|
->groupBy('products.name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get();
|
|
|
|
$topCustomers = DB::table('orders')
|
|
->select('customer_name as name', DB::raw('SUM(total) as total'))
|
|
->whereNotNull('customer_name')
|
|
->groupBy('customer_name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get();
|
|
|
|
$salesByHourRaw = DB::table('orders')
|
|
->select(
|
|
DB::raw('HOUR(created_at) as hour'),
|
|
DB::raw('COUNT(*) as total')
|
|
)
|
|
->groupBy(DB::raw('HOUR(created_at)'))
|
|
->get();
|
|
|
|
$salesByHour = collect(range(0, 23))->map(function ($hour) use ($salesByHourRaw) {
|
|
$found = $salesByHourRaw->firstWhere('hour', $hour);
|
|
|
|
return [
|
|
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT).':00',
|
|
'total' => $found ? (int) $found->total : 0,
|
|
];
|
|
});
|
|
|
|
// Revenue & Profit & Volume per month
|
|
$ordersByMonth = DB::table('orders')
|
|
->select(
|
|
DB::raw('MONTH(created_at) as month'),
|
|
DB::raw('SUM(total) as revenue'),
|
|
DB::raw('SUM(hpp) as cogs'),
|
|
DB::raw('COUNT(*) as count')
|
|
)
|
|
->groupBy(DB::raw('MONTH(created_at)'))
|
|
->get();
|
|
|
|
$expensesByMonth = DB::table('expenses')
|
|
->select(
|
|
DB::raw('MONTH(created_at) as month'),
|
|
DB::raw('SUM(amount) as total')
|
|
)
|
|
->groupBy(DB::raw('MONTH(created_at)'))
|
|
->get();
|
|
|
|
$payrollsByMonth = DB::table('payrolls')
|
|
->select(
|
|
DB::raw('MONTH(period_month) as month'),
|
|
DB::raw('SUM(total_salary) as total')
|
|
)
|
|
->whereNull('deleted_at')
|
|
->groupBy(DB::raw('MONTH(period_month)'))
|
|
->get();
|
|
|
|
$revenueVsProfit = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $expensesByMonth, $payrollsByMonth, $monthNames) {
|
|
$orderFound = $ordersByMonth->firstWhere('month', $month);
|
|
$expenseFound = $expensesByMonth->firstWhere('month', $month);
|
|
$payrollFound = $payrollsByMonth->firstWhere('month', $month);
|
|
|
|
$revenue = $orderFound ? (float) $orderFound->revenue : 0;
|
|
$cogs = $orderFound ? (float) $orderFound->cogs : 0;
|
|
$expense = $expenseFound ? (float) $expenseFound->total : 0;
|
|
$payroll = $payrollFound ? (float) $payrollFound->total : 0;
|
|
$profit = $revenue - $cogs - $expense - $payroll;
|
|
|
|
return [
|
|
'month' => $monthNames[$month - 1],
|
|
'revenue' => $revenue,
|
|
'profit' => $profit,
|
|
];
|
|
});
|
|
|
|
$transactionVolume = collect(range(1, 12))->map(function ($month) use ($ordersByMonth, $monthNames) {
|
|
$found = $ordersByMonth->firstWhere('month', $month);
|
|
$count = $found ? (int) $found->count : 0;
|
|
$revenue = $found ? (float) $found->revenue : 0;
|
|
$aov = $count > 0 ? $revenue / $count : 0;
|
|
|
|
return [
|
|
'month' => $monthNames[$month - 1],
|
|
'count' => $count,
|
|
'aov' => $aov,
|
|
];
|
|
});
|
|
|
|
$topCategories = DB::table('order_items')
|
|
->join('products', 'order_items.product_id', '=', 'products.id')
|
|
->join('category_product', 'products.id', '=', 'category_product.product_id')
|
|
->join('categories', 'category_product.category_id', '=', 'categories.id')
|
|
->select('categories.name', DB::raw('SUM(order_items.qty) as total'))
|
|
->whereNull('order_items.deleted_at')
|
|
->groupBy('categories.id', 'categories.name')
|
|
->orderByDesc('total')
|
|
->limit(5)
|
|
->get();
|
|
|
|
return Inertia::render('analysis', [
|
|
'stats' => $stats,
|
|
'salesByMonth' => $salesByMonth,
|
|
'revenueVsProfit' => $revenueVsProfit,
|
|
'transactionVolume' => $transactionVolume,
|
|
'salesByHour' => $salesByHour,
|
|
'paymentMethods' => $paymentMethods,
|
|
'orderStatuses' => $orderStatuses,
|
|
'orderChannels' => $orderChannels,
|
|
'topProducts' => $topProducts,
|
|
'topCustomers' => $topCustomers,
|
|
'topCategories' => $topCategories,
|
|
]);
|
|
}
|
|
|
|
private function getStats(callable $callback): array
|
|
{
|
|
$value = $callback();
|
|
|
|
return [
|
|
'value' => $value,
|
|
'yesterday' => null,
|
|
'change' => null,
|
|
];
|
|
}
|
|
|
|
private function calculateAov($revenue, $sales): array
|
|
{
|
|
$value = $sales['value'] > 0 ? $revenue['value'] / $sales['value'] : 0;
|
|
|
|
return [
|
|
'value' => $value,
|
|
'yesterday' => null,
|
|
'change' => null,
|
|
];
|
|
}
|
|
|
|
private function calculateDiff($a, $b): array
|
|
{
|
|
return [
|
|
'value' => $a['value'] - $b['value'],
|
|
'yesterday' => null,
|
|
'change' => null,
|
|
];
|
|
}
|
|
|
|
private function calculateMargin($profit, $revenue): array
|
|
{
|
|
$value = $revenue['value'] > 0 ? ($profit['value'] / $revenue['value']) * 100 : 0;
|
|
|
|
return [
|
|
'value' => round($value, 2),
|
|
'yesterday' => null,
|
|
'change' => null,
|
|
];
|
|
}
|
|
}
|