Files
2026-05-16 13:44:12 -04:00

136 lines
4.8 KiB
PHP
Executable File

<?php
namespace App\Models;
use CodeIgniter\Model;
class DiscountVoucherModel extends Model
{
protected $table = 'discount_vouchers';
protected $primaryKey = 'id';
protected $allowedFields = [
'code',
'discount_type',
'discount_value',
'max_uses',
'times_used',
'valid_from',
'valid_until',
'semester',
'school_year',
'is_active',
'description', // <-- NEW
'created_at',
'updated_at',
];
// Timestamps managed by CI
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
// Basic validation
protected $validationRules = [
'code' => 'required|alpha_numeric_punct|max_length[50]|is_unique[discount_vouchers.code,id,{id}]',
'discount_type' => 'required|in_list[percent,fixed]',
'discount_value' => 'required|numeric|greater_than_equal_to[0]',
'max_uses' => 'permit_empty|is_natural',
'valid_from' => 'permit_empty|valid_date[Y-m-d]',
'valid_until' => 'permit_empty|valid_date[Y-m-d]',
'is_active' => 'in_list[0,1]',
'description' => 'permit_empty|string|max_length[1000]', // <-- NEW
];
protected $validationMessages = [
'code' => [
'is_unique' => 'The voucher code must be unique.',
],
'discount_type' => [
'in_list' => 'The discount type must be either "percent" or "fixed".',
],
];
// Normalize inputs before save
protected $beforeInsert = ['normalizeFields', 'validateDatesOrder'];
protected $beforeUpdate = ['normalizeFields', 'validateDatesOrder'];
protected function normalizeFields(array $data): array
{
if (!isset($data['data'])) return $data;
// Normalize code: keep A-Z, 0-9 and dashes, uppercased
if (array_key_exists('code', $data['data'])) {
$raw = (string) $data['data']['code'];
$normalized = strtoupper(preg_replace('/[^A-Z0-9\-]/i', '', trim($raw)));
$data['data']['code'] = $normalized;
}
// Empty strings -> NULL for dates & max_uses
foreach (['valid_from', 'valid_until'] as $f) {
if (array_key_exists($f, $data['data'])) {
$v = trim((string) $data['data'][$f]);
$data['data'][$f] = ($v === '') ? null : $v;
}
}
if (array_key_exists('max_uses', $data['data'])) {
$v = $data['data']['max_uses'];
$data['data']['max_uses'] = ($v === '' || $v === null) ? null : max(0, (int) $v);
}
// Coerce booleans
if (array_key_exists('is_active', $data['data'])) {
$data['data']['is_active'] = (int) !!$data['data']['is_active'];
}
return $data;
}
protected function validateDatesOrder(array $data): array
{
if (!isset($data['data'])) return $data;
$from = $data['data']['valid_from'] ?? null;
$until = $data['data']['valid_until'] ?? null;
if ($from && $until && $from > $until) {
// Inject a model error; save() will fail and return errors()
$this->validator->setError('valid_until', 'Valid Until must be the same as or after Valid From.');
}
return $data;
}
/**
* Find a valid voucher for a student (not expired, active, under max uses, and not used by the student).
*/
public function findValidVoucher(string $code, int $studentId)
{
$code = strtoupper(preg_replace('/\s+/', '', trim($code))); // simple normalize to match stored code
$builder = $this->db->table('discount_vouchers v')
->select('v.*')
// Left join usages for this student only
->join('discount_usages u', 'u.voucher_id = v.id AND u.student_id = ' . (int) $studentId, 'left')
->where('v.code', $code)
->where('v.is_active', 1)
// valid_from is null or <= today
->groupStart()
->where('v.valid_from IS NULL', null, false)
->orWhere('v.valid_from <=', date('Y-m-d'))
->groupEnd()
// valid_until is null or >= today
->groupStart()
->where('v.valid_until IS NULL', null, false)
->orWhere('v.valid_until >=', date('Y-m-d'))
->groupEnd()
// max_uses is null or times_used < max_uses (use raw to compare two columns)
->groupStart()
->where('v.max_uses IS NULL', null, false)
->orWhere('v.times_used < v.max_uses', null, false)
->groupEnd()
// Not previously used by this student (no usage row)
->where('u.id IS NULL', null, false);
return $builder->get()->getRow();
}
}