'integer', 'invoice_id' => 'integer', 'amount' => 'decimal:2', // adjust if you store more precision 'due_date' => 'date', // if due_date is DATE/DATETIME 'created_by' => 'integer', ]; /* ========================= * Relationships (optional) * ========================= */ public function parentUser() { return $this->belongsTo(User::class, 'parent_id'); } public function invoice() { return $this->belongsTo(Invoice::class, 'invoice_id'); } /* ========================= * Query scopes (optional) * ========================= */ public function scopeForTerm(Builder $q, string $schoolYear, ?string $semester = null): Builder { $q->where('school_year', $schoolYear); if ($semester !== null && $semester !== '') { $q->where('semester', $semester); } return $q; } public function scopeStatus(Builder $q, ?string $status): Builder { return ($status !== null && $status !== '') ? $q->where('status', $status) : $q; } public function scopeByParentTerm( Builder $q, int $parentId, string $year, string $sem, ?string $status = null ): Builder { $q->where('parent_id', $parentId) ->where('school_year', $year) ->where('semester', $sem); if ($status !== null && $status !== '') { $q->where('status', $status); } return $q->orderByDesc('id'); } /* ========================= * CI methods equivalents * ========================= */ public static function byParentTerm(int $parentId, string $year, string $sem, ?string $status = null) { return static::query() ->where('parent_id', $parentId) ->where('school_year', $year) ->where('semester', $sem) ->when($status !== null, fn ($q) => $q->where('status', $status)) ->orderByDesc('id') ->get(); } /** * Set status=applied and invoice_id for given IDs. * Returns true if nothing to update (empty ids) or update succeeded. */ public static function markApplied($ids, int $invoiceId): bool { $ids = is_array($ids) ? $ids : [$ids]; $ids = array_values(array_filter(array_map('intval', $ids))); if (count($ids) === 0) { return true; } $affected = static::query() ->whereIn('id', $ids) ->update([ 'status' => 'applied', 'invoice_id' => $invoiceId, ]); return $affected >= 0; // update returns affected rows; 0 is still "ok" } /** * listAllForTerm (Laravel version) * - joins users + invoices * - supports status + q search * - paginates * - fallback: if no rows for semester and allowed, retry year-only */ public static function listAllForTerm( string $schoolYear, string $semester, ?string $status = null, ?string $q = null, int $perPage = 50, bool $fallbackToYear = true ) { $build = function (?string $semFilter) use ($schoolYear, $status, $q) { $query = static::query() ->select([ 'additional_charges.*', DB::raw("CONCAT(COALESCE(users.lastname, ''), ', ', COALESCE(users.firstname, '')) AS parent_name"), 'invoices.invoice_number', ]) ->leftJoin('users', 'users.id', '=', 'additional_charges.parent_id') ->leftJoin('invoices', 'invoices.id', '=', 'additional_charges.invoice_id') ->where('additional_charges.school_year', $schoolYear); if ($semFilter !== null && $semFilter !== '') { $query->where('additional_charges.semester', $semFilter); } if (!empty($status)) { $query->where('additional_charges.status', $status); } if (!empty($q)) { $term = trim($q); $query->where(function ($w) use ($term) { $w->where('additional_charges.title', 'like', "%{$term}%") ->orWhere('additional_charges.description', 'like', "%{$term}%") ->orWhere('users.firstname', 'like', "%{$term}%") ->orWhere('users.lastname', 'like', "%{$term}%") ->orWhere('invoices.invoice_number', 'like', "%{$term}%"); }); } return $query->orderByDesc('additional_charges.id'); }; // First attempt: year + semester $result = $build($semester)->paginate($perPage); // Fallback: if no rows and allowed, retry year-only if ($fallbackToYear && $result->total() === 0 && $semester !== '') { $result = $build(null)->paginate($perPage); } return $result; } }