feat(order): menambahkan kolom voucher ke table
- calculate voucher diskon dan manual diskon - menambahkan input voucher - munculkan input voucher hanya ketika ada member
This commit is contained in:
parent
4067882f8a
commit
7b0abd6aa2
@ -23,6 +23,8 @@ public function columns(): array
|
|||||||
return [
|
return [
|
||||||
Column::make('Pegawai', 'user.employee.full_name')->searchable(),
|
Column::make('Pegawai', 'user.employee.full_name')->searchable(),
|
||||||
|
|
||||||
|
Column::make('Voucher', 'voucher.name')->searchable(),
|
||||||
|
|
||||||
Column::make('Nomor Invoice')
|
Column::make('Nomor Invoice')
|
||||||
->label(function ($row) {
|
->label(function ($row) {
|
||||||
return <<<HTML
|
return <<<HTML
|
||||||
@ -135,6 +137,6 @@ public function columns(): array
|
|||||||
public function builder(): Builder
|
public function builder(): Builder
|
||||||
{
|
{
|
||||||
return Order::select('orders.id', 'orders.user_id', 'invoice_number', 'customer_id', 'orders.created_at', 'cogs', 'subtotal', 'discount', 'total', 'orders.status', 'channel')
|
return Order::select('orders.id', 'orders.user_id', 'invoice_number', 'customer_id', 'orders.created_at', 'cogs', 'subtotal', 'discount', 'total', 'orders.status', 'channel')
|
||||||
->with(['user', 'user.employee', 'customer']);
|
->with(['user', 'user.employee', 'customer', 'voucher']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,10 @@
|
|||||||
use App\Enums\PaymentStatus;
|
use App\Enums\PaymentStatus;
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Voucher;
|
||||||
use App\Rules\UnsignedInteger;
|
use App\Rules\UnsignedInteger;
|
||||||
|
use App\Traits\Order\WithDiscount;
|
||||||
use App\Traits\Order\WithOrderItem;
|
use App\Traits\Order\WithOrderItem;
|
||||||
use App\Traits\Order\WithUpdateStock;
|
use App\Traits\Order\WithUpdateStock;
|
||||||
use App\Traits\WithMediaHandler;
|
use App\Traits\WithMediaHandler;
|
||||||
@ -18,7 +21,7 @@
|
|||||||
|
|
||||||
class OrderForm extends Form
|
class OrderForm extends Form
|
||||||
{
|
{
|
||||||
use WithMediaHandler, WithOrderItem, WithUpdateStock;
|
use WithDiscount, WithMediaHandler, WithOrderItem, WithUpdateStock;
|
||||||
|
|
||||||
public ?Order $order = null;
|
public ?Order $order = null;
|
||||||
|
|
||||||
@ -50,6 +53,8 @@ class OrderForm extends Form
|
|||||||
|
|
||||||
public string $member_id = '';
|
public string $member_id = '';
|
||||||
|
|
||||||
|
public string $voucher_id = '';
|
||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -59,6 +64,7 @@ public function rules(): array
|
|||||||
'payment_method' => ['required', Rule::in(PaymentMethod::values())],
|
'payment_method' => ['required', Rule::in(PaymentMethod::values())],
|
||||||
'discount' => ['nullable', new UnsignedInteger],
|
'discount' => ['nullable', new UnsignedInteger],
|
||||||
'member_id' => ['nullable', Rule::exists('customers', 'id')],
|
'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',
|
'payment_method' => 'metode pembayaran',
|
||||||
'discount' => 'diskon',
|
'discount' => 'diskon',
|
||||||
'member_id' => 'member',
|
'member_id' => 'member',
|
||||||
|
'voucher_id' => 'voucher',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,17 +94,32 @@ public function store()
|
|||||||
// Calculate subtotal before any global discount
|
// Calculate subtotal before any global discount
|
||||||
$subtotal = $items->sum(fn ($item) => $item->unit_price * $item->quantity);
|
$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
|
// Apply manual discount
|
||||||
$total = $subtotal - $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
|
// Wrap the entire operation in a database transaction
|
||||||
// Ensures atomicity — if any step fails, all changes are rolled back
|
// 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([
|
$order = Order::create([
|
||||||
'outlet_id' => $this->outlet_id,
|
'outlet_id' => $this->outlet_id,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
|
'voucher_id' => $this->voucher_id == '' ? null : $this->voucher_id,
|
||||||
'customer_id' => $this->member_id == '' ? null : $this->member_id,
|
'customer_id' => $this->member_id == '' ? null : $this->member_id,
|
||||||
'invoice_number' => $invoiceNumber,
|
'invoice_number' => $invoiceNumber,
|
||||||
'cogs' => $cogs,
|
'cogs' => $cogs,
|
||||||
@ -126,6 +148,33 @@ public function store()
|
|||||||
'amount' => $total,
|
'amount' => $total,
|
||||||
'status' => PaymentStatus::PAID->value,
|
'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,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,8 @@ class Create extends Component
|
|||||||
|
|
||||||
public $items;
|
public $items;
|
||||||
|
|
||||||
|
public array $vouchers = [];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray();
|
$this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray();
|
||||||
@ -75,7 +77,13 @@ public function save()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->form->store();
|
$result = $this->form->store();
|
||||||
|
|
||||||
|
if (! $result['status']) {
|
||||||
|
$this->toast($result['message'], 'Gagal', 'danger');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->dispatch('refreshDatatable');
|
$this->dispatch('refreshDatatable');
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Traits\Order;
|
namespace App\Traits\Order;
|
||||||
|
|
||||||
|
use App\Enums\VoucherType;
|
||||||
|
use App\Models\Voucher;
|
||||||
|
|
||||||
trait WithDiscount
|
trait WithDiscount
|
||||||
{
|
{
|
||||||
public function updatedFormDiscount(string $value)
|
public function updatedFormDiscount(string $value)
|
||||||
@ -11,4 +14,39 @@ public function updatedFormDiscount(string $value)
|
|||||||
|
|
||||||
$this->total = $total - $discount;
|
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\UserStatus;
|
use App\Enums\UserStatus;
|
||||||
use App\Models\Customer;
|
use App\Models\Customer;
|
||||||
|
use App\Models\Voucher;
|
||||||
use Livewire\Attributes\Computed;
|
use Livewire\Attributes\Computed;
|
||||||
|
|
||||||
trait WithMember
|
trait WithMember
|
||||||
@ -27,4 +28,36 @@ public function members()
|
|||||||
])
|
])
|
||||||
->toArray();
|
->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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -290,6 +290,23 @@ class="text-xs text-gray-400">{{ currency($item->quantity, '') }}
|
|||||||
</flux:select>
|
</flux:select>
|
||||||
<flux:error name="form.member_id" />
|
<flux:error name="form.member_id" />
|
||||||
</flux:field>
|
</flux:field>
|
||||||
|
|
||||||
|
@if (!empty($vouchers))
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Voucher</flux:label>
|
||||||
|
<flux:description>Voucher diurutkan berdasarkan dengan diskon yang paling besar.
|
||||||
|
</flux:description>
|
||||||
|
<flux:select variant="listbox" placeholder="Pilih Voucher"
|
||||||
|
wire:model.live.debounce.500ms="form.voucher_id" searchable clearable>
|
||||||
|
@foreach ($vouchers as $key => $name)
|
||||||
|
<flux:select.option value="{{ $key }}" key="{{ $key }}">
|
||||||
|
{{ $name }}
|
||||||
|
</flux:select.option>
|
||||||
|
@endforeach
|
||||||
|
</flux:select>
|
||||||
|
<flux:error name="form.voucher_id" />
|
||||||
|
</flux:field>
|
||||||
|
@endif
|
||||||
</flux:card>
|
</flux:card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user