feat: Ensure order totals are non-negative, improve voucher deduction validation, and add pessimistic locking for stock updates.

This commit is contained in:
Yoga Pangestu 2025-12-17 04:21:37 +07:00
parent 4b1566c1a2
commit 2978d34e9e
3 changed files with 35 additions and 19 deletions

View File

@ -109,7 +109,7 @@ public function store(): array
$discount = $voucherDiscount + $manualDiscount; $discount = $voucherDiscount + $manualDiscount;
// Calculate total (subtotal - manual discount - voucher discount) // Calculate total (subtotal - manual discount - voucher discount)
$total = $afterManual - $voucherDiscount; $total = max(0, $afterManual - $voucherDiscount);
$pointsEarned = (int) floor($total / 5000); $pointsEarned = (int) floor($total / 5000);
@ -141,7 +141,7 @@ public function store(): array
// Deduct item stock from the outlet inventory // Deduct item stock from the outlet inventory
$result = $this->decreaseOutletStock($order->outlet, $item); $result = $this->decreaseOutletStock($order->outlet, $item);
if ($result && ! $result['status']) { if (! $result['status']) {
throw new \Exception($result['message']); throw new \Exception($result['message']);
} }
@ -161,27 +161,35 @@ public function store(): array
// Deduct voucher quantity // Deduct voucher quantity
if ($this->voucher_id) { if ($this->voucher_id) {
if (! $member) {
throw new \Exception('Data member tidak valid.');
}
$userVoucher = DB::table('user_voucher') $userVoucher = DB::table('user_voucher')
->where('user_id', $member->id) ->where('user_id', $member->id)
->where('voucher_id', $this->voucher_id) ->where('voucher_id', $this->voucher_id)
->lockForUpdate() ->lockForUpdate()
->first(); ->first();
if ($userVoucher) { if (! $userVoucher) {
if (! $userVoucher->quantity) { throw new \Exception('Member tidak memiliki voucher ini.');
return; }
} elseif ($userVoucher->quantity > 1) {
DB::table('user_voucher') if ($userVoucher->quantity < 1) {
->where('id', $userVoucher->id) throw new \Exception('Voucher sudah habis terpakai.');
->update([ }
'quantity' => $userVoucher->quantity - 1,
'updated_at' => now(), if ($userVoucher->quantity > 1) {
]); DB::table('user_voucher')
} else { ->where('id', $userVoucher->id)
DB::table('user_voucher') ->update([
->where('id', $userVoucher->id) 'quantity' => $userVoucher->quantity - 1,
->delete(); 'updated_at' => now(),
} ]);
} else {
DB::table('user_voucher')
->where('id', $userVoucher->id)
->delete();
} }
} }

View File

@ -11,6 +11,8 @@ public function getSubTotal(): int
public function getTotal(): int public function getTotal(): int
{ {
return $this->items->sum(fn ($item) => $item->unit_price * $item->quantity - parseRupiahToInt($this->form->discount) - $this->voucherDiscount); $subtotal = $this->items->sum(fn ($item) => $item->unit_price * $item->quantity);
return max(0, $subtotal - parseRupiahToInt($this->form->discount) - $this->voucherDiscount);
} }
} }

View File

@ -27,7 +27,11 @@ public function increaseOutletStock($outlet, $item): void
return; return;
} }
$existing = $outlet->{$relation}()->withPivot('stock')->where("{$relation}.id", $item->orderable_id)->first(); $existing = $outlet->{$relation}()
->withPivot('stock')
->where("{$relation}.id", $item->orderable_id)
->lockForUpdate()
->first();
if ($existing) { if ($existing) {
$currentStock = $existing->pivot->stock ?? 0; $currentStock = $existing->pivot->stock ?? 0;
@ -112,5 +116,7 @@ public function decreaseOutletStock($outlet, $item)
'purchase_id' => $item->purchase_id ?? null, 'purchase_id' => $item->purchase_id ?? null,
] ]
); );
return ['status' => true];
} }
} }