parfum/app/Livewire/Forms/Studio/Manage/OrderForm.php

225 lines
7.7 KiB
PHP

<?php
namespace App\Livewire\Forms\Studio\Manage;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Enums\PointRecordType;
use App\Models\Customer;
use App\Models\Order;
use App\Models\Payment;
use App\Models\PointRecord;
use App\Models\Voucher;
use App\Rules\UnsignedInteger;
use App\Traits\Media\WithMediaHandler;
use App\Traits\Order\WithDiscount;
use App\Traits\Order\WithOrderItem;
use App\Traits\Order\WithUpdateStock;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Form;
class OrderForm extends Form
{
use WithDiscount, WithMediaHandler, WithOrderItem, WithUpdateStock;
public ?Order $order = null;
public string $outlet_id = '';
public string $channel = '1';
public string $status = '4';
public string $payment_method = '1';
public string $perfume_id = '';
public string $bottle_id = '';
public string $quality_id = '';
public string $bottle_size = '';
public string $product_id = '';
public string $quantity_product = '';
public string $item_id = '';
public string $quantity_edit = '';
public string $discount = '';
public string $member_id = '';
public string $voucher_id = '';
public function rules(): array
{
return [
'outlet_id' => ['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, $discount, $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' => $discount,
'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,
];
}
}