151 lines
4.8 KiB
PHP
Executable File
151 lines
4.8 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Models\BaseModel;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class DiscountVoucher extends BaseModel
|
|
{
|
|
protected $table = 'discount_vouchers';
|
|
|
|
// ✅ CI manages created_at / updated_at
|
|
public $timestamps = true;
|
|
|
|
protected $fillable = [
|
|
'code',
|
|
'discount_type',
|
|
'description',
|
|
'discount_value',
|
|
'max_uses',
|
|
'times_used',
|
|
'valid_from',
|
|
'valid_until',
|
|
'is_active',
|
|
'created_at',
|
|
'updated_at',
|
|
'school_year',
|
|
'semester',
|
|
];
|
|
|
|
protected $casts = [
|
|
'discount_value' => 'decimal:2',
|
|
'max_uses' => 'integer',
|
|
'times_used' => 'integer',
|
|
'is_active' => 'boolean',
|
|
'valid_from' => 'date',
|
|
'valid_until' => 'date',
|
|
];
|
|
|
|
/* =========================
|
|
* Model hooks (CI beforeInsert/beforeUpdate equivalents)
|
|
* ========================= */
|
|
|
|
protected static function booted(): void
|
|
{
|
|
static::creating(function (self $m) {
|
|
$m->normalizeFields();
|
|
$m->validateDatesOrder(); // throws if invalid
|
|
});
|
|
|
|
static::updating(function (self $m) {
|
|
$m->normalizeFields();
|
|
$m->validateDatesOrder(); // throws if invalid
|
|
});
|
|
}
|
|
|
|
private function normalizeFields(): void
|
|
{
|
|
// Normalize code: keep A-Z, 0-9 and dashes, uppercased
|
|
if ($this->isDirty('code') && $this->code !== null) {
|
|
$raw = (string) $this->code;
|
|
$this->code = strtoupper(preg_replace('/[^A-Z0-9\-]/i', '', trim($raw)));
|
|
}
|
|
|
|
// Empty strings -> NULL for dates & max_uses
|
|
foreach (['valid_from', 'valid_until'] as $f) {
|
|
if ($this->isDirty($f)) {
|
|
$v = $this->{$f};
|
|
if (is_string($v)) {
|
|
$v = trim($v);
|
|
$this->{$f} = ($v === '') ? null : $v;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->isDirty('max_uses')) {
|
|
$v = $this->max_uses;
|
|
if ($v === '' || $v === null) {
|
|
$this->max_uses = null;
|
|
} else {
|
|
$this->max_uses = max(0, (int) $v);
|
|
}
|
|
}
|
|
|
|
if ($this->isDirty('is_active')) {
|
|
$this->is_active = (bool) $this->is_active;
|
|
}
|
|
}
|
|
|
|
private function validateDatesOrder(): void
|
|
{
|
|
if (!empty($this->valid_from) && !empty($this->valid_until)) {
|
|
$from = Carbon::parse($this->valid_from)->toDateString();
|
|
$until = Carbon::parse($this->valid_until)->toDateString();
|
|
if ($from > $until) {
|
|
// In Laravel, prefer validating in FormRequest.
|
|
// This keeps your CI behavior by failing the save.
|
|
throw new \InvalidArgumentException('Valid Until must be the same as or after Valid From.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/* =========================
|
|
* Query helpers
|
|
* ========================= */
|
|
|
|
/**
|
|
* Find a valid voucher for a student (not expired, active, under max uses, and not used by the student).
|
|
*
|
|
* NOTE: Your CI joins discount_usages on u.student_id. That requires discount_usages.student_id to exist.
|
|
* If your discount_usages table does NOT have student_id (some of your other code uses parent_id),
|
|
* switch the filter to parent_id instead.
|
|
*/
|
|
public static function findValidVoucher(string $code, int $studentId): ?self
|
|
{
|
|
$code = strtoupper(preg_replace('/\s+/', '', trim($code)));
|
|
$today = now()->toDateString();
|
|
|
|
$q = static::query()
|
|
->from('discount_vouchers as v')
|
|
->select('v.*')
|
|
// Left join usages for this student only
|
|
->leftJoin('discount_usages as u', function ($join) use ($studentId) {
|
|
$join->on('u.voucher_id', '=', 'v.id')
|
|
->where('u.student_id', '=', (int) $studentId);
|
|
})
|
|
->where('v.code', $code)
|
|
->where('v.is_active', 1)
|
|
// valid_from is null or <= today
|
|
->where(function ($w) use ($today) {
|
|
$w->whereNull('v.valid_from')
|
|
->orWhere('v.valid_from', '<=', $today);
|
|
})
|
|
// valid_until is null or >= today
|
|
->where(function ($w) use ($today) {
|
|
$w->whereNull('v.valid_until')
|
|
->orWhere('v.valid_until', '>=', $today);
|
|
})
|
|
// max_uses is null or times_used < max_uses
|
|
->where(function ($w) {
|
|
$w->whereNull('v.max_uses')
|
|
->orWhereColumn('v.times_used', '<', 'v.max_uses');
|
|
})
|
|
// Not previously used by this student (no usage row)
|
|
->whereNull('u.id')
|
|
->limit(1);
|
|
|
|
return $q->first();
|
|
}
|
|
} |