dress/app/Http/Controllers/AnalysisController.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,
];
}
}