diff --git a/app/Livewire/Datatable/Studio/Manage/OrdersTable.php b/app/Livewire/Datatable/Studio/Manage/OrdersTable.php index df6e0fa..144f926 100644 --- a/app/Livewire/Datatable/Studio/Manage/OrdersTable.php +++ b/app/Livewire/Datatable/Studio/Manage/OrdersTable.php @@ -23,6 +23,8 @@ public function columns(): array return [ Column::make('Pegawai', 'user.employee.full_name')->searchable(), + Column::make('Voucher', 'voucher.name')->searchable(), + Column::make('Nomor Invoice') ->label(function ($row) { return <<with(['user', 'user.employee', 'customer']); + ->with(['user', 'user.employee', 'customer', 'voucher']); } } diff --git a/app/Livewire/Forms/Studio/Manage/OrderForm.php b/app/Livewire/Forms/Studio/Manage/OrderForm.php index 657f9d3..91be13e 100644 --- a/app/Livewire/Forms/Studio/Manage/OrderForm.php +++ b/app/Livewire/Forms/Studio/Manage/OrderForm.php @@ -8,7 +8,10 @@ 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; @@ -18,7 +21,7 @@ class OrderForm extends Form { - use WithMediaHandler, WithOrderItem, WithUpdateStock; + use WithDiscount, WithMediaHandler, WithOrderItem, WithUpdateStock; public ?Order $order = null; @@ -50,6 +53,8 @@ class OrderForm extends Form public string $member_id = ''; + public string $voucher_id = ''; + public function rules(): array { return [ @@ -59,6 +64,7 @@ public function rules(): array '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')], ]; } @@ -68,6 +74,7 @@ public function validationAttributes(): array 'payment_method' => 'metode pembayaran', 'discount' => 'diskon', 'member_id' => 'member', + 'voucher_id' => 'voucher', ]; } @@ -87,17 +94,32 @@ public function store() // Calculate subtotal before any global discount $subtotal = $items->sum(fn ($item) => $item->unit_price * $item->quantity); - $discount = replaceCurrency($this->discount); + $manualDiscount = replaceCurrency($this->discount); - // Apply global discount to get final total - $total = $subtotal - $discount; + // Apply manual discount + $afterManual = $subtotal - $manualDiscount; + + $voucherDiscount = $this->calculateDiscount($afterManual); + + if (is_array($voucherDiscount)) { + return $voucherDiscount; + } + + // Calculate total discount + $discount = $voucherDiscount + $manualDiscount; + + // Calculate total (subtotal - manual discount - voucher discount) + $total = $afterManual - $voucherDiscount; + + $member = User::whereHas('customer', fn ($q) => $q->where('id', $this->member_id))->first(); // Wrap the entire operation in a database transaction // Ensures atomicity — if any step fails, all changes are rolled back - DB::transaction(function () use ($invoiceNumber, $cogs, $subtotal, $discount, $total, $items) { + DB::transaction(function () use ($invoiceNumber, $cogs, $subtotal, $discount, $total, $items, $member) { $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, @@ -126,6 +148,33 @@ public function store() 'amount' => $total, 'status' => PaymentStatus::PAID->value, ]); + + 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 > 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(); + } + } + } }); + + return [ + 'status' => true, + ]; } } diff --git a/app/Livewire/Studio/Manage/Order/Create.php b/app/Livewire/Studio/Manage/Order/Create.php index d4a3da5..3438683 100644 --- a/app/Livewire/Studio/Manage/Order/Create.php +++ b/app/Livewire/Studio/Manage/Order/Create.php @@ -43,6 +43,8 @@ class Create extends Component public $items; + public array $vouchers = []; + public function mount() { $this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray(); @@ -75,7 +77,13 @@ public function save() return; } - $this->form->store(); + $result = $this->form->store(); + + if (! $result['status']) { + $this->toast($result['message'], 'Gagal', 'danger'); + + return; + } $this->dispatch('refreshDatatable'); diff --git a/app/Traits/Order/WithDiscount.php b/app/Traits/Order/WithDiscount.php index fffe509..506ee94 100644 --- a/app/Traits/Order/WithDiscount.php +++ b/app/Traits/Order/WithDiscount.php @@ -2,6 +2,9 @@ namespace App\Traits\Order; +use App\Enums\VoucherType; +use App\Models\Voucher; + trait WithDiscount { public function updatedFormDiscount(string $value) @@ -11,4 +14,39 @@ public function updatedFormDiscount(string $value) $this->total = $total - $discount; } + + public function calculateDiscount(int $subtotal) + { + $voucher = Voucher::find($this->voucher_id); + $discount = 0; + + if ($voucher) { + // Check minimum purchase + if ($voucher->min_purchase && $subtotal < $voucher->min_purchase) { + $this->discount = 0; + + return [ + 'status' => false, + 'message' => 'Minimum pembelian harus sebesar '.currency($voucher->min_purchase, 'Rp').' untuk menggunakan voucher ini.', + ]; + } + + // Calculate discount based on voucher type + if ($voucher->type === VoucherType::PERCENTAGE) { + $discount = ($subtotal * $voucher->discount_amount) / 100; + + // Apply maximum discount limit + if ($voucher->max_discount && $discount > $voucher->max_discount) { + $discount = $voucher->max_discount; + } + } else { + $discount = $voucher->discount_amount; + } + + // Ensure discount does not exceed subtotal + $discount = min($discount, $subtotal); + } + + return $discount; + } } diff --git a/app/Traits/Order/WithMember.php b/app/Traits/Order/WithMember.php index 3ac40a9..2c77b58 100644 --- a/app/Traits/Order/WithMember.php +++ b/app/Traits/Order/WithMember.php @@ -4,6 +4,7 @@ use App\Enums\UserStatus; use App\Models\Customer; +use App\Models\Voucher; use Livewire\Attributes\Computed; trait WithMember @@ -27,4 +28,36 @@ public function members() ]) ->toArray(); } + + public function updatedFormMemberId(Customer $customer) + { + $customer->load('user'); + + $memberId = $customer->user?->id; + + $this->vouchers = Voucher::whereHas('users', function ($q) use ($memberId) { + $q->where('user_id', $memberId); + }) + ->where(function ($q) { + $q->where(function ($q) { + $q->whereNull('start_date') + ->orWhere('start_date', '<=', now()); + }); + $q->where(function ($q) { + $q->whereNull('end_date') + ->orWhere('end_date', '>=', now()); + }); + }) + ->select('id', 'name', 'type', 'discount_amount', 'max_discount') + ->orderByRaw(" + CASE + WHEN type = 'percentage' THEN + LEAST(discount_amount / 100 * COALESCE(max_discount, 99999999), COALESCE(max_discount, 99999999)) + ELSE + discount_amount + END DESC + ") + ->pluck('name', 'id') + ->toArray(); + } } diff --git a/resources/views/livewire/studio/manage/order/form.blade.php b/resources/views/livewire/studio/manage/order/form.blade.php index 5f15f03..644edea 100644 --- a/resources/views/livewire/studio/manage/order/form.blade.php +++ b/resources/views/livewire/studio/manage/order/form.blade.php @@ -290,6 +290,23 @@ class="text-xs text-gray-400">{{ currency($item->quantity, '') }} + + @if (!empty($vouchers)) + + Voucher + Voucher diurutkan berdasarkan dengan diskon yang paling besar. + + + @foreach ($vouchers as $key => $name) + + {{ $name }} + + @endforeach + + + + @endif