210 lines
7.1 KiB
PHP
210 lines
7.1 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\Models\Order;
|
|
use App\Models\Payment;
|
|
use App\Models\User;
|
|
use App\Models\Voucher;
|
|
use App\Rules\UnsignedInteger;
|
|
use App\Traits\Order\WithDiscount;
|
|
use App\Traits\Order\WithOrderItem;
|
|
use App\Traits\Order\WithUpdateStock;
|
|
use App\Traits\WithMediaHandler;
|
|
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 = $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,
|
|
];
|
|
}
|
|
}
|