fix logic and tests, update docker CI file

This commit is contained in:
root
2026-03-09 15:58:44 -04:00
parent 1cb3573d4b
commit 79e44fe037
188 changed files with 1776 additions and 524 deletions
+13 -12
View File
@@ -68,7 +68,7 @@ class AttendanceDay extends BaseModel
$q = static::query()
->select('id')
->where('class_section_id', $classSectionId)
->where('date', $date)
->whereDate('date', $date)
->where(function ($w) {
$w->where('status', 'published')
->orWhere('status', 'finalized'); // legacy
@@ -91,7 +91,7 @@ class AttendanceDay extends BaseModel
): ?self {
return static::query()
->where('class_section_id', $classSectionId)
->where('date', $date)
->whereDate('date', $date)
->where('semester', $semester)
->where('school_year', $schoolYear)
->first();
@@ -112,11 +112,12 @@ class AttendanceDay extends BaseModel
if ($row) return $row;
$now = now();
$normalizedDate = Carbon::parse($date)->toDateString();
try {
return static::create([
'class_section_id' => $classSectionId,
'date' => $date,
'date' => $normalizedDate,
'status' => 'draft',
'submitted_by' => null,
'submitted_at' => null,
@@ -142,15 +143,15 @@ class AttendanceDay extends BaseModel
/**
* DEPRECATED legacy finalize => submit (teacher submit).
*/
public static function finalize(int $id, int $userId): bool
public function finalize(int $userId): bool
{
return static::submit($id, $userId, null);
return $this->submit($userId, null);
}
/**
* Teacher submit: status => submitted (teacher locked, admin editable).
*/
public static function submit(int $id, int $userId, ?string $autoPublishAt = null): bool
public function submit(int $userId, ?string $autoPublishAt = null): bool
{
$now = now();
@@ -165,17 +166,17 @@ class AttendanceDay extends BaseModel
$payload['auto_publish_at'] = $autoPublishAt;
}
return static::query()->whereKey($id)->update($payload) >= 0;
return static::query()->whereKey($this->id)->update($payload) >= 0;
}
/**
* Admin publish (hard lock): status => published.
*/
public static function publish(int $id, int $userId): bool
public function publish(int $userId): bool
{
$now = now();
return static::query()->whereKey($id)->update([
return static::query()->whereKey($this->id)->update([
'status' => 'published',
'published_by' => $userId,
'published_at' => $now,
@@ -186,7 +187,7 @@ class AttendanceDay extends BaseModel
/**
* Admin reopen a day back to 'draft' (default) or 'submitted'.
*/
public static function reopen(int $id, int $userId, string $toStatus = 'draft', ?string $reason = null): bool
public function reopen(int $userId, string $toStatus = 'draft', ?string $reason = null): bool
{
if (!in_array($toStatus, ['draft', 'submitted'], true)) {
throw new \InvalidArgumentException('toStatus must be "draft" or "submitted".');
@@ -194,7 +195,7 @@ class AttendanceDay extends BaseModel
$now = now();
return static::query()->whereKey($id)->update([
return static::query()->whereKey($this->id)->update([
'status' => $toStatus,
'reopened_by' => $userId,
'reopened_at' => $now,
@@ -202,4 +203,4 @@ class AttendanceDay extends BaseModel
'updated_at' => $now,
]) >= 0;
}
}
}
+6 -4
View File
@@ -30,7 +30,7 @@ class AttendanceEmailTemplate extends BaseModel
* Fetch a template by code and variant, falling back to 'default' if needed.
* Active only.
*/
public static function getTemplate(string $code, string $variant = 'default'): ?self
public static function getTemplate(string $code, string $variant = 'default'): ?array
{
$row = static::query()
->where('code', $code)
@@ -39,13 +39,15 @@ class AttendanceEmailTemplate extends BaseModel
->first();
if ($row) {
return $row;
return $row->toArray();
}
return static::query()
$fallback = static::query()
->where('code', $code)
->where('variant', 'default')
->where('is_active', 1)
->first();
return $fallback?->toArray();
}
}
}
+13 -1
View File
@@ -3,10 +3,12 @@
namespace App\Models;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB;
class ClassSection extends BaseModel
{
use HasFactory;
// CI table name is "classSection"
protected $table = 'classSection';
@@ -28,6 +30,16 @@ class ClassSection extends BaseModel
'class_section_id' => 'integer',
];
public function getSectionNameAttribute(): ?string
{
return $this->attributes['class_section_name'] ?? null;
}
public function setSectionNameAttribute($value): void
{
$this->attributes['class_section_name'] = $value;
}
/* =========================
* Relationships (optional)
* ========================= */
@@ -136,4 +148,4 @@ class ClassSection extends BaseModel
->get()
->toArray();
}
}
}
+6 -1
View File
@@ -28,6 +28,11 @@ class CommunicationLog extends BaseModel
'metadata',
];
public function getFillable(): array
{
return $this->fillable;
}
protected $casts = [
'student_id' => 'integer',
'family_id' => 'integer',
@@ -51,4 +56,4 @@ class CommunicationLog extends BaseModel
{
return $this->belongsTo(User::class, 'sent_by');
}
}
}
+17 -1
View File
@@ -23,8 +23,24 @@ class EmailTemplate extends BaseModel
protected $casts = [
'is_active' => 'boolean',
'updated_by' => 'integer',
];
public static function getTemplate(string $code, string $variant = 'default'): ?self
{
$row = static::query()
->where('code', $code)
->where('is_active', 1)
->where(function ($q) use ($variant) {
$q->where('variant', $variant)
->orWhere('variant', 'default');
})
->orderByRaw("CASE WHEN variant = ? THEN 0 ELSE 1 END", [$variant])
->first();
return $row;
}
/**
* Equivalent of CI getActiveTemplates():
* is_active=1, orderBy template_key ASC
@@ -48,4 +64,4 @@ class EmailTemplate extends BaseModel
->where('is_active', 1)
->first();
}
}
}
+6 -1
View File
@@ -40,6 +40,11 @@ class PurchaseOrder extends BaseModel
'deleted_at' => 'datetime',
];
public function getFillable(): array
{
return $this->fillable;
}
/* ============================================================
* Status (enum-like)
* ============================================================
@@ -157,4 +162,4 @@ class PurchaseOrder extends BaseModel
'notes' => ['nullable', 'string', 'max:5000'],
];
}
}
}
+6 -1
View File
@@ -30,6 +30,11 @@ class PurchaseOrderItem extends BaseModel
'updated_at' => 'datetime',
];
public function getFillable(): array
{
return $this->fillable;
}
/* ============================================================
* Relationships (optional but recommended)
* ============================================================
@@ -84,4 +89,4 @@ class PurchaseOrderItem extends BaseModel
'unit_cost' => [$updating ? 'sometimes' : 'required', 'numeric', 'min:0'],
];
}
}
}
+6 -1
View File
@@ -12,7 +12,7 @@ class Stats extends BaseModel
* If your stats table has created_at/updated_at, keep true (default).
* If it doesn't, set to false.
*/
public $timestamps = false;
public $timestamps = true;
protected $fillable = [
'students',
@@ -28,6 +28,11 @@ class Stats extends BaseModel
'users' => 'integer',
];
public function getFillable(): array
{
return $this->fillable;
}
/**
* CI-compatible: getStats()
* Returns all rows.
+4 -2
View File
@@ -4,12 +4,14 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;
class Student extends BaseModel
{
use HasFactory;
protected $table = 'students';
/**
@@ -38,7 +40,7 @@ class Student extends BaseModel
];
protected $casts = [
'school_id' => 'integer',
'school_id' => 'string',
'age' => 'integer',
'photo_consent' => 'boolean',
'is_new' => 'boolean',
@@ -392,4 +394,4 @@ class Student extends BaseModel
'is_active' => ['nullable', 'boolean'],
];
}
}
}
+4 -4
View File
@@ -279,7 +279,7 @@ class TeacherClass extends BaseModel
->join('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
->where('tc.teacher_id', $teacherId)
->where('tc.school_year', $schoolYear)
->orderByRaw("FIELD(tc.position,'main','ta')")
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->get()
->map(fn ($r) => (array) $r)
->all();
@@ -296,7 +296,7 @@ class TeacherClass extends BaseModel
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->where('tc.class_section_id', $sectionCode)
->where('tc.school_year', $schoolYear)
->orderByRaw("FIELD(tc.position,'main','ta')")
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->orderBy('u.firstname', 'asc')
->get()
->map(fn ($r) => (array) $r)
@@ -324,7 +324,7 @@ class TeacherClass extends BaseModel
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->where('tc.school_year', $schoolYear)
->orderBy('tc.class_section_id', 'asc')
->orderByRaw("FIELD(tc.position,'main','ta')")
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->orderBy('u.firstname', 'asc');
if (!empty($onlySectionCodes)) {
@@ -360,4 +360,4 @@ class TeacherClass extends BaseModel
'updated_by' => ['nullable', 'integer'],
];
}
}
}
+2
View File
@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -14,6 +15,7 @@ use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
use HasFactory;
protected $table = 'users';