From 42f7214efad5a78348e854145d8409e96e4ab075 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 17 Aug 2026 22:50:20 +0700 Subject: [PATCH] feat: add monthly retail revenue calculation and display in analysis component --- app/Http/Controllers/AnalysisController.php | 2 + app/Services/AnalysisService.php | 69 +++++++++++++++ resources/js/pages/admin/analysis/index.tsx | 96 +++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/app/Http/Controllers/AnalysisController.php b/app/Http/Controllers/AnalysisController.php index 84942f8..d49fe11 100644 --- a/app/Http/Controllers/AnalysisController.php +++ b/app/Http/Controllers/AnalysisController.php @@ -45,6 +45,7 @@ public function index(Request $request): Response $marketingSales = $this->service->getMarketingSales($startDate, $endDate, $user); $orderStats = $this->service->getOrderStats($startDate, $endDate, $user); $revenueTrend = $this->service->getRevenueTrend($startDate, $endDate, $user); + $monthlyRetailRevenue = $this->service->getMonthlyRetailRevenue($startDate, $endDate, $user); return Inertia::render('admin/analysis/index', [ 'filters' => [ @@ -72,6 +73,7 @@ public function index(Request $request): Response 'marketingSales' => $marketingSales, 'orderStats' => $orderStats, 'revenueTrend' => $revenueTrend, + 'monthlyRetailRevenue' => $monthlyRetailRevenue, ]); } } diff --git a/app/Services/AnalysisService.php b/app/Services/AnalysisService.php index f53bd98..433c4fa 100644 --- a/app/Services/AnalysisService.php +++ b/app/Services/AnalysisService.php @@ -445,6 +445,75 @@ public function getMonthlyRevenue(?string $startDate, ?string $endDate, ?User $u return $result; } + public function getMonthlyRetailRevenue(?string $startDate, ?string $endDate, ?User $user = null): array + { + $query = Order::where('orders.status', OrderStatus::COMPLETED); + $this->applyDateFilter($query, $startDate, $endDate, 'orders.created_at'); + + if ($user && $this->isMarketingUser($user)) { + $query->where('orders.marketing_id', $user->id); + } + + if ($user && $this->isCashierUser($user)) { + $query->whereIn('orders.created_by_id', $this->getCashierUserIds()); + } + + $retailOrderIds = OrderItem::where('stock_quality', 'retail') + ->pluck('order_id') + ->unique(); + + $query->whereIn('orders.id', $retailOrderIds); + + $monthly = (clone $query) + ->selectRaw("DATE_FORMAT(created_at, '%b %Y') as month") + ->selectRaw('COALESCE(SUM(total_amount), 0) as total') + ->selectRaw('COALESCE(SUM(discount), 0) as discount') + ->selectRaw('COALESCE(SUM(nego_price), 0) as deduction') + ->selectRaw('COALESCE(SUM(cogs), 0) as cogs') + ->groupBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"), DB::raw("DATE_FORMAT(created_at, '%b %Y')")) + ->orderBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')")) + ->get() + ->keyBy('month'); + + $summary = (clone $query) + ->selectRaw('COALESCE(SUM(total_amount), 0) as total') + ->selectRaw('COALESCE(SUM(discount), 0) as discount') + ->selectRaw('COALESCE(SUM(nego_price), 0) as deduction') + ->selectRaw('COALESCE(SUM(cogs), 0) as cogs') + ->first(); + + $gross = (int) $summary->total - (int) $summary->cogs; + + $result = []; + foreach ($monthly as $month => $row) { + $total = (int) $row->total; + $discount = (int) $row->discount; + $deduction = (int) $row->deduction; + $cogs = (int) $row->cogs; + $itemGross = $total - $cogs; + + $result[] = [ + 'month' => $month, + 'total' => $total, + 'gross' => $itemGross, + 'discount' => $discount, + 'deduction' => $deduction, + 'cogs' => $cogs, + ]; + } + + return [ + 'monthly' => $result, + 'summary' => [ + 'total' => (int) $summary->total, + 'discount' => (int) $summary->discount, + 'deduction' => (int) $summary->deduction, + 'cogs' => (int) $summary->cogs, + 'gross' => $gross, + ], + ]; + } + public function getMonthlyRevenueByChannel(?string $startDate, ?string $endDate, ?User $user = null): array { $query = Order::where('orders.status', OrderStatus::COMPLETED); diff --git a/resources/js/pages/admin/analysis/index.tsx b/resources/js/pages/admin/analysis/index.tsx index 829c1d5..ba13610 100644 --- a/resources/js/pages/admin/analysis/index.tsx +++ b/resources/js/pages/admin/analysis/index.tsx @@ -203,6 +203,23 @@ type AnalysisProps = { date: string; qty: number; }>; + monthlyRetailRevenue: { + monthly: Array<{ + month: string; + total: number; + gross: number; + discount: number; + deduction: number; + cogs: number; + }>; + summary: { + total: number; + discount: number; + deduction: number; + cogs: number; + gross: number; + }; + }; }; const revenueChartConfig = (() => { @@ -219,6 +236,19 @@ const revenueChartConfig = (() => { const revenueKeys = ['total', 'deduction', 'discount', 'cogs', 'gross', 'net'] as const; +const retailRevenueChartConfig = (() => { + const colors = generateRandomColors(5); + return { + total: { label: 'Total', color: colors[0] }, + gross: { label: 'Keuntungan Kotor', color: colors[1] }, + deduction: { label: 'Potongan Nego', color: colors[2] }, + discount: { label: 'Diskon', color: colors[3] }, + cogs: { label: 'HPP', color: colors[4] }, + } satisfies ChartConfig; +})(); + +const retailRevenueKeys = ['total', 'deduction', 'discount', 'cogs', 'gross'] as const; + const expenseChartConfig = (() => { const colors = generateRandomColors(4); return { @@ -450,12 +480,14 @@ export default function Analysis({ marketingSales, orderStats, revenueTrend, + monthlyRetailRevenue, }: AnalysisProps) { const { can, hasAnyRole, hasRole } = useCan(); const [startDate, setStartDate] = useState(initialFilters.start_date ?? ''); const [endDate, setEndDate] = useState(initialFilters.end_date ?? ''); const [selectedPreset, setSelectedPreset] = useState(''); const [activeRevenueKey, setActiveRevenueKey] = useState('total'); + const [activeRetailRevenueKey, setActiveRetailRevenueKey] = useState('total'); const [activeExpenseKey, setActiveExpenseKey] = useState('total'); const [activeStockKey, setActiveStockKey] = useState<'good' | 'reject' | 'retail'>('good'); @@ -878,6 +910,70 @@ export default function Analysis({ )} + {can('analysis.revenue') && ( + + +
+ Pendapatan Penjualan Ecer +
+
+ {retailRevenueKeys.map((key) => { + const value = key === 'total' ? monthlyRetailRevenue.summary.total : key === 'discount' ? monthlyRetailRevenue.summary.discount : key === 'gross' ? monthlyRetailRevenue.summary.gross : key === 'cogs' ? monthlyRetailRevenue.summary.cogs : monthlyRetailRevenue.summary.deduction; + return ( + + ); + })} +
+
+ + {monthlyRetailRevenue.monthly.length > 0 ? ( + + + + + ( + <> +
+ {retailRevenueChartConfig[activeRetailRevenueKey]?.label ?? name} + + Rp{Number(value).toLocaleString('id-ID')} + + + )} + /> + } + /> + + + + ) : ( +
Belum ada data pendapatan ecer
+ )} + + + )} + {can('analysis.revenue') && (