238 lines
7.9 KiB
PHP
238 lines
7.9 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);
|
|
$this->toast('Ups, kamu harus login dulu nih buat klaim vouchernya! 🔐✨ Silakan masuk ke akunmu ya~', 'Waduh Belum Masuk', 'danger');
|
|
|
|
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('Yahh, sayang sekali vouchernya udah gak tersedia atau kadaluarsa nih 🥺💔 Coba cek voucher lainnya yuk!', 'Voucher Tidak Tersedia', '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('Maaf ya, voucher ini spesial buat tier tertentu aja 🌟👑 Terus tingkatkan tiermu buat dapetin aksesnya!', 'Upgrade Tier Yuk', '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('Waduh, vouchernya gak ketemu nih 🕵️♂️❌ Mungkin udah dihapus atau ada kesalahan teknis.', 'Voucher Hilang', 'danger');
|
|
|
|
return;
|
|
}
|
|
|
|
if ($result === 'stock_empty') {
|
|
$this->toast('Yahh, kamu telat! Kuota vouchernya udah ludes 😭💨 Jangan sedih, coba buruan klaim voucher yang lain ya!', 'Kehabisan Stok', 'danger');
|
|
|
|
return;
|
|
}
|
|
|
|
if ($result === 'limit_reached') {
|
|
$this->toast('Wow, semangat banget! Tapi kamu udah maksimal klaim voucher ini nih 🛑✋ Bagi-bagi kesempatan ke yang lain ya~', 'Batas Tercapai', 'warning');
|
|
|
|
return;
|
|
}
|
|
|
|
if ($result === 'success') {
|
|
$this->toast(
|
|
'Hore! 🎉 Voucher berhasil diklaim buat kamu! 🎁🎈 Jangan lupa cek menu Voucher di dashboard ya, selamat belanja!',
|
|
'Klaim 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)
|
|
->through(function (VoucherModel $voucher) {
|
|
$voucher->formatted_min_purchase = formatCurrencyNumber($voucher->min_purchase, 'Rp');
|
|
$voucher->formatted_end_date = formatDateLocalized($voucher->end_date);
|
|
|
|
return $voucher;
|
|
});
|
|
|
|
if ($this->selectedVoucher) {
|
|
$this->selectedVoucher->formatted_min_purchase = formatCurrencyNumber($this->selectedVoucher->min_purchase, 'Rp');
|
|
$this->selectedVoucher->formatted_end_date = formatDateLocalized($this->selectedVoucher->end_date);
|
|
}
|
|
|
|
return view('livewire.home.vouchers', [
|
|
'pageTitle' => 'Voucher',
|
|
'pageDesc' => 'Nikmati berbagai voucher menarik biar belanja kamu makin hemat dan happy! 🛍️💸✨',
|
|
'vouchers' => $vouchers,
|
|
]);
|
|
}
|
|
}
|