['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(): array { $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 = parseRupiahToInt($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 = max(0, $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, $manualDiscount, $voucherDiscount, $total, $items, $pointsEarned) { $customer = $this->member_id ? Customer::find($this->member_id) : null; $member = $customer?->user()->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, 'customer_id' => $customer?->id, 'invoice_number' => $invoiceNumber, 'cogs' => $cogs, 'subtotal' => $subtotal, 'discount' => $manualDiscount, 'voucher_discount' => $voucherDiscount, 'total' => $total, 'channel' => (int) $this->channel, 'status' => (int) $this->status, 'ordered_at' => now(), ]); foreach ($items as $item) { // Deduct item stock from the outlet inventory $result = $this->decreaseOutletStock($order->outlet, $item); if (! $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) { 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) { 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(); } } // Update member total spending and reward points if ($member && $member->membership) { $member->membership->increment('total_spending', $total); $member->membership->increment('reward_points', $pointsEarned); $member->membership->update(['last_transaction_at' => now()]); $member->membership->recalculateTier(); PointRecord::create([ 'user_id' => $member->id, 'description' => 'Pembelian #'.$order->invoice_number, 'change' => $pointsEarned, 'is_addition' => true, 'type' => PointRecordType::REWARD, ]); } }); } catch (\Exception $e) { return [ 'status' => false, 'message' => $e->getMessage(), ]; } return [ 'status' => true, 'data' => $order, ]; } }