From 60b358fb49f803f1199bfb5c80920f04deb03d15 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 1 Jul 2026 11:45:25 +0700 Subject: [PATCH] feat: enhance order management by calculating and displaying total cost price and net amount in order summaries --- app/Services/Manage/OrderService.php | 59 +++++++++++++++++-- app/Services/System/AnalysisService.php | 46 +++++++++++++-- app/Services/System/DashboardService.php | 18 +++++- resources/js/pages/admin/Dashboard.vue | 6 +- .../js/pages/admin/manage/orders/Show.vue | 26 ++++---- .../manage/orders/table/OrderGroupedTable.vue | 4 +- resources/js/types/order.ts | 4 ++ 7 files changed, 139 insertions(+), 24 deletions(-) diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index 90a32b3..408efcd 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -64,6 +64,7 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi 'createdBy.profile', 'items.productVariant.product:id,name', 'items.productVariant:id,product_id,name', + 'items.productVariant.prices', ]) ->when($user->hasAnyRole(['marketing-offline', 'marketing-online']), fn (Builder $query) => $query->where('marketing_id', $user->id)) ->when($user->hasRole('cashier'), fn (Builder $query) => $query->where('created_by_id', $user->id)) @@ -97,9 +98,11 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi $order->setAttribute('is_editable', $order->status->isEditable()); $order->setAttribute( 'marketplace_settings_snapshot', - $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot), + $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot, $order), ); + $this->appendNetAmount($order); + return $order; }); } @@ -242,6 +245,7 @@ public function findForShow(Order $order): Order 'marketing.profile', 'items.productVariant.product:id,name', 'items.productVariant.media', + 'items.productVariant.prices', 'cashTransaction:id,amount,description,created_at', 'media', ]); @@ -272,9 +276,11 @@ public function findForShow(Order $order): Order $order->setAttribute('is_editable', $order->status->isEditable()); $order->setAttribute( 'marketplace_settings_snapshot', - $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot), + $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot, $order), ); + $this->appendNetAmount($order); + return $order; } @@ -886,16 +892,61 @@ private function presentVariantPricesFromCollection(int $variantId, Collection $ ->all(); } - private function enrichMarketplaceSnapshot(?array $snapshot): ?array + private function enrichMarketplaceSnapshot(?array $snapshot, Order $order): ?array { if ($snapshot === null) { return null; } + $totalCostPrice = 0; + foreach ($order->items as $item) { + $variant = $item->productVariant; + if ($variant) { + $hargaModal = $variant->prices + ->first(fn ($price) => $price->type === PriceType::HARGA_MODAL) + ?->price ?? 0; + + $totalCostPrice += $hargaModal * $item->quantity; + } + } + + $netAmount = max(0, ($snapshot['net_amount'] ?? 0) - $totalCostPrice); + $snapshot['base_amount_formatted'] ??= 'Rp '.number_format($snapshot['base_amount'] ?? 0, 0, ',', '.'); $snapshot['total_fee_amount_formatted'] ??= 'Rp '.number_format($snapshot['total_fee_amount'] ?? 0, 0, ',', '.'); - $snapshot['net_amount_formatted'] ??= 'Rp '.number_format($snapshot['net_amount'] ?? 0, 0, ',', '.'); + $snapshot['total_cost_price'] = $totalCostPrice; + $snapshot['total_cost_price_formatted'] = 'Rp '.number_format($totalCostPrice, 0, ',', '.'); + $snapshot['net_amount'] = $netAmount; + $snapshot['net_amount_formatted'] = 'Rp '.number_format($netAmount, 0, ',', '.'); return $snapshot; } + + private function appendNetAmount(Order $order): void + { + $totalCostPrice = 0; + foreach ($order->items as $item) { + $variant = $item->productVariant; + if ($variant) { + $hargaModal = $variant->prices + ->first(fn ($price) => $price->type === PriceType::HARGA_MODAL) + ?->price ?? 0; + + $totalCostPrice += $hargaModal * $item->quantity; + } + } + + $baseAmount = $order->total_amount; + $netAmount = max(0, $baseAmount - $totalCostPrice); + + if ($order->marketplace_settings_snapshot !== null) { + $snapshot = $order->marketplace_settings_snapshot; + $netAmount = $snapshot['net_amount'] ?? 0; + } + + $order->setAttribute('total_cost_price', $totalCostPrice); + $order->setAttribute('total_cost_price_formatted', 'Rp '.number_format($totalCostPrice, 0, ',', '.')); + $order->setAttribute('net_amount', $netAmount); + $order->setAttribute('net_amount_formatted', 'Rp '.number_format($netAmount, 0, ',', '.')); + } } diff --git a/app/Services/System/AnalysisService.php b/app/Services/System/AnalysisService.php index 74cb8c8..5bbb389 100644 --- a/app/Services/System/AnalysisService.php +++ b/app/Services/System/AnalysisService.php @@ -223,11 +223,25 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = ->get() ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); + $totalCostPrice = 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') + ->where('product_prices.type', PriceType::HARGA_MODAL->value); + }) + ->where('orders.status', OrderStatus::COMPLETED) + ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) + ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) + ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) + ->selectRaw('COALESCE(SUM(order_items.quantity * product_prices.price), 0) as total_hpp') + ->value('total_hpp'); + return [ 'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0), 'total_discount' => (int) ($revenueSummary->total_discount ?? 0), 'total_marketplace_fees' => $totalMarketplaceFees, - 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees, + 'total_cost_price' => (int) ($totalCostPrice ?? 0), + 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees + (int) ($totalCostPrice ?? 0), 'total_orders' => (int) ($revenueSummary->total_orders ?? 0), 'avg_order' => (int) ($revenueSummary->avg_order ?? 0), ]; @@ -269,6 +283,24 @@ 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() + ->join('orders', 'order_items.order_id', '=', 'orders.id') + ->leftJoin('product_prices', function ($join) { + $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') + ->where('product_prices.type', PriceType::HARGA_MODAL->value); + }) + ->where('orders.status', OrderStatus::COMPLETED) + ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) + ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) + ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) + ->selectRaw(" + DATE_FORMAT(orders.created_at, '%Y-%m') as month_key, + COALESCE(SUM(order_items.quantity * product_prices.price), 0) as hpp + ") + ->groupBy('month_key') + ->get() + ->pluck('hpp', 'month_key'); + if ($monthlyData->isEmpty()) { return []; } @@ -284,8 +316,9 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = $revenue = $monthlyData->firstWhere('month_key', $key); $fees = $monthlyFees->get($key, 0); + $hppVal = (int) $monthlyHpp->get($key, 0); $discount = (int) ($revenue->total_discount ?? 0); - $deduction = $discount + $fees; + $deduction = $discount + $fees + $hppVal; $result[] = [ 'month' => $monthLabel, @@ -459,15 +492,18 @@ public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = n $totalQty = (int) ($itemsData->total_qty ?? 0); $totalItems = (int) ($itemsData->total_items ?? 0); - // HPP from cutting_result_prices + // HPP from product_prices with type = 'harga_modal' $hpp = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') - ->leftJoin('cutting_result_prices', 'order_items.product_variant_id', '=', 'cutting_result_prices.product_variant_id') + ->leftJoin('product_prices', function ($join) { + $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') + ->where('product_prices.type', PriceType::HARGA_MODAL->value); + }) ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) - ->selectRaw('COALESCE(SUM(order_items.quantity * cutting_result_prices.cost_per_unit), 0) as total_hpp') + ->selectRaw('COALESCE(SUM(order_items.quantity * product_prices.price), 0) as total_hpp') ->value('total_hpp'); $totalHpp = (int) ($hpp ?? 0); diff --git a/app/Services/System/DashboardService.php b/app/Services/System/DashboardService.php index 2cbb9a5..fdca500 100644 --- a/app/Services/System/DashboardService.php +++ b/app/Services/System/DashboardService.php @@ -5,6 +5,7 @@ use App\Enums\OrderChannel; use App\Enums\OrderStatus; use App\Enums\PaymentType; +use App\Enums\PriceType; use App\Enums\Role; use App\Models\Attendance; use App\Models\CashAccount; @@ -14,6 +15,7 @@ use App\Models\Expense; use App\Models\LeaveRequest; use App\Models\Order; +use App\Models\OrderItem; use App\Models\Purchase; use App\Models\User; use Carbon\Carbon; @@ -148,11 +150,25 @@ public function getRevenueSummary(): array ->get() ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); + $totalCostPrice = 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') + ->where('product_prices.type', PriceType::HARGA_MODAL->value); + }) + ->where('orders.status', OrderStatus::COMPLETED) + ->whereDate('orders.created_at', Carbon::today()) + ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) + ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) + ->selectRaw('COALESCE(SUM(order_items.quantity * product_prices.price), 0) as total_hpp') + ->value('total_hpp'); + return [ 'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0), 'total_discount' => (int) ($revenueSummary->total_discount ?? 0), 'total_marketplace_fees' => $totalMarketplaceFees, - 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees, + 'total_cost_price' => (int) ($totalCostPrice ?? 0), + 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees + (int) ($totalCostPrice ?? 0), 'total_orders' => (int) ($revenueSummary->total_orders ?? 0), 'avg_order' => (int) ($revenueSummary->avg_order ?? 0), ]; diff --git a/resources/js/pages/admin/Dashboard.vue b/resources/js/pages/admin/Dashboard.vue index bb3a9cd..2c1907e 100644 --- a/resources/js/pages/admin/Dashboard.vue +++ b/resources/js/pages/admin/Dashboard.vue @@ -326,9 +326,13 @@ const chartsGridClass = computed(() => { revenueSummary.total_deduction, ), }, + { + label: 'Harga Modal', + value: 'Rp' + formatRupiah(revenueSummary.total_cost_price ?? 0), + }, { label: 'Potongan', - value: 'Rp' + formatRupiah(revenueSummary.total_deduction), + value: 'Rp' + formatRupiah(revenueSummary.total_marketplace_fees ?? 0), }, { label: 'Diskon', diff --git a/resources/js/pages/admin/manage/orders/Show.vue b/resources/js/pages/admin/manage/orders/Show.vue index 9524a2b..269e1ba 100644 --- a/resources/js/pages/admin/manage/orders/Show.vue +++ b/resources/js/pages/admin/manage/orders/Show.vue @@ -324,17 +324,18 @@ const feeEntries = computed(() => { - - + + - Rincian Biaya {{ snapshot.platform === 'tiktok' ? 'TikTok Shop' : 'Shopee' }} - Afiliasi + Rincian Biaya {{ snapshot.platform === 'tiktok' ? 'TikTok Shop' : 'Shopee' }} + Rincian Keuntungan + Afiliasi -
+
@@ -361,25 +362,28 @@ const feeEntries = computed(() => {
+
- -
Dasar Perhitungan - {{ formatRp(snapshot.base_amount) }} + {{ formatRp(snapshot ? snapshot.base_amount : order.total_amount) }}
-
- Total Potongan +
+ Total Potongan Marketplace -{{ formatRp(snapshot.total_fee_amount) }}
+
+ Total Harga Modal + -{{ order.total_cost_price_formatted }} +
Pendapatan Bersih - {{ formatRp(snapshot.net_amount) }} + {{ order.net_amount_formatted }}
diff --git a/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue b/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue index c8bbe5c..0cdbeab 100644 --- a/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue +++ b/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue @@ -104,8 +104,8 @@ function rowNumber(index: number): number { Potongan Marketplace -{{ order.marketplace_settings_snapshot.total_fee_amount_formatted }} - - Total Bersih {{ order.marketplace_settings_snapshot.net_amount_formatted }} + + Total Bersih {{ order.net_amount_formatted }}

diff --git a/resources/js/types/order.ts b/resources/js/types/order.ts index ede8f72..eca0d46 100644 --- a/resources/js/types/order.ts +++ b/resources/js/types/order.ts @@ -82,6 +82,10 @@ export type OrderListItem = { }; items: OrderItemListItem[]; marketplace_settings_snapshot?: MarketplaceSettingsSnapshot | null; + total_cost_price?: number; + total_cost_price_formatted?: string; + net_amount?: number; + net_amount_formatted?: string; }; export type OrderCartItem = {