diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php index c9360d5..ad6ffac 100644 --- a/app/Services/Manage/CuttingService.php +++ b/app/Services/Manage/CuttingService.php @@ -599,7 +599,7 @@ function () use ($cutting, $validated): void { foreach ($materials as $materialData) { $combinationId = $materialData['combination_id']; if ($combinationId !== null) { - if (!isset($combinationGroups[$combinationId])) { + if (! isset($combinationGroups[$combinationId])) { $combinationGroups[$combinationId] = [ 'materials' => [], 'material_result' => $materialData['combination_material_result'] ?? null, @@ -622,7 +622,7 @@ function () use ($cutting, $validated): void { $newCombinationId = $materialData['combination_id'] !== null ? $combinationIdMap[$materialData['combination_id']] ?? null : null; - + $cutting->materials()->create([ 'raw_material_price_id' => $materialData['raw_material_price_id'], 'material_usage' => $materialData['material_usage'], diff --git a/app/Services/System/AnalysisService.php b/app/Services/System/AnalysisService.php index 8d8eba3..e442184 100644 --- a/app/Services/System/AnalysisService.php +++ b/app/Services/System/AnalysisService.php @@ -5,6 +5,7 @@ use App\Enums\EmployeeAdvanceStatus; use App\Enums\OrderStatus; use App\Enums\PriceType; +use App\Enums\ProductStockQuality; use App\Enums\Role; use App\Models\Attendance; use App\Models\CashAccount; @@ -285,7 +286,7 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = ->groupBy('month_key') ->map(fn ($orders) => $orders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0))); - $monthlyHpp = OrderItem::query() + $monthlyItemsData = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->leftJoin('product_prices', function ($join) { $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') @@ -297,11 +298,13 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw(" DATE_FORMAT(orders.created_at, '%Y-%m') as month_key, + order_items.stock_quality, + SUM(order_items.subtotal) as subtotal, COALESCE(SUM(order_items.quantity * product_prices.price), 0) as hpp ") - ->groupBy('month_key') + ->groupBy('month_key', 'order_items.stock_quality') ->get() - ->pluck('hpp', 'month_key'); + ->groupBy('month_key'); if ($monthlyData->isEmpty()) { return []; @@ -319,13 +322,42 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = $revenue = $monthlyData->firstWhere('month_key', $key); $fees = $monthlyFees->get($key, 0); $discount = (int) ($revenue->total_discount ?? 0); - $hpp = (int) ($monthlyHpp->get($key, 0)); $deduction = $discount + $fees; + $monthItems = $monthlyItemsData->get($key, collect()); + $gudangItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'good' + ); + $ecerItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'retail' + ); + $rejectItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'reject' + ); + + $gudangSubtotal = (int) ($gudangItem?->subtotal ?? 0); + $gudangHpp = (int) ($gudangItem?->hpp ?? 0); + $ecerSubtotal = (int) ($ecerItem?->subtotal ?? 0); + $ecerHpp = (int) ($ecerItem?->hpp ?? 0); + $rejectSubtotal = (int) ($rejectItem?->subtotal ?? 0); + $rejectHpp = (int) ($rejectItem?->hpp ?? 0); + + $totalItemsSubtotal = $gudangSubtotal + $ecerSubtotal + $rejectSubtotal; + $hpp = $gudangHpp + $ecerHpp + $rejectHpp; + + $gudangDeduction = 0; + $ecerDeduction = 0; + if ($totalItemsSubtotal > 0) { + $gudangDeduction = ($gudangSubtotal / $totalItemsSubtotal) * $deduction; + $ecerDeduction = ($ecerSubtotal / $totalItemsSubtotal) * $deduction; + } + + $netGudang = $gudangSubtotal - $gudangDeduction - $gudangHpp; + $netEcer = $ecerSubtotal - $ecerDeduction - $ecerHpp; + $result[] = [ 'month' => $monthLabel, 'total' => (int) ($revenue->total_revenue ?? 0), 'net' => (int) ($revenue->total_revenue ?? 0) - $deduction - $hpp, + 'net_gudang' => (int) round($netGudang), + 'net_ecer' => (int) round($netEcer), 'deduction' => $deduction, ]; diff --git a/resources/js/pages/admin/Analysis.vue b/resources/js/pages/admin/Analysis.vue index a681333..cb24846 100644 --- a/resources/js/pages/admin/Analysis.vue +++ b/resources/js/pages/admin/Analysis.vue @@ -97,6 +97,8 @@ const props = defineProps<{ month: string; total: number; net: number; + net_gudang: number; + net_ecer: number; deduction: number; }>; expenseSummary: { @@ -198,6 +200,8 @@ type MonthlyRevenueData = { month: string; total: number; net: number; + net_gudang: number; + net_ecer: number; deduction: number; }; @@ -210,20 +214,57 @@ const revenueChartConfig = { label: 'Bersih', color: '#22c55e', }, + net_gudang: { + label: 'Total Gudang', + color: '#10b981', + }, + net_ecer: { + label: 'Total Ecer', + color: '#06b6d4', + }, deduction: { label: 'Potongan', color: '#f97316', }, } satisfies ChartConfig; -const revenueTotals = computed(() => ({ - total: props.revenueSummary.total_revenue, - net: - props.revenueSummary.total_revenue - - props.revenueSummary.total_deduction - - props.profitMetrics.hpp, - deduction: props.revenueSummary.total_deduction, -})); +const revenueTotals = computed(() => { + const net_gudang = props.monthlyRevenue.reduce((sum, item) => sum + (item.net_gudang ?? 0), 0); + const net_ecer = props.monthlyRevenue.reduce((sum, item) => sum + (item.net_ecer ?? 0), 0); + return { + total: props.revenueSummary.total_revenue, + net: + props.revenueSummary.total_revenue - + props.revenueSummary.total_deduction - + props.profitMetrics.hpp, + net_gudang, + net_ecer, + deduction: props.revenueSummary.total_deduction, + }; +}); + +const visibleRevenueCharts = computed(() => { + const isOwner = hasAnyRole(['owner', 'developer']); + const isCashier = hasRole('cashier'); + + if (isOwner) { + return ['total', 'net', 'net_gudang', 'net_ecer', 'deduction'] as const; + } + + if (isCashier) { + return ['total', 'deduction'] as const; + } + + return ['total', 'net', 'deduction'] as const; +}); + +const revenueYAccessors = computed(() => { + return visibleRevenueCharts.value.map((chart) => (d: MonthlyRevenueData) => d[chart]); +}); + +const revenueColors = computed(() => { + return visibleRevenueCharts.value.map((chart) => revenueChartConfig[chart].color); +}); // Expense Chart type MonthlyExpenseData = { @@ -356,6 +397,7 @@ const barSelector = GroupedBar.selectors.bar; function revenueTooltip(d: any) { const item = d as MonthlyRevenueData; + const isOwner = hasAnyRole(['owner', 'developer']); const isCashier = hasRole('cashier'); return `
+ + ${revenueChartConfig.net_gudang.label} + Rp${formatRupiah(item.net_gudang)} +
++ + ${revenueChartConfig.net_ecer.label} + Rp${formatRupiah(item.net_ecer)} +
+ ` : ''}
${revenueChartConfig.deduction.label}
@@ -655,9 +709,7 @@ watch([startDate, endDate], () => {