['required', Rule::exists('outlets', 'id')], 'channel' => ['required', Rule::in(OrderChannel::values())], 'status' => ['required', Rule::in(OrderStatus::values())], 'payment_method' => ['required', Rule::in(PaymentMethod::values())], 'discount' => ['nullable', new UnsignedInteger], 'member_id' => ['nullable', Rule::exists('customers', 'id')], 'voucher_id' => ['nullable', Rule::exists('vouchers', 'id')], ]; } public function validationAttributes(): array { return [ 'payment_method' => 'metode pembayaran', 'discount' => 'diskon', 'member_id' => 'member', 'voucher_id' => 'voucher', ]; } public function store() { $this->validate(); $items = $this->loadOrderItems(); // Generate a unique invoice number based on today's paid order count $todayCount = Order::whereDate('created_at', now())->paid()->count() + 1; $invoiceNumber = 'INV'.now()->format('Ymd').str_pad($todayCount, 4, '0', STR_PAD_LEFT); // Calculate total cost of goods sold (COGS) — internal purchase cost $cogs = $items->sum(fn ($item) => $item->cogs * $item->quantity); // Calculate subtotal before any global discount $subtotal = $items->sum(fn ($item) => $item->unit_price * $item->quantity); $manualDiscount = replaceCurrency($this->discount); // Apply manual discount $afterManual = $subtotal - $manualDiscount; $voucherDiscount = $this->calculateDiscount($subtotal, $this->voucher_id); if (is_array($voucherDiscount)) { return $voucherDiscount; } // Calculate total discount $discount = $voucherDiscount + $manualDiscount; // Calculate total (subtotal - manual discount - voucher discount) $total = $afterManual - $voucherDiscount; $pointsEarned = (int) floor($total / 5000); // Wrap the entire operation in a database transaction // Ensures atomicity — if any step fails, all changes are rolled back try { DB::transaction(function () use (&$order, $invoiceNumber, $cogs, $subtotal, $discount, $total, $items, $pointsEarned) { $member = User::whereHas('customer', fn ($q) => $q->where('id', $this->member_id)) ->with(['membership' => fn ($q) => $q->lockForUpdate()]) ->first(); $order = Order::create([ 'outlet_id' => $this->outlet_id, 'user_id' => auth()->id(), 'voucher_id' => $this->voucher_id == '' ? null : $this->voucher_id, 'customer_id' => $this->member_id == '' ? null : $this->member_id, 'invoice_number' => $invoiceNumber, 'cogs' => $cogs, 'subtotal' => $subtotal, 'discount' => $discount, 'total' => $total, 'channel' => $this->channel, 'status' => $this->status, 'payment_method' => $this->payment_method, 'ordered_at' => now(), ]); foreach ($items as $item) { // Deduct item stock from the outlet inventory $result = $this->decreaseOutletStock($order->outlet, $item); if ($result && ! $result['status']) { throw new \Exception($result['message']); } // Attach all order items to the newly created order $item->order_id = $order->id; $item->save(); } // Automatically record payment for the order Payment::create([ 'order_id' => $order->id, 'user_id' => auth()->id(), 'method' => $this->payment_method, 'amount' => $total, 'status' => PaymentStatus::PAID->value, ]); // Deduct voucher quantity if ($this->voucher_id) { $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(); } } } // Update member tier points if ($member && $member->membership) { $member->membership()->update([ 'tier_points' => DB::raw("tier_points + $pointsEarned"), 'reward_points' => DB::raw("reward_points + $pointsEarned"), 'last_transaction_at' => now(), ]); } }); } catch (\Exception $e) { return [ 'status' => false, 'message' => $e->getMessage(), ]; } return [ 'status' => true, 'data' => $order, ]; } }