Refactor order service to utilize new marketplace snapshot structure. Update create and update methods to call buildOrderSnapshot with total amount and line items for improved fee calculation. Introduce lineItemsForSnapshot method for consistent item formatting in snapshots.

This commit is contained in:
Yoga Pangestu 2026-06-13 01:27:53 +07:00
parent 7d721cab31
commit c30fe015e2
3 changed files with 132 additions and 3 deletions

View File

@ -299,7 +299,11 @@ public function create(array $validated, User $user): Order
'created_by_id' => $user->id,
'subtotal' => $subtotal,
'discount' => $discount,
'marketplace_settings_snapshot' => $this->marketplaceService->snapshotForChannel($channel),
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($draftItems),
),
'total_amount' => $totalAmount,
'notes' => $validated['notes'] ?? null,
]);
@ -346,7 +350,11 @@ public function update(Order $order, array $validated): void
$order->price_type = $priceType;
$order->subtotal = $subtotal;
$order->discount = $discount;
$order->marketplace_settings_snapshot = $this->marketplaceService->snapshotForChannel($channel);
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($lineItems),
);
$order->total_amount = $totalAmount;
$order->notes = $validated['notes'] ?? null;
$order->save();
@ -531,6 +539,21 @@ private function incrementStock(OrderItem $item): void
->increment('stock', $item->quantity);
}
/**
* @param EloquentCollection<int, OrderItem>|list<array{quantity: int, subtotal: int}> $items
* @return list<array{quantity: int, subtotal: int}>
*/
private function lineItemsForSnapshot(EloquentCollection|array $items): array
{
return collect($items)
->map(fn (OrderItem|array $item) => [
'quantity' => (int) (is_array($item) ? $item['quantity'] : $item->quantity),
'subtotal' => (int) (is_array($item) ? $item['subtotal'] : $item->subtotal),
])
->values()
->all();
}
private function applySorting(Builder $query, string $sort, string $direction): void
{
if (in_array($sort, ['created_at', 'total_amount', 'discount', 'subtotal', 'order_number'], true)) {

View File

@ -4,6 +4,7 @@
use App\Enums\OrderChannel;
use App\Settings\MarketplaceSettings;
use App\Support\Marketplace\MarketplaceFeeCalculator;
use App\Support\Marketplace\MarketplaceFeeRule;
class MarketplaceService
@ -76,9 +77,37 @@ public function updateMarketplace(array $validated): void
}
/**
* @param list<array{quantity: int, subtotal: int}> $lineItems
* @return array<string, mixed>|null
*/
public function snapshotForChannel(OrderChannel $channel): ?array
public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, array $lineItems): ?array
{
$feeSnapshot = $this->feeRulesForChannel($channel);
if ($feeSnapshot === null) {
return null;
}
$calculation = app(MarketplaceFeeCalculator::class)->calculate(
$feeSnapshot['fees'],
$totalAmount,
$lineItems,
);
return [
'platform' => $feeSnapshot['platform'],
'fees' => $feeSnapshot['fees'],
'base_amount' => $totalAmount,
'results' => $calculation['results'],
'total_fee_amount' => $calculation['total_fee_amount'],
'net_amount' => $calculation['net_amount'],
];
}
/**
* @return array{platform: string, fees: array<string, array{scope: string, value_type: string, value: float}>}|null
*/
private function feeRulesForChannel(OrderChannel $channel): ?array
{
$settings = app(MarketplaceSettings::class);

View File

@ -0,0 +1,77 @@
<?php
namespace App\Support\Marketplace;
use App\Enums\MarketplaceFeeScope;
use App\Enums\MarketplaceFeeValueType;
class MarketplaceFeeCalculator
{
/**
* @param array<string, array{scope: string, value_type: string, value: float|int}> $fees
* @param list<array{quantity: int, subtotal: int}> $lineItems
* @return array{
* results: array<string, array{amount: int, balance_after: int}>,
* total_fee_amount: int,
* net_amount: int
* }
*/
public function calculate(array $fees, int $totalAmount, array $lineItems): array
{
$results = [];
$balance = $totalAmount;
$totalFeeAmount = 0;
foreach ($fees as $key => $ruleData) {
$amount = $this->calculateFeeAmount(
MarketplaceFeeRule::fromArray($ruleData),
$totalAmount,
$lineItems,
);
$balance = max($balance - $amount, 0);
$totalFeeAmount += $amount;
$results[$key] = [
'amount' => $amount,
'balance_after' => $balance,
];
}
return [
'results' => $results,
'total_fee_amount' => $totalFeeAmount,
'net_amount' => max($totalAmount - $totalFeeAmount, 0),
];
}
/**
* @param list<array{quantity: int, subtotal: int}> $lineItems
*/
private function calculateFeeAmount(MarketplaceFeeRule $rule, int $totalAmount, array $lineItems): int
{
if ($rule->scope === MarketplaceFeeScope::TRANSACTION) {
return $this->calculateFromBasis(
$rule,
$totalAmount,
);
}
return (int) collect($lineItems)->sum(function (array $item) use ($rule): int {
if ($rule->valueType === MarketplaceFeeValueType::FLAT) {
return (int) round($rule->value * $item['quantity']);
}
return (int) round($item['subtotal'] * $rule->value / 100);
});
}
private function calculateFromBasis(MarketplaceFeeRule $rule, int $basis): int
{
if ($rule->valueType === MarketplaceFeeValueType::FLAT) {
return (int) round($rule->value);
}
return (int) round($basis * $rule->value / 100);
}
}