parfum/app/Livewire/Home/Voucher.php

244 lines
7.4 KiB
PHP

<?php
namespace App\Livewire\Home;
use App\Models\Outlet;
use App\Models\Voucher as VoucherModel;
use App\Traits\Components\WithToast;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Livewire\WithoutUrlPagination;
use Livewire\WithPagination;
#[Layout('components.layouts.home', [
'title' => 'Voucher',
])]
class Voucher extends Component
{
use WithoutUrlPagination, WithPagination, WithToast;
public array $outlets = [];
public ?string $search = null;
public ?string $outlet_id = null;
public ?string $sort = null;
public ?VoucherModel $selectedVoucher = null;
public bool $showDetailModal = false;
public function mount(): void
{
$this->outlets = Outlet::query()->operational()->pluck('name', 'id')->toArray();
}
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedOutletId(): void
{
$this->resetPage();
}
public function updatedSort(): void
{
$this->resetPage();
}
public function showDetail(VoucherModel $voucher): void
{
$this->selectedVoucher = $voucher;
$this->selectedVoucher->load(['outlets', 'tiers']);
$this->showDetailModal = true;
}
public function closeDetail(): void
{
$this->showDetailModal = false;
$this->selectedVoucher = null;
}
public function claim(VoucherModel $voucher): void
{
$user = auth()->user();
if (! auth()->check()) {
$this->redirect(route('login'), navigate: true);
return;
}
// 1. Check if voucher exists and is active
if (
Carbon::parse($voucher->start_date)->gt(now()) ||
(
$voucher->end_date &&
Carbon::parse($voucher->end_date)->lt(now())
)
) {
$this->toast('Voucher tidak tersedia atau sudah kadaluarsa.', 'Gagal', 'danger');
return;
}
// 2. Check Tier Requirement (before transaction)
$userTierId = $user->membership?->tier_id;
$voucher->load('tiers'); // Ensure tiers are loaded
$voucherTierIds = $voucher->tiers->pluck('id');
if (
$voucherTierIds->isNotEmpty() &&
(! $userTierId || ! $voucherTierIds->contains($userTierId))
) {
$this->toast('Voucher ini hanya tersedia untuk tier tertentu.', 'Gagal', 'danger');
return;
}
// 3. Execute claim within transaction for data consistency
$result = DB::transaction(function () use ($user, $voucher) {
// Lock voucher row to prevent race condition
$v = VoucherModel::whereKey($voucher->id)
->lockForUpdate()
->first();
if (! $v) {
return 'not_found';
}
// Check if available_count (stock) is sufficient
// available_count harus > 0 untuk bisa di-claim
if ($v->quota !== null && $v->available_count <= 0) {
return 'stock_empty';
}
// Check how many times this user has claimed this voucher
// Using SUM quantity to calculate total voucher that has been claimed
$userClaimedQuantity = DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->sum('quantity');
// Check if user has reached the limit
if ($v->limit_per_user !== null && $userClaimedQuantity >= $v->limit_per_user) {
return 'limit_reached';
}
// Check if user already has a record for this voucher
$existingPivot = DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->first();
if ($existingPivot) {
// User already has this voucher, increment the quantity
DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->update([
'quantity' => $existingPivot->quantity + 1,
'updated_at' => now(),
]);
} else {
// User doesn't have this voucher yet, create new record
$user->vouchers()->attach($v->id, [
'quantity' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
}
// Decrease available stock
$v->decrement('available_count');
return 'success';
});
// Handle transaction results
if ($result === 'not_found') {
$this->toast('Voucher tidak ditemukan.', 'Gagal', 'danger');
return;
}
if ($result === 'stock_empty') {
$this->toast('Klaim gagal! Kuota voucher sudah habis.', 'Gagal', 'danger');
return;
}
if ($result === 'limit_reached') {
$this->toast('Anda sudah mencapai batas maksimal klaim voucher ini.', 'Peringatan', 'warning');
return;
}
if ($result === 'success') {
$this->toast(
'Voucher berhasil diklaim! Silakan cek di menu Voucher di halaman dasbor.',
'Berhasil',
'success'
);
}
}
public function render(): View
{
$vouchers = VoucherModel::query()
->with(['tiers', 'outlets'])
->active()
->show()
->where(function ($query) {
$query->where('name', 'like', '%'.$this->search.'%')
->orWhere('code', 'like', '%'.$this->search.'%');
})
->when($this->outlet_id, function ($query) {
$query->whereHas('outlets', function ($q) {
$q->where('outlets.id', $this->outlet_id);
});
})
->when($this->sort, function ($query) {
match ($this->sort) {
'latest' => $query->orderBy('created_at', 'desc'),
'biggest' => $query->orderBy('discount_amount', 'desc'),
'ending-soon' => $query->orderByRaw('end_date IS NULL')->orderBy('end_date', 'asc'),
default => $query->latest(),
};
}, fn ($query) => $query->latest())
->paginate(6);
$user = auth()->user();
$userTierId = $user?->membership?->tier_id;
$voucherIds = $vouchers->pluck('id');
if ($this->selectedVoucher) {
$voucherIds->push($this->selectedVoucher->id);
}
$userClaimedCounts = $user
? DB::table('user_voucher')
->where('user_id', $user->id)
->whereIn('voucher_id', $voucherIds->unique())
->select('voucher_id', DB::raw('SUM(quantity) as total_quantity'))
->groupBy('voucher_id')
->pluck('total_quantity', 'voucher_id')
->toArray()
: [];
return view('livewire.home.vouchers', [
'pageTitle' => 'Voucher',
'pageDesc' => 'Nikmati berbagai voucher menarik untuk belanja lebih hemat dan menyenangkan.',
'vouchers' => $vouchers,
]);
}
}