feat: enhance order management by calculating and displaying total cost price and net amount in order summaries
This commit is contained in:
parent
ce26551666
commit
60b358fb49
@ -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, ',', '.'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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),
|
||||
];
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -324,17 +324,18 @@ const feeEntries = computed(() => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Marketplace Fee Breakdown -->
|
||||
<Card v-if="snapshot">
|
||||
<!-- Fee & Profit Breakdown -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Receipt class="size-5" />
|
||||
Rincian Biaya {{ snapshot.platform === 'tiktok' ? 'TikTok Shop' : 'Shopee' }}
|
||||
<Badge v-if="snapshot.is_affiliate" variant="outline" class="ml-1">Afiliasi</Badge>
|
||||
<span v-if="snapshot">Rincian Biaya {{ snapshot.platform === 'tiktok' ? 'TikTok Shop' : 'Shopee' }}</span>
|
||||
<span v-else>Rincian Keuntungan</span>
|
||||
<Badge v-if="snapshot?.is_affiliate" variant="outline" class="ml-1">Afiliasi</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="overflow-x-auto">
|
||||
<div v-if="snapshot" class="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@ -361,25 +362,28 @@ const feeEntries = computed(() => {
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Separator class="my-4" />
|
||||
</div>
|
||||
|
||||
<Separator class="my-4" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Dasar Perhitungan</span>
|
||||
<span class="tabular-nums">{{ formatRp(snapshot.base_amount) }}</span>
|
||||
<span class="tabular-nums">{{ formatRp(snapshot ? snapshot.base_amount : order.total_amount) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Total Potongan</span>
|
||||
<div v-if="snapshot" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Total Potongan Marketplace</span>
|
||||
<span class="tabular-nums text-destructive">-{{ formatRp(snapshot.total_fee_amount)
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="order.total_cost_price > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Total Harga Modal</span>
|
||||
<span class="tabular-nums text-destructive">-{{ order.total_cost_price_formatted }}</span>
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold">Pendapatan Bersih</span>
|
||||
<span class="text-lg font-bold tabular-nums text-green-600">
|
||||
{{ formatRp(snapshot.net_amount) }}
|
||||
{{ order.net_amount_formatted }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -104,8 +104,8 @@ function rowNumber(index: number): number {
|
||||
<span v-if="order.marketplace_settings_snapshot?.total_fee_amount" class="text-destructive">
|
||||
Potongan Marketplace <strong class="text-destructive">-{{ order.marketplace_settings_snapshot.total_fee_amount_formatted }}</strong>
|
||||
</span>
|
||||
<span v-if="order.marketplace_settings_snapshot?.net_amount">
|
||||
Total Bersih <strong class="text-green-600">{{ order.marketplace_settings_snapshot.net_amount_formatted }}</strong>
|
||||
<span v-if="order.net_amount_formatted">
|
||||
Total Bersih <strong class="text-green-600">{{ order.net_amount_formatted }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="order.notes" class="text-muted-foreground text-sm">
|
||||
|
||||
@ -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 = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user