parfum/app/Livewire/Home/Voucher.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,
]);
}
}