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