# API Controllers

This document lists every API controller alphabetically with any inline route comments we can gather.

## AdministratorController
**File:** `app/Http/Controllers/Api/AdministratorController.php`  
**Purpose:** Surface administrator-portal analytics, student and parent search tools, and the enrollment workflow.

**Endpoints**
- `GET /api/v1/administrator/dashboard` (`dashboard`)
  - Returns counts for students, staff roles, and parents plus the four most recent login activities. Always reflects the configured school year/semester.
- `GET /api/v1/administrator/absence` (`absenceInfo`)
  - Requires auth. Responds with semester/year metadata, existing absence submissions for the current admin, and the list of valid Sunday dates (computed via `allowedAbsenceDates()`).
- `POST /api/v1/administrator/absence` (`submitAbsence`)
  - Body: `{"dates":["YYYY-MM-DD",...], "reason_type":"optional", "reason":"required"}`.
  - Validates each date against the allowed list, creates/upserts staff attendance rows, and emails the principal. Response lists `saved`, `invalid`, and the saved date values; validation errors return `401/422`.
- `GET /api/v1/administrator/search` (`search`)
  - Query param `q` is required. Tokenizes the search string and looks across users, students, parents, staff, and emergency contacts (including phone-number heuristics). Returns bucketed arrays and a total hit count.
- `GET /api/v1/administrator/enrollment-withdrawal` (`enrollmentWithdrawal`)
  - Paginated list of enrollment rows filtered by `school_year` (defaults to configured year).
- `GET /api/v1/administrator/enrollment-withdrawal/data` (`enrollmentWithdrawalData`)
  - Supplies UI metadata: available school years, each student with parent context, and class-section options for the chosen year.
- `POST /api/v1/administrator/enrollment-withdrawal/update` (`updateEnrollmentStatuses`)
  - Body: `{"enrollment_status":{"student_id":"status", ...}}` where status must be one of `admission under review`, `payment pending`, `enrolled`, `withdraw under review`, `refund pending`, `withdrawn`, `denied`, or `waitlist`.
  - Wraps updates in a transaction, adjusts admission flags, and when a status moves into `refund pending` runs `FeeCalculationService` + `RefundModel` bookkeeping. Responds with summary + any per-student errors.
- `GET /api/v1/administrator/students` (`studentProfiles`)
  - Returns every student ordered alphabetically, enriched with parent contact info, emergency contacts, allergy/medical summaries, and current class section name.
- `GET /api/v1/administrator/parents` (`parentProfiles`)
  - Iterates all users, filters by `parent` role, and augments each record with their latest invoice `paid_amount`/`balance`.
- `GET /api/v1/administrator/enrollment/new-students` (`newStudents`)
  - Lists all students marked as new for the active school year, including parent/emergency contacts, enrollment status, class assignment, and formatted registration dates.

## AssignmentController
**File:** `app/Http/Controllers/Api/AssignmentController.php`  
**Purpose:** CRUD interface around the `assignments` table plus APIs for the class-assignment management screen.

- `GET /api/v1/assignments` (`index`) – Optional `class_id`, `teacher_id`, `student_id`, `page`, and `per_page` filters; returns paginated assignments.
- `GET /api/v1/assignments/{id}` (`show`) – Returns a single assignment or `404`.
- `POST /api/v1/assignments` (`store`) – Requires `title`, `due_date (Y-m-d)`, `class_id`, and `teacher_id`; creates an assignment row.
- `PATCH /api/v1/assignments/{id}` (`update`) – Partial update for the same fields as `store`.
- `DELETE /api/v1/assignments/{id}` (`delete`) – Removes the assignment.
- `GET /api/v1/assignments/student/{id}` (`student`) – Lists assignments attached to a specific student.
- `GET /api/v1/assignments/class-sections` (`classAssignments`) – Mirrors the legacy admin Class Assignment screen; groups teacher + student rosters by class section, with optional `school_year` filter, and returns the list of available school years.
- `GET /api/v1/assignments/class-sections/data` (`classAssignmentData`) – Lightweight JSON payload of all class sections/teachers/students used by the SPA.
- `POST /api/v1/assignments/class-sections` (`saveClassAssignment`) – Creates a `student_class` record for a student/section pairing, capturing term metadata and editor.

## AttendanceController
**File:** `app/Http/Controllers/Api/AttendanceController.php`  
**Purpose:** Manage per-student attendance plus provide roster tools for class-wide check-in.

- `index()` – Query params: `page`, `per_page`, `student_id`, `date`. Responds with paginated attendance rows and pager metadata.
- `show($id)` – Returns a single attendance record or `404`.
- `store()` – Body requires `student_id`, `date (Y-m-d)`, `status (present|absent|late)`, optional `notes`. Prevents duplicates per (student,date) and returns `201` with inserted payload.
- `update($id)` – Allows patching `status` and/or `notes`, re-validates, and echoes the updated entity.
- `delete($id)` – Removes the record if it exists.
- `student($id)` – Returns all attendance rows for the given student sorted newest-first.
- `classRoster($class_section_id)` – `GET /api/v1/attendance/class/{class_section_id}` accepts optional `date` query param (default today) and returns each student in the section with their attendance status/reason for that date.
- `recordClass($class_section_id)` – `POST /api/v1/attendance/class/{class_section_id}/record` body: `{"date":"YYYY-MM-DD","records":[{"student_id":1,"status":"present|absent|late","reason":"optional"},...]}`. Validates rows, ensures roster membership, and records each entry atomically, returning per-student success/error arrays.
- `GET /api/v1/attendance/teacher/grid` (`teacherSectionGrid`) – Returns the section dropdown, current staff grid, and lock state for the teacher attendance landing page; accepts `class_section_id`, `date`, `semester`, and `school_year`.
- `GET /api/v1/attendance/teacher/update-data` (`teacherUpdateData`) – Supplies the detailed payload used by the teacher update form: teacher metadata, the most recent dates (today plus the last three sessions), student attendance history, staff statuses, and any no-school days for the selected section/term.

## AttendanceTrackingController
**File:** `app/Http/Controllers/Api/AttendanceTrackingController.php`  
**Purpose:** Surface pending attendance violations, automate/track parent notifications, and expose helpers for the manual email workflow.

- `GET /api/v1/attendance-tracking/pending` (`pendingViolations`) – Filters unreported/unnotified absences & lates in the last five active weeks and returns the computed violation list, including parent contact info and recommended actions.
- `GET /api/v1/attendance-tracking/notified` (`notifiedViolations`) – Lists previously notified violations enriched with student, class, and parent metadata.
- `POST /api/v1/attendance-tracking/notify` (`recordNotification`) – Manually mark a violation as notified (and dispatch the corresponding Laravel event). Body must include `student_id` and `date`; optional term overrides and parent contact info may be supplied.
- `POST /api/v1/attendance-tracking/send-auto-emails` (`sendAutoEmails`) – Scans for auto-email violations and sends templated emails (with CC to secondary parents when available); responds with `{sent, skipped, errors, details}`.
- `GET /api/v1/attendance-tracking/compose` (`composeEmail`) – Returns the rendered subject/body preview plus parent addresses for a given `student_id`, `code`, and optional `variant`/`incident_date`.
- `POST /api/v1/attendance-tracking/send-email` (`sendEmailManual`) – Sends a manual attendance email using the body/subject from the compose UI, logs the notification, and flips the tracking/attendance rows to notified.
- `GET /api/v1/attendance-tracking/parents-info` (`parentsInfo`) – Convenience endpoint that returns the primary and secondary parent contacts for a student.
- `POST /api/v1/attendance-tracking/notes` (`saveNotificationNote`) – Persists the admin’s follow-up note against the relevant tracking row (or creates one if missing) after a notification is sent.

## AuthController
**File:** `app/Http/Controllers/Api/Auth/AuthController.php`  
**Purpose:** JWT-backed authentication endpoints.

- `POST /api/v1/auth/login` (`login`) – Validates `email` and `password`, looks up the user case-insensitively, uses `ci_password_verify`, and returns `{access_token, token_type, expires_in}` or `401`.
- `GET /api/v1/auth/me` (`me`) – Requires a valid bearer token, returns the decoded `User` JSON via `JWTAuth::parseToken()->authenticate()`.
- `POST /api/v1/auth/logout` (`logout`) – Invalidates the caller’s JWT token (best-effort) and returns `{message: "Logged out"}` even if the token was already invalid.

## AuthorizedUsersController
**File:** `app/Http/Controllers/Api/AuthorizedUsersController.php`  
**Purpose:** Allow primary users to manage their authorized delegates and guide those delegates through the confirmation/password flow.

- `GET /api/v1/authorized-users` (`index`) – Auth required; returns every authorized user tied to the signed-in primary account.
- `GET /api/v1/authorized-users/{id}` (`show`) – Auth required; fetches a single authorized user (scoped to the current owner).
- `POST /api/v1/authorized-users` (`store`) – Auth required; accepts `email`, verifies that user exists, creates a pending authorized-user row, and sends a confirmation email with a tokenized link.
- `PATCH /api/v1/authorized-users/{id}` (`update`) – Auth required; currently allows updating the email address for the authorized user.
- `DELETE /api/v1/authorized-users/{id}` (`destroy`) – Auth required; removes the authorized user entry for the current owner.
- `POST /api/v1/authorized-users/confirm` (`confirm`) – Public endpoint hit from email links; validates the token, activates the record, and returns the delegate’s `authorized_user_id` for the password setup UI.
- `GET /api/v1/authorized-users/{id}/set-password` (`setPassword`) – Public; returns the delegate’s basic info so the frontend can render the password form.
- `POST /api/v1/authorized-users/{id}/password` (`savePassword`) – Public; accepts `password`/`password_confirm`, hashes it with PBKDF2, and updates the delegate’s user record.

## BaseApiController
**File:** `app/Http/Controllers/Api/BaseApiController.php`  
**Purpose:** Shared foundation for every API controller. Wraps the Laravel request in a `CiRequestAdapter`, standardizes JSON responses, bridges CodeIgniter-style validation rules, and exposes helper utilities.

- **Response helpers:** `success`, `error`, and the `respond*` variants keep payloads consistent (`{status, message, data}`) and map to the right HTTP codes (`200`, `201`, `204`, `400`, `422`, etc.).
- **Validation bridge:** `validateRequest`, `validate`, `payloadData`, and `normalizeRules` accept CodeIgniter rule strings (e.g., `max_length[255]`, `permit_empty`) and translate them to Laravel validator syntax before running `Validator::make`.
- **Pagination:** `paginate($source, $page, $perPage)` works with Eloquent builders, query builders, or arrays, returning `{data, pagination:{current_page, per_page, total, total_pages}}`.
- **Session context:** `getCurrentUserId()` reads the `user_id` from the session, and `getCurrentUser()` fetches the hydrated `UserModel` record, falling back to querying `user_roles` if roles are missing from the session cache.
- **Constructor:** captures the Laravel `Request` and exposes it as `$this->request` via `CiRequestAdapter` so consuming controllers can continue to use familiar CI-style helpers (`getGet`, `getJSON`, etc.).

## BroadcastEmailController
**File:** `app/Http/Controllers/Api/BroadcastEmailController.php`  
**Purpose:** Simple bulk-email helper for administrators plus a convenience list of eligible parent recipients.

- `GET /api/v1/broadcast-email` (`metadata`) – Provides the parent roster (using `UserModel::getParents`) and the sender dropdown derived from `MAIL_SENDERS` so the UI can render the compose screen.
- `POST /api/v1/broadcast-email/send` (`send`) – Supports both test mode (`send_test_only`, `test_email`) and broadcast mode. Accepts layout options (`wrap_layout`, `preheader`, CTA text/URL) plus personalization toggle. Sends via `EmailService`, reports `{attempted, sent, failed}`.
- `POST /api/v1/broadcast-email/upload-image` (`uploadImage`) – Validates and stores rich-text editor image uploads (JPEG/PNG/GIF/WebP ≤ 5 MB) under `public/uploads/email` and returns the public URL.

## CalendarController
**File:** `app/Http/Controllers/Api/CalendarController.php`  
**Purpose:** CRUD for the school calendar, including month/year filters and validation for event creation.

- `GET /api/v1/calendar` (`index`) – Supports `page`, `per_page`, `month`, and `year` query params. Filters by `start_date`, orders ascending, and returns paginated events.
- `GET /api/v1/calendar/{id}` (`show`) – Returns the event by ID or `404`.
- `POST /api/v1/calendar` (`store`) – Body must include `title`, `start_date (Y-m-d)` and optional `description`, `end_date`, `location`. Responds with `201` and the created record.
- `PATCH /api/v1/calendar/{id}` (`update`) – Allows partial updates to the same fields; returns the refreshed record.
- `DELETE /api/v1/calendar/{id}` (`delete`) – Removes the event and returns the standard success envelope.

## CiRequestAdapter
**File:** `app/Http/Controllers/Api/CiRequestAdapter.php`  
**Purpose:** Lightweight shim that lets the Laravel request behave like CodeIgniter’s `$this->request`.

- `getGet()` / `getPost()` – Mirror CI helpers by returning either all query/form params or a single key with a default.
- `getJSON($assoc = false)` – Parses the raw body once and returns decoded JSON (array/object).
- `getVar()` – Unified accessor that checks body/query for a key.
- `getHeader()` / `getHeaderLine()` – Pull HTTP headers using familiar CI naming.
- `getFile()` – Returns the uploaded file object (if any) for the key.
- `getRawInput()` – Gives the unparsed request body string.
- `all()` – Aggregates query, form, and JSON payloads into a single array—handy when migrating legacy controllers.

## ClassController
**File:** `app/Http/Controllers/Api/ClassController.php`  
**Purpose:** Provide read endpoints for classes and their rosters.

- `GET /api/v1/classes` (`index`) – Supports pagination plus optional `grade` and `teacher_id` filters; returns ordered list of class records.
- `GET /api/v1/classes/{id}` (`show`) – Retrieves a single class or `404`.
- `GET /api/v1/classes/{id}/students` (`students`) – Joins `student_class` and `students` to return each enrolled student (`id`, `firstname`, `lastname`, `school_id`) sorted alphabetically.

## ClassPreparationController
**File:** `app/Http/Controllers/Api/ClassPreparationController.php`  
**Purpose:** Track supplies needed for each class and provide a lightweight "requirements" calculator.

- `GET /api/v1/class-prep/list` (`index`) – Returns class preparation data filtered by optional `school_year` and `semester` query params. Calculates per-section prep items, available inventory, shortages, and change flags. Returns:
  - `results` – Array of class sections with prep items, student counts, adjustments, and print status
  - `totals` – Required totals for each prep category
  - `shortages` – Items where available inventory is less than required
  - `available` – Current inventory map
- `POST /api/v1/class-prep/adjustments` (`saveAdjustment`) – Body requires `class_section_id`, `school_year`, and `adjustments` object. Persists manual adjustments for large/small tables and returns `{updated: count}`.
- `POST /api/v1/class-prep/mark-printed` (`markPrinted`) – Body requires `school_year` and `class_section_ids` array. Stores printed snapshots for the supplied section IDs to reset `needs_print` flag. Returns `{updated: count}`.

## CommunicationController
**File:** `app/Http/Controllers/Api/CommunicationController.php`  
**Purpose:** Internal messaging between users (parents ↔ staff, etc.).

- `GET /api/v1/communications` (`index`) – Paginated feed filtered by optional `sender_id` and/or `receiver_id`, ordered newest-first.
- `POST /api/v1/communications` (`send`) – Body must include `sender_id`, `receiver_id`, `subject`, `message`. Returns `201` with stored record.
- `GET /api/v1/communications/conversation` (`conversation`) – Requires both `sender_id` and `receiver_id` query params. Returns the chronological conversation (both directions) between the two users.
- `DELETE /api/v1/communications/{id}` (`delete`) – Removes a message by ID.
- `GET /api/v1/communications/metadata` (`metadata`) – Returns the active students list (id, firstname, lastname, grade) plus all active email templates for building a mailing UI.
- `GET /api/v1/communications/students/{student_id}/families` (`families`) – Returns the active families for the supplied student along with the `is_primary_home` flag so the front-end can preselect the right household.
- `GET /api/v1/communications/families/{family_id}/guardians` (`guardians`) – Joins `family_guardians` with `users` to return each guardian’s name, email, relation, and preferred delivery flags.
- `POST /api/v1/communications/preview` (`preview`) – Body requires `template_key`, `student_id`, and `family_id`; optional `vars`. Renders the Twig-like placeholders in the chosen template and returns a sanitized subject/body preview.
- `POST /api/v1/communications/send-email` (`sendEmail`) – Body requires `student_id`, `family_id`, `template_key`, `subject`, `body`, and `recipients` (JSON array or string). Accepts optional `cc`/`bcc`, sends the HTML email through `Mail::send`, and logs the result in `communication_logs`.

## ConfigurationController
**File:** `app/Http/Controllers/Api/ConfigurationController.php`  
**Purpose:** Manage key/value pairs stored in the `configuration` table.

- `GET /api/v1/configuration` (`index`) – Paginated list ordered by ID with optional fuzzy `key` search.
- `GET /api/v1/configuration/{id}` (`show`) – Fetch a config row by numeric ID.
- `GET /api/v1/configuration/key/{config_key}` (`getByKey`) – Convenience lookup using the string key.
- `POST /api/v1/configuration` (`create`) – Requires unique `config_key` and `config_value`; wraps persistence in try/catch and returns `201`.
- `PATCH /api/v1/configuration/{id}` (`update`) – Allows updating `config_key` and/or `config_value`, ensuring the payload isn’t empty.
- `DELETE /api/v1/configuration/{id}` (`delete`) – Removes the row after existence check.
- `GET /api/v1/configuration/list` (`listData`) – Returns all configs as `{id, config_key, config_value}` for settings UIs.

## ContactController
**File:** `app/Http/Controllers/Api/ContactController.php`  
**Purpose:** Public “Contact Us” submission endpoint that relays a message via the shared `EmailService`.

- `POST /api/v1/contact` (`submit`) – Body requires `name`, `email`, `subject`, and `message`. Validates lengths + email format, then composes HTML and sends it to `SUPPORT_EMAIL` (default `support@alrahmaisgl.org`). Returns a friendly success message or `500` if delivery fails.

## DiscountController
**File:** `app/Http/Controllers/Api/DiscountController.php`  
**Purpose:** Validate and apply voucher codes against invoices, enforcing date windows and usage limits.

- `GET /api/v1/discounts` (`index`) – Returns all vouchers ordered newest-first. Optional `?active=true` filters down to vouchers that are enabled, within the valid-from/until window.
- `GET /api/v1/discounts/{code}` (`getByCode`) – Looks up the code (case-sensitive in the DB). Responds `404` if inactive/expired and `400` if the max usage has already been hit.
- `POST /api/v1/discounts/apply` (`apply`) – Requires authentication plus a JSON body with `code` and `invoice_id`. Validates the voucher, ensures it hasn’t been applied to that invoice, records the usage (`discount_usage`), increments `times_used`, and returns the refreshed voucher payload. Errors return `400/401/500` with descriptive messages.

## EmailController
**File:** `app/Http/Controllers/Api/EmailController.php`  
**Purpose:** Send HTML emails using PHPMailer with support for multiple email profiles, DKIM signing, and attachments.

- `POST /api/v1/email/send` (`send`) – Body requires `recipient`, `subject`, and `html_message`. Optional parameters include:
  - `profile` – Email profile name (e.g., 'communication', 'payment', 'default'). Uses `MAIL_PROFILE_DEFAULT` or 'default' if not specified.
  - `reply_to_email` – Override reply-to email address
  - `reply_to_name` – Override reply-to name
  - `attachments` – Array of attachment objects: `[{'path': '...', 'name': '...'}, ...]`
  
  Supports multiple SMTP profiles via environment variables (`MAIL_{PROFILE}_*` or `MAIL_DEFAULT_*`). Features include:
  - DKIM signing (if configured)
  - Socket preflight checks
  - Debug mode (via `MAIL_DEBUG` env)
  - TLS/SSL encryption with auto-correction
  - Returns `{recipient, subject, sent: true}` or error response on failure.

## EmailExtractorController
**File:** `app/Http/Controllers/Api/EmailExtractorController.php`  
**Purpose:** Admin utility to export user/parent emails and compare them against a CSV list or uploaded file.

- `GET /api/v1/email-extractor/emails` (`getEmails`) – Returns all normalized emails from `users.email` and `parents.secondparent_email` as `{users: string[], parents: string[]}`. Emails are validated, lowercased, and de-duplicated.
- `POST /api/v1/email-extractor/compare` (`compare`) – Accepts either:
  - Multipart/form-data with a CSV file named `file` (emails extracted via regex)
  - JSON body with `csvEmails` array
  
  Compares CSV emails against database emails and returns:
  - `existed` – Emails present in both CSV and database
  - `needToAdd` – Emails in database but not in CSV
  - `counts` – Object with `csv`, `db`, `users`, and `parents` counts

## EmergencyContactController
**File:** `app/Http/Controllers/Api/EmergencyContactController.php`  
**Purpose:** Maintain emergency contacts scoped to the configured school year/semester.

- `GET /api/v1/emergency-contacts` (`index`) – Optional `parent_id` filter; automatically filters by current year/semester. Returns paginated list of emergency contacts.
- `GET /api/v1/emergency-contacts/data` (`data`) – Returns emergency contacts grouped by parent with enriched data:
  - `groups` – Array of parent groups, each containing:
    - `parent_id` – Parent user ID
    - `parent_name` – Full name of the parent
    - `students` – Array of students associated with this parent (id, firstname, lastname, school_id)
    - `contacts` – Array of emergency contacts for this parent
    - `parent_phones` – Array of phone numbers (primary and secondary parent phones, filtered to non-empty values)
- `GET /api/v1/emergency-contacts/{id}` (`show`) – Fetch a single emergency contact or `404`.
- `GET /api/v1/emergency-contacts/parent/{parent_id}` (`getByParent`) – Returns all emergency contacts for a specific parent, filtered by current year/semester.
- `POST /api/v1/emergency-contacts` (`create`) – Requires `parent_id`, `emergency_contact_name`, `cellphone`, `relation`; optional `email`. Attaches the current year/semester before inserting.
- `PATCH /api/v1/emergency-contacts/{id}` (`update`) – Allows updating `emergency_contact_name`, `cellphone`, `email`, and `relation`.
- `DELETE /api/v1/emergency-contacts/{id}` (`delete`) – Removes the contact.

## EventController
**File:** `app/Http/Controllers/Api/EventController.php`  
**Purpose:** CRUD operations for events and event charges management.

- `GET /api/v1/events` (`index`) – Returns all events filtered by optional `school_year` query param. When `?upcoming=true`, filters by `expiration_date >= today`. Returns `{events: array, active_event_count: number}`.
- `GET /api/v1/events/{id}` (`show`) – Fetch an event by ID or `404`.
- `POST /api/v1/events` (`create`) – Creates a new event. Body requires `event_name`, `amount`, `expiration_date`. Optional: `description`, `semester`, `school_year`, `flyer` (file upload). Automatically sets `created_by` from session.
- `PATCH /api/v1/events/{id}` (`update`) – Updates an event. Allows updating `event_name`, `description`, `amount`, `expiration_date`, `semester`, `school_year`, and `flyer` (file upload). Old flyer is deleted when a new one is uploaded.
- `DELETE /api/v1/events/{id}` (`delete`) – Deletes an event and all associated event charges. Collects affected parent IDs for invoice regeneration (note: `generateInvoice` method may need implementation).
- `GET /api/v1/events/charges` (`charges`) – Returns event charges with parent and student information. Optional query params: `school_year`, `semester`. Returns `{charges: array, parents: array, events: array, school_year: string, semester: string}`.
- `POST /api/v1/events/charges/update` (`updateCharges`) – Updates event charges for a parent and students. Body requires `parent_id`, `event_id`, `participation` (object mapping student_id to 'yes'/'no'). Optional: `school_year`, `semester`. Creates or updates charges, deletes when participation is 'no'. Regenerates invoice for parent (note: `generateInvoice` may need implementation).
- `GET /api/v1/events/students-with-charges` (`getStudentsWithCharges`) – Returns students for a parent with their charge status. Query params: `parent_id` (required), `semester`, `school_year`. Returns array of `{id, name, charged: boolean}`.

## ExpenseController
**File:** `app/Http/Controllers/Api/ExpenseController.php`  
**Purpose:** CRUD for expense reimbursement records with school year/semester scoping.

- `GET /api/v1/expenses` (`index`) – Paginated list filtered by `status` and/or `category`, always constrained to the active year/semester.
- `GET /api/v1/expenses/{id}` (`show`) – Retrieve a single expense.
- `POST /api/v1/expenses` (`create`) – Body needs `category`, `amount`, `description`, `date_of_purchase`; fills metadata like `school_year`, `semester`, `purchased_by`, and `status`.
- `PATCH /api/v1/expenses/{id}` (`update`) – Allows selective updates (category/amount/description/retailor/date/status/status_reason) and stamps `updated_by`.
- `DELETE /api/v1/expenses/{id}` (`delete`) – Deletes the record.

## ExtraChargesController
**File:** `app/Http/Controllers/Api/ExtraChargesController.php`  
**Purpose:** Manage "additional charges" tacked onto parent invoices with full transaction support and invoice adjustment.

- `GET /api/v1/extra-charges` (`index`) – Paginated list filtered by `school_year`, `semester`, `status`, and optional search query `q`. Returns charges with parent names and invoice numbers. Defaults to configured year/semester.
- `GET /api/v1/extra-charges/{id}` (`show`) – Retrieve a single charge or `404`.
- `POST /api/v1/extra-charges` (`store`) – Creates a new charge. Body requires:
  - `parent_id` (integer, required) – The user ID of the parent
  - `title` (string, required, min 2 chars)
  - `amount` (numeric, required)
  - `charge_type` (required, `add` or `deduct`)
  - `due_date` (nullable, date format)
  - `invoice_id` (nullable, integer) – If provided, charge is immediately applied to invoice
  - `description` (optional string)
  - `school_year` (optional, defaults to configured year)
  - `semester` (optional, defaults to configured semester)
  
  When `invoice_id` is provided, the charge is automatically applied to the invoice (adjusting `total_amount` and `balance`). Dispatches `extraCharge` event with full context including pre/post balance snapshots.
- `PATCH /api/v1/extra-charges/{id}` (`update`) – Updates charge fields (`title`, `description`, `amount`, `due_date`, `charge_type`). If the charge is already applied to an invoice and the amount changes, the invoice is automatically adjusted to reflect the delta.
- `POST /api/v1/extra-charges/{id}/void` (`void`) – Marks a charge as void. If the charge was applied to an invoice, the invoice impact is rolled back (reversed for additions, re-added for deductions). Uses database transactions.
- `POST /api/v1/extra-charges/{id}/reverse` (`reverse`) – Reverses an applied charge back to pending status. Undoes the invoice impact and clears the `invoice_id`. Only works on charges with status `applied`.
- `DELETE /api/v1/extra-charges/{id}` (`delete`) – Permanently deletes the charge record.
- `GET /api/v1/extra-charges/parent-options` (`parentOptions`) – Returns Select2-compatible `{results: [{id, text}]}` array for parent users. Optional query param `q` filters by firstname, lastname, or email. Format: "Lastname, Firstname — email (ID: x)".
- `GET /api/v1/extra-charges/invoices-for-parent` (`invoicesForParent`) – Returns Select2-compatible invoice list for a parent. Query params: `parent_id` (required), `school_year` (optional). Format: "INV-123 — status (Bal: $X.XX)".

## FamilyAdminController
**File:** `app/Http/Controllers/Api/FamilyAdminController.php`  
**Purpose:** Admin-only aggregate view of students, families, guardians, and financial data.

- `GET /api/v1/family-admin` (`index`) – Returns comprehensive family administration data. Query params:
  - `student_id` (optional) – Filters to that student's family memberships
  - `guardian_id` (optional) – If provided without `student_id`, resolves to one of the guardian's students
  
  Returns:
  - `students` – Simple list of all students (id, name) for dropdowns
  - `search_students` – Student search dataset (id, firstname, lastname)
  - `search_guardians` – Guardian search dataset (id, firstname, lastname, email, cellphone)
  - `student` – Selected student info (if `student_id` provided)
  - `families` – Array of families for the student, each containing:
    - Family details (family_code, household_name, address, etc.)
    - `guardians` – Array of guardians with contact info and preferences
    - `students` – Array of students in the family with grade information
    - `invoices` – All invoices for guardians in this family
    - `payments` – Recent payments (limit 10) for guardians
    - `finance_summary` – Aggregated totals (invoices_count, total_amount, paid_amount, balance)
    - `invoice_map` – Mapping of invoice IDs to invoice numbers
  - `guardians` – Guardians of the first family (back-compat)

- `GET /api/v1/family-admin/search` (`search`) – Search for students and guardians. Query param `q` (required) searches by:
  - Students: firstname, lastname
  - Guardians: firstname, lastname, email, cellphone
  
  Returns `{items: []}` array where each item has:
  - `type` – "student" or "guardian"
  - `id` – ID of the student/guardian
  - `label` – Full name
  - `sub` – Additional info (email/phone for guardians, "Student" for students)
  
  Limited to 8 results per type.

- `GET /api/v1/family-admin/card` (`card`) – Returns detailed family card data. Query params:
  - `family_id` (optional) – Direct family ID
  - `student_id` (optional) – Resolves to primary family for that student
  - `guardian_id` (optional) – Resolves to a family via guardian link or student parent_id
  
  Returns a single `family` object with:
  - Family details (all fields from families table)
  - `guardians` – All guardians with full contact info
  - `students` – All students with grade information
  - `invoices` – All invoices for guardians
  - `payments` – Recent payments (limit 10)
  - `finance_summary` – Aggregated financial totals
  - `invoice_map` – Invoice ID to number mapping
  - `emergency_contacts` – Emergency contacts for all guardians, enriched with parent labels

## FamilyController
**File:** `app/Http/Controllers/Api/FamilyController.php`  
**Purpose:** CRUD and management endpoints for families, linking families to guardians (users) and students, including bootstrap from existing schema.

**Read Endpoints:**
- `GET /api/v1/families` (`index`) – Paginated families with eager-loaded `students` and `guardians`.
- `GET /api/v1/families/{id}` (`show`) – Fetch a family with its relations.
- `GET /api/v1/families/by-student/{student_id}` (`getByStudent`) – Returns every family associated with the student, including nested members.
- `GET /api/v1/families/{id}/guardians` (`getGuardians`) – Convenience list of guardians for a family.
- `GET /api/v1/families/students/{student_id}/families` (`familiesByStudent`) – Returns all active families for a student with `is_primary_home` flag, ordered by primary home first.
- `GET /api/v1/families/{family_id}/guardians-list` (`guardiansByFamily`) – Returns guardians for a family with user details (firstname, lastname, email) and guardian flags (relation, is_primary, receive_emails, receive_sms).

**Bootstrap & Linking:**
- `POST /api/v1/families/bootstrap` (`bootstrap`) – Creates/ensures families for every student with `parent_id`, links students to families, and links primary parents as guardians. Returns counts of families created, students linked, and guardians linked. Uses database transactions.
- `POST /api/v1/families/attach-second-by-user` (`attachSecondByUser`) – Attaches a second parent/guardian to a student's primary family. Body requires `student_id`, `user_id`, and optional `relation` (defaults to 'secondary').
- `POST /api/v1/families/attach-second-by-email` (`attachSecondByEmail`) – Attaches a second parent by email, creating a stub user if the email doesn't exist. Body requires `student_id`, `email`, and optional `firstname`, `lastname`, `relation` (defaults to 'secondary').

**Maintenance Actions:**
- `POST /api/v1/families/set-primary-home` (`setPrimaryHome`) – Sets the `is_primary_home` flag for a student-family relationship. Body requires `family_id`, `student_id`, and `is_primary_home` (0/1).
- `POST /api/v1/families/set-guardian-flags` (`setGuardianFlags`) – Updates guardian flags. Body requires `family_id`, `user_id`, and any of: `receive_emails` (0/1), `is_primary` (0/1), `receive_sms` (0/1), `relation` (string).
- `POST /api/v1/families/unlink-guardian` (`unlinkGuardian`) – Removes a guardian from a family. Body requires `family_id` and `user_id`.
- `POST /api/v1/families/unlink-student` (`unlinkStudent`) – Removes a student from a family. Body requires `family_id` and `student_id`.
- `POST /api/v1/families/import-second-parents` (`importSecondParentsFromLegacy`) – Imports second parents from legacy `parents` table. Creates users for email-only entries, links them as guardians, and ensures all students are linked to families. Returns counts of users created, guardians linked, skipped, and total source rows.

## FilesController
**File:** `app/Http/Controllers/Api/FilesController.php`  
**Purpose:** Serve uploaded receipts and reimbursements from storage with security validation, MIME detection, and HTTP caching support.

**Endpoints:**
- `GET /api/v1/files/receipt/{name}` (`receipt`) – Streams a receipt file with path traversal protection, extension allow-list validation, MIME type detection, ETag/Last-Modified headers, and 304 Not Modified support. Returns `400` for invalid filenames, `404` for missing files or disallowed extensions.
- `GET /api/v1/files/reimb/{name}` (`reimb`) – Streams a reimbursement file with the same security and caching features as `receipt()`.
- `GET /api/v1/files/reimbursement/{name}` (`reimbursement`) – Alias for `reimb()` for backward compatibility.
- `GET /api/v1/files` (`index`) – Requires auth. Query param `type` must be one of `receipts|reimbursements|checks` (defaults to `receipts`). Returns paginated list of files with name, size, and modified timestamp.

**Security Features:**
- Path traversal protection via `basename()` validation
- Extension allow-list: `jpg`, `jpeg`, `png`, `webp`, `gif`, `pdf`
- MIME type detection using `finfo_open()` (with fallback to `mime_content_type()`)
- `X-Content-Type-Options: nosniff` header

**Caching:**
- ETag generation based on filename, modification time, and file size
- Last-Modified header support
- 304 Not Modified responses when `If-None-Match` or `If-Modified-Since` conditions are met
- `Cache-Control: public, max-age=86400` header for browser caching

## FinalController
**File:** `app/Http/Controllers/Api/FinalController.php`  
**Purpose:** Manage final exam scores per student/class-section for the active term.

**Endpoints:**
- `GET /api/v1/finals/student/{student_id}` (`getByStudent`) – Returns the final exam entry for the student scoped by school year/semester.
- `GET /api/v1/finals/class-section/{class_section_id}` (`getByClassSection`) – Returns all students in a class section with their final exam scores. Accepts `class_section_id` as route parameter, query param, or POST body. Uses a subquery to select the latest final exam per student (by MAX(id)) and joins it with the student roster. Returns an array of students with `student_id`, `school_id`, `firstname`, `lastname`, and `score`, along with `semester`, `schoolYear`, and `class_section_id` metadata. Students are ordered by lastname, then firstname.
- `POST /api/v1/finals` (`create`) – Upserts by (`student_id`, `class_section_id`) after validating input; fills metadata and returns `201`.
- `PATCH /api/v1/finals/{id}` (`update`) – Allows changing `score` and `comment`.

## MidtermController
**File:** `app/Http/Controllers/Api/MidtermController.php`  
**Purpose:** Manage midterm exam scores for students in class sections.

**Endpoints:**
- `GET /api/v1/midterm` (`index`) – Returns midterm exam data for a class section. Query parameters:
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via POST or session). If not provided, falls back to session value.
  - Returns array of students with their midterm scores, semester, school year, and class section ID. Uses a subquery to select the latest midterm exam per student (by MAX(id)) and joins it with the student roster. Students are ordered by lastname, then firstname.
- `POST /api/v1/midterm` (`update`) – Updates midterm exam scores (teacher endpoint). Body requires:
  - `final_score` (object, required) – Object mapping student IDs to score objects: `{"student_id": {"score": float}}`
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via session)
  - Automatically uses current authenticated user as `updated_by`. Updates or creates midterm exam records and triggers semester score recalculation via `SemesterScoreService::updateScoresForStudents()`.
- `GET /api/v1/midterm/management` (`showManagement`) – Returns midterm exam data for management view. Same as `index` but intended for administrative use. Query parameters:
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via POST or session)
- `POST /api/v1/midterm/management` (`updateManagement`) – Updates midterm exam scores (management endpoint). Body requires:
  - `final_score` (object, required) – Object mapping student IDs to score objects: `{"student_id": {"score": float}}`
  - `teacher_id` (integer, optional) – Teacher ID to record as updater (defaults to current user)
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via session)
  - Returns updated student roster with scores after successful update. Triggers semester score recalculation.

**Notes:**
- All endpoints require authentication (`auth:api` middleware).
- Class section ID can be provided via query parameter, POST body, or session (checked in that order).
- Scores are automatically scoped to the configured semester and school year.
- Updates trigger `SemesterScoreService::updateScoresForStudents()` to recalculate semester scores.
- The system ensures one score record per student per class section per term (updates existing or creates new).

## FinancialController
**File:** `app/Http/Controllers/Api/FinancialController.php`  
**Purpose:** Generate comprehensive financial reports, summaries, and identify parents with outstanding balances.

**Endpoints:**
- `GET /api/v1/financial/report` (`report`) – Detailed financial report with invoices, payments, payment breakdown by method (cash/credit/check), refunds, expenses, reimbursements, and discounts. Supports `date_from`, `date_to`, and `school_year` query parameters. Returns invoices with parent names, payment breakdown per invoice, overall payment totals by method, grouped refunds/discounts, expenses by category, and reimbursements by status. Also includes available school years list.
- `GET /api/v1/financial/report-summary` (`reportSummary`) – Aggregated financial summary. Supports `date_from`, `date_to`, and `school_year` query parameters. Returns totals for charges (including extra charges), discounts, refunds, expenses, reimbursements (excluding donations), donations to school, paid amounts, unpaid amounts, and net amount calculations. Includes available school years list.
- `GET /api/v1/financial/unpaid-parents` (`unpaidParents`) – Lists parents with outstanding balances (> 0) for a school year. Supports `school_year` query parameter (defaults to configured year). Computes balances dynamically by subtracting payments, discounts, and refunds from invoice totals. Returns parent details, total invoiced, total balance, remaining installments, suggested installment amount, payment type (installment vs no_payment), total paid, and next installment date. Results ordered by balance descending.
- `GET /api/v1/financial/download-csv` (`downloadCsv`) – Downloads financial report as CSV file. Supports `date_from`, `date_to`, and `school_year` query parameters. CSV includes invoices section (with parent names, totals, paid, balance, refund), expenses summary by category, and reimbursements summary by status. Filename format: `financial_report_YYYYMMDD_HHMMSS.csv`.

## FlagController
**File:** `app/Http/Controllers/Api/FlagController.php`  
**Purpose:** Track behavioral/academic flags assigned to students and manage their lifecycle.

**Endpoints:**
- `GET /api/v1/flags` (`index`) – Paginates `current_flags` with optional `student_id`, `flag_type` (or `flag`), `state` (or `flag_state`) filters. Also returns class sections (grades) array for UI dropdowns.
- `GET /api/v1/flags/{id}` (`show`) – Returns a single flag from `current_flag` table.
- `GET /api/v1/flags/students/{grade_id}` (`getStudentsByGrade`) – Returns students in the class section (grade), helpful for flag assignment UIs. Returns array of `{id, name}` objects.
- `POST /api/v1/flags` (`create`) – Alias for `addFlag()`. Creates or updates a flag for a student.
- `POST /api/v1/flags/add` (`addFlag`) – Add or update a flag for a student. If a flag of the same type already exists for the student, it updates it (appends description based on state); otherwise creates a new one. Requires `student_id` (or `student`), `flag` (or `flag_type`), `description` (or `open_description`). Optional: `flag_state`, `state_description`, `grade`. Automatically sets semester and school_year from configuration. New flags default to `Open` state.
- `PATCH /api/v1/flags/{id}/state` (`updateState`) – Updates `flag_state` and/or `state_description`. Updates appropriate description field (`open_description`, `close_description`, or `cancel_description`) based on the new state.
- `POST /api/v1/flags/{id}/close` (`close`) – Alias for `closeFlag()`. Closes a flag and moves it to history.
- `POST /api/v1/flags/{id}/close-flag` (`closeFlag`) – Closes a flag and moves it to history. Requires `state_description` (or `close_description`) in request body. Updates flag state to `Closed`, sets `updated_by_closed`, then moves the flag from `current_flag` to `flag` table and deletes from `current_flag`.
- `POST /api/v1/flags/{id}/cancel` (`cancel`) – Alias for `cancelFlag()`. Cancels a flag and moves it to history.
- `POST /api/v1/flags/{id}/cancel-flag` (`cancelFlag`) – Cancels a flag and moves it to history. Requires `state_description` (or `cancel_description`) in request body. Updates flag state to `Canceled`, sets `updated_by_canceled`, then moves the flag from `current_flag` to `flag` table and deletes from `current_flag`.

**Flag States:** `Open`, `Closed`, `Canceled` (capitalized)

**Flag Lifecycle:** Flags start in `current_flag` table. When closed or canceled, they are moved to `flag` (history) table via `moveToHistory()` method.

## FrontendController
**File:** `app/Http/Controllers/Api/FrontendController.php`  
**Purpose:** Provide lightweight data to the public frontend SPA.

- `GET /api/v1/frontend/user` (`getUser`) – Requires auth; returns first/last name, full name, and initials for the current session.
- `GET /api/v1/frontend/pages` (`getPages`) – Returns the static mapping of page keys to URLs that the frontend expects.

## InfoIconController
**File:** `app/Http/Controllers/Api/InfoIconController.php`  
**Purpose:** Provide user profile icon information (name and initials) for UI display.

- `GET /api/v1/info-icon/profile` (`profileIcon`) – Requires auth; returns user name and initials for the current logged-in user. Returns:
  - `user_name` (string) – Full name of the user (firstname + lastname), or email if name is empty, or "User #ID" as fallback
  - `user_initials` (string) – Uppercase initials derived from firstname and lastname (first letter of each), or first 2 characters of firstname/lastname/email as fallback, or "??" if no data available
  
  Returns `401 Unauthorized` if user is not logged in. Returns `200 OK` with fallback values if user is not found.

## LandingPageController
**File:** `app/Http/Controllers/Api/LandingPageController.php`  
**Purpose:** Deliver role-based dashboard data including school year, deadlines, notifications, students, attendance, grades, and enrollments.

**Endpoints:**
- `GET /api/v1/landing` (`index`) – Requires auth; returns role-specific dashboard data based on user's role. Routes to:
  - `administrator` – Returns basic admin metadata (school year, semester, deadlines)
  - `admin` – Returns basic admin metadata
  - `teacher` – Returns teacher metadata including `class_section_id`
  - `student` – Returns student metadata
  - `parent` / `authorized_user` – Returns comprehensive parent dashboard data (see below)
  - `guest` – Returns basic guest metadata
- `GET /api/v1/landing/data` (`getData`) – Requires auth; returns basic landing page bootstrap data:
  - `school_year`, `semester`
  - `enrollment_deadline`, `refund_deadline`
  - `class_section_id` (for teachers)
  - `user_role`

**Parent Dashboard Data** (returned when role is `parent` or `authorized_user`):
- `notifications` – Array of active, non-expired notifications (personal and broadcast) for parents
- `students` – Array of students associated with the parent, including class section/grade information
- `attendance` – Attendance records for students filtered by current school year/semester
- `grades` – Final scores for students filtered by current school year/semester
- `enrollments` – Enrollment records for students filtered by current school year/semester
- `paymentBalance` – Latest invoice total amount for the parent
- `lastDayOfRegistration` – Enrollment deadline from configuration
- `withdrawalDeadline` – Refund deadline from configuration
- `school_year`, `semester` – Current term information

**User Type Handling:**
- Supports `primary`, `secondary`, and `tertiary` user types
- Automatically resolves parent ID for secondary/tertiary users from `parents` and `authorized_users` tables

## LateSlipLogsController
**File:** `app/Http/Controllers/Api/LateSlipLogsController.php`  
**Purpose:** Paginated search and retrieval of late slip submission logs.

**Endpoints:**
- `GET /api/v1/late-slip-logs` (`index`) – Paginated list of late slip logs with optional filters. Query parameters:
  - `school_year` (string, optional) – Filter by school year (defaults to configured year)
  - `semester` (string, optional) – Filter by semester (defaults to configured semester)
  - `q` (string, optional) – Search term to filter by student name (LIKE search)
  - `date_from` (date, optional) – Filter logs from this date (inclusive, format: Y-m-d or any parseable date)
  - `date_to` (date, optional) – Filter logs until this date (inclusive, format: Y-m-d or any parseable date)
  - `page` (integer, optional) – Page number for pagination (default: 1)
  - `per_page` (integer, optional) – Items per page (default: 20, max: 200)
  - Returns paginated results with logs array, pagination metadata, and applied filters
- `GET /api/v1/late-slip-logs/{id}` (`show`) – Fetch a single late slip log by ID or `404` if not found.

**Log Fields:**
- `id`, `school_year`, `semester`
- `student_name`, `slip_date`, `time_in`
- `grade`, `reason`, `admin_name`
- `printed_by`, `printed_at`

## MessageController
**File:** `app/Http/Controllers/Api/MessageController.php`  
**Purpose:** Inbox/sent endpoints with read receipts for the legacy messaging UI.

- `GET /api/v1/messages` – Query params `type=sent|inbox` and `read=true|false`.
- `GET /api/v1/messages/{id}` – Ensures the caller is sender/recipient, marks as read.
- `POST /api/v1/messages` – Validates `to`, `subject`, `body`, auto-increments `message_number`.

## MessagesController
**File:** `app/Http/Controllers/Api/MessagesController.php`  
**Purpose:** Full-featured messaging module with folder management, attachments, and recipient lookup.

**Endpoints:**
- `GET /api/v1/messages` (`index`) – Returns role and received messages overview. Includes user role and all received messages with sender information.
- `GET /api/v1/messages/inbox` (`inbox`) – Returns all inbox messages (status='received') for the authenticated user, ordered by sent_datetime DESC.
- `GET /api/v1/messages/sent` (`sent`) – Returns all sent messages (status='sent') for the authenticated user, ordered by sent_datetime DESC.
- `GET /api/v1/messages/drafts` (`drafts`) – Returns all draft messages (status='draft') for the authenticated user, ordered by sent_datetime DESC.
- `GET /api/v1/messages/trash` (`trash`) – Returns all trashed messages (status='trashed') for the authenticated user (both sent and received), ordered by sent_datetime DESC.
- `POST /api/v1/messages` (`send`) – Sends a new message. Body requires:
  - `recipient_id` (integer) – Recipient user ID, or
  - `recipient_role` (string) – Role name to resolve recipient (uses hardcoded mapping: teacher=1, parent=2, admin=3, student=4, guest=5, administrator=6)
  - `subject` (string, required) – Message subject
  - `message` (string, required) – Message body
  - Optional: `priority` (string, default: 'normal'), `status` (string, default: 'sent'), `semester`, `school_year`
  - Optional: `attachment` (file upload) – Attachment file
  - Automatically generates `message_number` in format 'MSG-YYYYMMDDHHMMSS'
  - Returns created message
- `POST /api/v1/messages/receive` (`receive`) – Fetches all received messages for the authenticated user and automatically marks them as read. Returns messages with sender information.
- `GET /api/v1/messages/recipients/{type}` (`getRecipients`) – Lists recipients for message composition. Path parameter `type` must be 'teacher' or 'parent':
  - `teacher` – Returns all teachers from teacher_class assignments with their names
  - `parent` – Returns all parents (first and second parents) from students in teacher classes, with their names
  - Returns array of `{id, name}` objects, deduplicated by ID

**Message Fields:**
- `id`, `sender_id`, `recipient_id`
- `subject`, `message`
- `sent_datetime`, `read_status`, `read_datetime`
- `message_number`, `priority` (normal/high/low)
- `attachment` (file path), `status` (sent/received/draft/trashed)
- `semester`, `school_year`

## MessagingController
**File:** `app/Http/Controllers/Api/MessagingController.php`  
**Purpose:** Chat-style messaging (“subjectless” chats) with unread counts.

- `GET /api/v1/messaging/conversations` – Lists conversation previews.
- `GET /api/v1/messaging/conversations/{user}` – Paginated thread with another user; marks inbound as read.
- `POST /api/v1/messaging/messages` – Validates `recipient_id` and body, enforces no self-messaging, records term metadata.
- Additional helpers (mark read, unread count, search users, delete) support the chat client.

## NavBuilderController
**File:** `app/Http/Controllers/Api/NavBuilderController.php`  
**Purpose:** Manage navigation menu items and their role-based access assignments.

**Endpoints:**
- `GET /api/v1/nav-builder/data` (`data`) – Returns navigation builder data including:
  - `items` – Flattened array of all navigation items with hierarchical structure, role assignments, parent relationships, and metadata (label, url, icon_class, target, order, enabled status, depth)
  - `roles` – Array of all available roles (id, name) for assignment
  - `parentOptions` – Alphabetically sorted list of navigation items that can be used as parents (id, label)
  - Items are sorted alphabetically (case-insensitive, ignoring leading articles and punctuation)
  - Requires authentication and admin access (user must have a role that has access to the 'nav-builder' navigation item)
- `POST /api/v1/nav-builder` (`save`) – Creates or updates a navigation item. Body requires:
  - `id` (integer, optional) – If provided, updates existing item; otherwise creates new
  - `label` (string, required) – Display label for the menu item
  - `url` (string, optional) – URL path for the menu item
  - `icon_class` (string, optional) – CSS class for icon
  - `target` (string, optional) – Link target (e.g., '_blank')
  - `menu_parent_id` or `parent_id` (integer, optional) – Parent navigation item ID (null for top-level)
  - `sort_order` (integer, optional) – Sort order (default: 0)
  - `is_enabled` (boolean, optional) – Whether the item is enabled (default: false)
  - `roles` (array, optional) – Array of role IDs to assign to this navigation item
  - Prevents items from being their own parent. Validates parent exists if provided. Clears navigation cache after save.
- `DELETE /api/v1/nav-builder/{id}` (`delete`) – Deletes a navigation item by ID. Children are handled by foreign key constraints (SET NULL on parent). Clears navigation cache after deletion.
- `POST /api/v1/nav-builder/reorder` (`reorder`) – Reorders navigation items. Body requires:
  - `orders` (object, required) – Object mapping item IDs to sort orders: `{"item_id": sort_order, ...}`
  - Clears navigation cache after reordering.

**Access Control:**
- All endpoints require authentication (`auth:api` middleware)
- All endpoints require admin access via `ensureAdmin()` method which:
  - Checks that user has at least one role
  - Verifies that at least one of the user's roles has access to the 'nav-builder' navigation item (checked via `role_nav_items` table)
  - Returns `403 Forbidden` if access is denied

**Notes:**
- Navigation items are organized hierarchically using `menu_parent_id`
- Items are sorted alphabetically using natural case-insensitive sorting, ignoring leading articles ("a", "an", "the") and punctuation
- Role assignments are stored in the `role_nav_items` table linking roles to navigation items
- The `clearCache()` method is called after save/delete/reorder operations (currently a no-op, placeholder for future caching)

## NotificationController
**File:** `app/Http/Controllers/Api/NotificationController.php`  
**Purpose:** Retrieve and mark notifications read for the signed-in user.

- `GET /api/v1/notifications` – Paginated feed with optional `read=true|false`.
- `GET /api/v1/notifications/{id}` – Ensures the caller owns the record.
- `POST /api/v1/notifications/{id}/read` – Sets `is_read` + `read_at`.

## NotificationsController
**File:** `app/Http/Controllers/Api/NotificationsController.php`  
**Purpose:** Placeholder for legacy notifications (no public API methods today).

## NotificationManagementController
**File:** `app/Http/Controllers/Api/NotificationManagementController.php`  
**Purpose:** Admin/management endpoints for creating, sending, and managing notifications to user groups.

**Endpoints:**
- `GET /api/v1/notifications/active` (`activeNotificationsData`) – Returns active (non-deleted, non-expired) notifications. Query parameters:
  - `target_group` (string, optional) – Filter by target group (e.g., 'parent', 'teacher', 'admin')
  - Returns array of notifications with metadata including `id`, `title`, `message`, `priority`, `scheduled_at`, `expires_at`, `created_at`, `target_group`, and `isExpired` flag
- `GET /api/v1/notifications/deleted` (`deletedNotificationsData`) – Returns soft-deleted notifications. Returns array of deleted notifications with `id`, `title`, `message`, `priority`, `scheduled_at`, `expires_at`, and `deleted_at` fields.
- `POST /api/v1/notifications/send` (`send`) – Creates and sends notifications to a target group. Body requires:
  - `title` (string, required) – Notification title
  - `message` (string, required) – Notification message content
  - `target_group` (string, required) – Target role/group (e.g., 'parent', 'teacher', 'admin')
  - `channels` (array, required) – Array of delivery channels: `['in_app', 'email', 'sms']`
  - Creates notification record, creates user notification records for all users in the target group, and sends via specified channels (email/SMS). Returns summary with `notification_id`, `users_notified`, `emails_sent`, and `sms_sent` counts.
- `POST /api/v1/notifications/{id}/restore` (`restore`) – Restores a soft-deleted notification by ID. Returns success message with notification ID.

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- The `send` endpoint uses `UserModel::getUsersByRole()` to find users in the target group
- Email sending uses `EmailService` for delivery
- SMS sending is currently logged only (placeholder for future SMS service integration)
- Active notifications are filtered by `scheduled_at <= NOW()` and `(expires_at IS NULL OR expires_at > NOW())`
- Notifications support soft deletes and can be restored

## PageController
**File:** `app/Http/Controllers/Api/PageController.php`  
**Purpose:** Handle marketing-site contact submissions and serve static policy/help content.

**Endpoints:**
- `GET /api/v1/pages/privacy` (`privacyPolicy`) – Returns privacy policy HTML content from `public/html/privacy_policy.html`. Returns JSON response with `content` (HTML string) and `type` fields. Returns error if file is not readable or cannot be loaded.
- `GET /api/v1/pages/terms` (`termsOfService`) – Returns terms of service HTML content from `public/html/terms_of_service.html`. Returns JSON response with `content` (HTML string) and `type` fields. Returns error if file is not readable or cannot be loaded.
- `GET /api/v1/pages/help` (`helpCenter`) – Returns help center HTML content from `public/html/help_center.html`. Returns JSON response with `content` (HTML string) and `type` fields. Returns error if file is not readable or cannot be loaded.
- `POST /api/v1/pages/contact` (`submitContact`) – Submits contact form. Body requires:
  - `email` (string, required, valid email) – Contact email address
  - `message` (string, required, min 10 characters) – Contact message content
  - Saves message to `contactus` table (uses `ContactUsModel` if supported, otherwise falls back to direct DB insert)
  - Sends email notification to `alrahma.isgl@gmail.com` using `EmailService`
  - Returns success response with `email` and `email_sent` (boolean) fields
  - Does not fail the request if email sending fails (logs error instead)

**Notes:**
- All endpoints are public (no authentication required)
- Static HTML files are read from `public/html/` directory
- Contact form attempts to use `ContactUsModel` but falls back to direct DB insert if model structure differs
- Email sending failures are logged but don't cause the request to fail

## ParentAttendanceReportController
**File:** `app/Http/Controllers/Api/ParentAttendanceReportController.php`  
**Purpose:** Allow parents to report lateness/absences for their children.

- `POST /api/v1/parent-attendance-reports` – Body includes `student_ids`, `date`, `type`, optional arrival/dismiss times/reason.
- `GET /api/v1/parent-attendance-reports` – Paginated list filtered by school year.
- `PATCH /api/v1/parent-attendance-reports/{id}` – Parents can edit their own submission fields.

## ParentController
**File:** `app/Http/Controllers/Api/ParentController.php`  
**Purpose:** Backbone for the parent portal (enrollment, emergency contacts, authorized pickups, student CRUD, dashboard metrics, payments view, etc.).

- Examples:
  - `GET /api/v1/parent` – Dashboard snapshot (students, balances, notifications).
  - `GET /api/v1/parent/enrollment-classes` + `POST /api/v1/parent/enrollments` – Manage enrollment/withdrawal requests.
  - Emergency contact + authorized user endpoints for CRUD operations.
  - `POST /api/v1/parent/students` / `PATCH /api/v1/parent/students/{id}` – Mirror the flows covered in `ParentFlowsTest`.
  - Additional helpers (`attendance`, `payments`, `events`, `profile`) expose data to the SPA; authorization checks ensure parents only touch their own records.

## ParticipationController
**File:** `app/Http/Controllers/Api/ParticipationController.php`  
**Purpose:** Manage participation scores for students in class sections.

**Endpoints:**
- `GET /api/v1/participation` (`index`) – Returns participation exam data for a class section. Query parameters:
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via POST or session). If not provided, falls back to session value.
  - Returns array of students with their participation scores, semester, school year, and class section ID. Uses a join with the student roster and participation table. Students are ordered by lastname, then firstname.
- `POST /api/v1/participation` (`update`) – Updates participation scores (teacher endpoint). Body requires:
  - `final_score` (object, required) – Object mapping student IDs to score objects: `{"student_id": {"score": float}}`
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via session)
  - Automatically uses current authenticated user as `updated_by`. Updates or creates participation records and triggers semester score recalculation via `SemesterScoreService::updateScoresForStudents()`.
- `GET /api/v1/participation/management` (`showManagement`) – Returns participation data for management view. Same as `index` but intended for administrative use. Query parameters:
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via POST or session)
- `POST /api/v1/participation/management` (`updateManagement`) – Updates participation scores (management endpoint). Body requires:
  - `final_score` (object, required) – Object mapping student IDs to score objects: `{"student_id": {"score": float}}`
  - `teacher_id` (integer, optional) – Teacher ID to record as updater (defaults to current user)
  - `class_section_id` (integer, optional) – Class section ID (can also be provided via session)
  - Returns updated student roster with scores after successful update. Triggers semester score recalculation.
- `GET /api/v1/participation/student/{id}` (`getByStudent`) – Returns the student's participation entry for the current term.

**Notes:**
- All endpoints require authentication (`auth:api` middleware).
- Class section ID can be provided via query parameter, POST body, or session (checked in that order).
- Scores are automatically scoped to the configured semester and school year.
- Updates trigger `SemesterScoreService::updateScoresForStudents()` to recalculate semester scores.
- The system ensures one score record per student per class section per term (updates existing or creates new).

## PaymentController
**File:** `app/Http/Controllers/Api/PaymentController.php`  
**Purpose:** Comprehensive payment management including CRUD operations, manual payment processing, event charges, and invoice balance management.

**Endpoints:**
- `GET /api/v1/payments` (`index`) – Paginated list of payments with optional filters: `parent_id`, `status`, `page`, `per_page`.
- `GET /api/v1/payments/{id}` (`show`) – Returns a single payment by ID or `404` if not found.
- `GET /api/v1/payments/parent/{id}` (`getByParent`) – Returns all payments for a specific parent with invoice numbers. Includes parent name in response.
- `POST /api/v1/payments` (`store`) – Creates a new payment plan. Body requires:
  - `parent_id` (integer, required)
  - `total_amount` (numeric, required)
  - `payment_date` (date, required)
  - Optional: `number_of_installments`, `payment_type`, `semester`, `school_year`
  - Automatically sets `paid_amount` to 0, `balance_amount` to `total_amount`, and `status` to 'Pending'.
- `PATCH /api/v1/payments/{id}` (`update`) – Updates payment fields. Allowed fields: `paid_amount`, `balance`, `status`, `payment_method`, `payment_date`, `check_number`. Requires authentication.
- `DELETE /api/v1/payments/{id}` (`destroy`) – Removes a payment record.
- `POST /api/v1/payments/{id}/update-balance` (`updateBalance`) – Updates balance after payment. Body requires `paid_amount` (numeric, required).
- `GET /api/v1/payments/enrolled-students/{parentId}` (`getEnrolledStudents`) – Returns enrolled students for a parent with their grades.
- `GET /api/v1/payments/manual-pay-search` (`manualPaySearch`) – Search for parent by email or phone and retrieve payment data. Query param `search_term` (required). Returns:
  - `parent` – Parent user data (excluding password)
  - `students` – Students associated with the parent
  - `payments` – Paginated payment history
  - `invoices` – Invoices with calculated balances (total - paid - discounts - refunds)
  - `today_ymd` – Current date
  - `installment_end_ymd` – Installment deadline from configuration
- `POST /api/v1/payments/manual-pay-update` (`manualPayUpdate`) – Records a manual payment. Body requires:
  - `invoice_id` (integer, required)
  - `amount` (numeric, required, must be > 0)
  - `payment_method` (string, required, one of: `cash`, `check`, `card`)
  - Optional: `check_number` (required if method is `check`), `payment_date`, `payment_file` (file upload for receipt)
  - Card payments must equal the full remaining balance
  - Automatically calculates installment sequence, generates transaction ID, updates invoice balance, and triggers enrollment status update if payment is made
  - Dispatches `paymentReceived` event with payment and student data
- `POST /api/v1/payments/manual-pay-edit` (`manualPayEdit`) – Edits an existing manual payment. Body requires:
  - `payment_id` (integer, required)
  - `paid_amount` (numeric, required, must be > 0)
  - `payment_method` (string, required)
  - Optional: `check_number` (required if method is `check`), `payment_file` (file upload)
  - Validates amount doesn't exceed remaining balance (excluding current payment)
  - Recalculates invoice after update
- `GET /api/v1/payments/event-charges` (`eventChargesShow`) – Returns event charges filtered by `school_year` and `semester` query params. Includes parent and student names. Also returns list of all parents.
- `POST /api/v1/payments/event-charges` (`eventChargesUpdate`) – Creates event charges for students. Body requires:
  - `parent_id` (integer, required)
  - `event_name` (string, required)
  - `amount` (numeric, required)
  - `student_ids` (array, required) OR `student_id` (integer, required)
  - Optional: `description`, `semester`, `school_year`
- `GET /api/v1/payments/redirect-page` (`redirectPage`) – Returns payment redirect page data for authenticated parent. Includes parent name, total amount from latest invoice, school ID, and PayPal mode.
- `GET /api/v1/payments/check-file/{filename}` (`serveCheckFile`) – Serves payment receipt files (checks, cards, misc). Searches in multiple upload directories. Returns file download or inline view.
- `GET /api/v1/payments/check-file/{filename}/inline` – Serves payment file inline (same as above with `mode=inline`).

**Payment Processing Features:**
- Automatic invoice balance recalculation after payments
- Installment sequence tracking
- Transaction ID generation
- Enrollment status auto-update when payment is received
- Event dispatching for `paymentReceived` and `studentEnrolled` events
- Support for cash, check, and card payment methods
- File upload support for payment receipts (JPG, PNG, PDF, max 5MB)
- Discount and refund calculation in balance computation
- Grade-based tuition calculation (regular vs youth students)

**Invoice Balance Calculation:**
- Balance = `total_amount` - `paid_amount` - `discounts` - `refund_paid_amount`
- Automatically recalculates when payments are added/edited
- Considers voided/failed payments
- Handles partial payments and full payments

**Notes:**
- All endpoints require authentication (`auth:api` middleware) except file serving endpoints.
- Payments are automatically scoped to configured school year and semester.
- Manual payment updates trigger invoice recalculation and enrollment status updates.
- Event charges can be created for multiple students at once.
- Payment files are stored in `storage/app/public/uploads/payments/{checks|cards|misc}/`.

## PaymentNotificationController
**File:** `app/Http/Controllers/Api/PaymentNotificationController.php`  
**Purpose:** Manage payment reminder notifications - view logs and send monthly tuition reminders to parents.

**Endpoints:**
- `GET /api/v1/payments/notifications` (`index`) – Returns paginated payment notification logs with optional filters. Query parameters:
  - `page` (integer, optional, default: 1) – Page number for pagination
  - `per_page` (integer, optional, default: 20, max: 100) – Number of records per page
  - `from` (string, optional) – Filter logs sent on or after this date
  - `to` (string, optional) – Filter logs sent on or before this date
  - `type` (string, optional) – Filter by notification type: `no_payment` or `installment`
  - Returns paginated list of notification logs with parent names included
- `POST /api/v1/payments/notifications/send` (`send`) – Sends payment reminder notifications. Body parameters:
  - `parent_id` (integer, optional) – If provided, sends notification to this parent only. If omitted, sends to all parents with positive balance
  - `type` (string, optional) – Notification type: `no_payment` or `installment`. If not provided, automatically detected based on payment history
  - `school_year` (string, optional) – School year to process (defaults to configured school year)
  - `force` (integer, optional, default: 0) – Set to `1` to bypass idempotency check and send even if already sent this month
  - Returns summary with `sent`, `skipped`, `failed` counts and detailed results array

**Notification Behavior:**
- **Single Parent Mode:** When `parent_id` is provided:
  - Sends notification only to that parent (if balance > 0 or `force=1`)
  - Skips if already sent this month/type (unless `force=1`)
  - Skips if balance is 0 (unless `force=1`)
- **Bulk Mode:** When `parent_id` is omitted:
  - Processes all parents with invoices in the selected school year
  - Sends to parents with positive balance (or all if `force=1`)
  - Respects idempotency (1 email per month/type per parent) unless `force=1`
  - Optionally notifies head of finance users in-app

**Email Content:**
- Subject: "Monthly Tuition Reminder — {school_year}"
- Includes:
  - Personalized greeting with parent name
  - Current outstanding balance
  - Remaining installments count
  - Suggested installment amount for current month
  - Maximum installments available
  - Reminder period (month and year)
- Email is sent to primary parent email and CC'd to secondary guardian (if configured and `receive_emails=1`)

**Balance Calculation:**
- Computes total balance across all invoices for parent/school year
- Formula: `total_amount - discounts - paid_amount - refunds`
- Excludes voided, failed, or cancelled payments
- Rounds to 2 decimal places

**Idempotency:**
- Prevents duplicate notifications by checking `payment_notification_logs` table
- One notification per parent per month per type (unless `force=1`)
- Checks `period_year`, `period_month`, `type`, and `parent_id`

**Notification Types:**
- `no_payment` – Parent has no payment history for the school year
- `installment` – Parent has made at least one payment for the school year

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Email sending uses `EmailService` with 'finance' profile
- Notifications are logged in `payment_notification_logs` table with full details
- Secondary guardian emails are automatically included if they have `receive_emails=1`
- Head of finance users are optionally notified in bulk mode (logged for now, can be extended to in-app notifications)

## PaymentTransactionController
**File:** `app/Http/Controllers/Api/PaymentTransactionController.php`  
**Purpose:** Manage payment-processor transactions linked to payments. Handles creation, retrieval, and status updates for payment transactions (installments).

**Endpoints:**
- `GET /api/v1/payment-transactions` (`index`) – Returns paginated list of payment transactions with optional filters. Query parameters:
  - `page` (integer, optional, default: 1) – Page number for pagination
  - `per_page` (integer, optional, default: 20, max: 100) – Number of records per page
  - `payment_id` (integer, optional) – Filter transactions by payment ID
  - `status` (string, optional) – Filter transactions by payment status
  - Returns paginated list of transactions
- `GET /api/v1/payment-transactions/{id}` (`show`) – Returns a single payment transaction by ID. Returns `404` if not found.
- `GET /api/v1/payment-transactions/payment/{paymentId}` (`getByPayment`) – Returns all transactions for a specific payment, ordered by transaction date (newest first). Returns `404` if no transactions found for the payment.
- `POST /api/v1/payment-transactions` (`store`) – Creates a new payment transaction (installment). Body parameters:
  - `transaction_id` (string, required, unique) – Unique transaction identifier
  - `payment_id` (integer, required) – Reference to the payment record
  - `amount` (numeric, required, min: 0) – Transaction amount
  - `payment_method` (string, required, max: 50) – Payment method (e.g., 'cash', 'check', 'card')
  - `transaction_date` (string, optional) – Transaction date in `Y-m-d H:i:s` format (defaults to current date/time)
  - `transaction_fee` (numeric, optional, min: 0) – Transaction fee (defaults to 0)
  - `payment_reference` (string, optional, max: 255) – Payment reference number
  - `is_full_payment` (integer, optional) – Flag indicating full payment: `0` for installment, `1` for full payment (defaults to 0)
  - Automatically sets `payment_status` to 'Pending' and includes current `school_year` and `semester` from configuration
  - Returns `201` with created transaction data
- `PATCH /api/v1/payment-transactions/{transactionId}/status` (`updateStatus`) – Updates the payment status of a transaction. Body parameters:
  - `status` (string, required, max: 50) – New payment status (e.g., 'Pending', 'Completed', 'Failed', 'Refunded')
  - The `transactionId` can be either the numeric `id` or the string `transaction_id`
  - Returns updated transaction data on success, `404` if transaction not found

**Transaction Data:**
- Each transaction is linked to a payment record via `payment_id`
- Transactions track installment payments and full payments via `is_full_payment` flag
- Default status is 'Pending' when created
- Automatically includes `school_year` and `semester` from system configuration
- Supports transaction fees and payment references

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Transaction IDs must be unique across all transactions
- Status updates can be performed using either the numeric ID or the transaction_id string
- Transactions are ordered by `transaction_date` descending when retrieved by payment ID
- The controller uses `PaymentTransactionModel` which provides helper methods like `getTransactionsByPaymentId()` and `updateTransactionStatus()`

## PaypalTransactionsController
**File:** `app/Http/Controllers/Api/PaypalTransactionsController.php`  
**Purpose:** Inspect and export PayPal webhook transaction data stored locally. Provides search, retrieval, and CSV export functionality for PayPal payment transactions.

**Endpoints:**
- `GET /api/v1/paypal-transactions` (`index`) – Returns paginated list of PayPal transactions with optional search. Query parameters:
  - `page` (integer, optional, default: 1) – Page number for pagination
  - `per_page` (integer, optional, default: 20, max: 100) – Number of records per page
  - `q` (string, optional) – Search keyword that searches across multiple fields:
    - `transaction_id` – PayPal transaction identifier
    - `payer_email` – Payer's email address
    - `event_type` – PayPal webhook event type
    - `order_id` – Order identifier
    - `parent_school_id` – Parent school ID
  - Returns paginated list of transactions ordered by creation date (newest first)
- `GET /api/v1/paypal-transactions/{id}` (`show`) – Returns a single PayPal transaction by numeric ID. Returns `404` if not found.
- `GET /api/v1/paypal-transactions/transaction/{transactionId}` (`getByTransactionId`) – Returns a transaction by PayPal transaction ID (string). Returns `404` if not found.
- `GET /api/v1/paypal-transactions/export-csv` (`exportCsv`) – Exports PayPal transactions to CSV file. Query parameters:
  - `q` (string, optional) – Search keyword (same search logic as `index` endpoint)
  - Returns a downloadable CSV file with filename format: `paypal_transactions_YYYYMMDD_HHMMSS.csv`
  - CSV columns: ID, Transaction ID, Order ID, Parent School ID, Email, Amount, Net Amount, Currency, Status, Event Type, Created At
  - Includes all matching transactions (no pagination limit for export)

**Search Functionality:**
- The search keyword (`q` parameter) performs a case-insensitive LIKE search across multiple fields
- Uses OR logic - matches if any field contains the keyword
- Searches in: `transaction_id`, `payer_email`, `event_type`, `order_id`, `parent_school_id`

**CSV Export:**
- Generates CSV file with all matching transactions
- File is streamed directly to the browser for download
- Filename includes timestamp for uniqueness
- Includes all transaction fields relevant for reporting/analysis
- Respects the same search filters as the index endpoint

**Transaction Data:**
- Transactions are stored from PayPal webhook events
- Includes payment details: amount, currency, fees, net amount
- Tracks payer information: email address
- Contains event metadata: event type, status, order ID
- Linked to parent/school via `parent_school_id`
- Includes raw webhook payload for reference

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Transactions are ordered by `created_at` descending (newest first)
- CSV export returns all matching records without pagination
- The controller uses `PayPalPaymentModel` which stores webhook data from PayPal
- Search is case-insensitive and uses SQL LIKE pattern matching

## PaypalWebhook
**File:** `app/Http/Controllers/Api/PaypalWebhook.php`  
**Purpose:** Validate and process PayPal webhook notifications.

- `POST /api/v1/paypal/webhook` – Verifies headers + signature, requests an OAuth token, and persists the webhook payload when verification succeeds.

## PolicyController
**File:** `app/Http/Controllers/Api/PolicyController.php`  
**Purpose:** Serve school and picture policy documents as JSON API responses. Loads policy content from PHP partial files and renders them using Blade views.

**Endpoints:**
- `GET /api/v1/policy` (`index`) – Placeholder endpoint that returns a success response. Can be used to check if the policy API is available.
- `GET /api/v1/policy/picture` (`schoolPicturePolicy`) – Returns the school picture policy content. 
  - Loads content from `resources/views/policy/picture_policy_partial.php` (PHP file that may return data array or output HTML)
  - If the partial returns an array, attempts to render `resources/views/policy/picture_policy.blade.php` view with that data
  - If the partial returns HTML string, uses it directly
  - Returns JSON response with `html` field containing the rendered policy content
  - Returns `404` if the partial file is not found
- `GET /api/v1/policy/school` (`schoolPolicy`) – Returns the main school policy content.
  - Loads metadata from `resources/views/policy/school_policy_partial.php` (PHP file that returns data array)
  - Renders `resources/views/policy/school_policy.blade.php` Blade view with the metadata
  - Returns JSON response with both `meta` (data array) and `html` (rendered content) fields
  - Returns `404` if the partial file is not found
  - Falls back to JSON-encoded meta if the Blade view doesn't exist

**Policy File Structure:**
- Partial files (`.php`) are included and expected to return data arrays or HTML strings
- Blade views (`.blade.php`) are rendered with the data from partial files
- Partial files are located in `resources/views/policy/` directory

**Response Format:**
- All endpoints return JSON with standard API response format: `{status: true, message: "...", data: {...}}`
- Picture policy returns: `{html: "<rendered HTML>"}`
- School policy returns: `{meta: {...}, html: "<rendered HTML>"}`

**Notes:**
- All endpoints are **public** (no authentication required)
- Policy files are read from the filesystem at runtime
- Errors are logged and return appropriate HTTP status codes (404 for not found, 500 for server errors)
- The controller handles both array and string returns from PHP partial files for flexibility

## PreferencesController
**File:** `app/Http/Controllers/Api/PreferencesController.php`  
**Purpose:** Let users adjust notification + UI preferences.

- `GET /api/v1/preferences` – Retrieves or falls back to defaults; includes current `style_color`/`menu_color`.
- `PATCH /api/v1/preferences` – Validates toggles and theme selections, persists them, updates session colors.

## PrintablesReportsController
**File:** `app/Http/Controllers/Api/PrintablesReportsController.php`  
**Purpose:** Provide report-card metadata, badge printing, sticker generation, and student data for printable reports. Handles metadata retrieval, badge print logging, and sticker count calculations.

**Endpoints:**
- `GET /api/v1/printables/report-card-meta` (`reportCardMeta`) – Returns available school years, students, and class sections for report generation. Query parameters:
  - `school_year` (string, optional) – Filter by school year (defaults to configured school year)
  - `semester` (string, optional) – Filter by semester (defaults to configured semester)
  - Returns:
    - `schoolYears` – Array of available school years
    - `selectedYear` – Currently selected school year
    - `semesters` – Array of available semesters for selected year
    - `selectedSemester` – Currently selected semester
    - `students` – Array of students with scores in selected year/semester (or all students if none found)
    - `classSections` – Array of class sections for selected year
- `GET /api/v1/printables/badge-status` (`badgePrintStatus`) – Returns badge print status for given users. Query parameters:
  - `user_ids` (array|string, required) – User IDs (can be comma-separated string or array)
  - `school_year` (string, optional) – Filter by school year
  - Returns object mapping user IDs to print status: `{count, last_printed_at, last_printed_by}`
- `POST /api/v1/printables/badge-status` (`logBadgePrint`) – Logs badge prints for users. Body parameters:
  - `user_ids` (array, required) – Array of user IDs
  - `roles` (array, optional) – Map of user_id => role label
  - `classes` (array, optional) – Map of user_id => class name
  - `school_year` (string, optional) – School year for the prints
  - Returns count of inserted log records
- `GET /api/v1/printables/students-by-class/{classSectionId}` (`getByClass`) – Returns students in a class section. Returns array of students with id, firstname, lastname, registration_grade, and gender.
- `GET /api/v1/printables/students-by-class/{classSectionId}/alt` (`studentsByClass`) – Alternative endpoint for students by class with additional class section name in response.
- `GET /api/v1/printables/preview-sticker-counts` (`previewStickerCounts`) – Returns sticker counts per student. Query parameters:
  - `class_id` (integer, optional) – If provided, returns counts for that class only; otherwise returns for entire school year
  - Returns:
    - `students` – Array of students with `primary_count` and `secondary_count` sticker requirements
    - `totals` – Summary totals for primary, secondary, and student counts
- `POST /api/v1/printables/badge` (`badge`) – Generates staff badge PDF. **Note:** Requires FPDF library integration (not yet implemented).
- `POST /api/v1/printables/generate-stickers` (`generateStickers`) – Generates student name sticker PDF. **Note:** Requires FPDF library integration (not yet implemented).
- `GET /api/v1/printables/report-card/{studentId}` (`generateSingleReport`) – Generates single student report card PDF. **Note:** Requires FPDF library integration (not yet implemented).
- `GET /api/v1/printables/report-card/class/{classSectionId}` (`generateClassReport`) – Generates report cards for all students in a class. **Note:** Requires FPDF library integration (not yet implemented).

**Sticker Count Rules:**
- Special grades: KG (1), 1-A (3), 1-B (4), 2-A (5), 2-B (5), 9 (2)
- Upper block (3-8): New students (4), Grade 5 returning (3), Other returning (2)
- Other grades: Default (4)
- Youth and KG classes are excluded from sticker calculations

**Badge Print Logging:**
- Tracks which users had badges printed, when, and by whom
- Supports role and class metadata for each print
- Can filter by school year
- Used for audit and tracking purposes

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- PDF generation endpoints (`badge`, `generateStickers`, `generateSingleReport`, `generateClassReport`) require FPDF library integration and are currently placeholders
- Sticker counts follow specific rules based on grade level and student status (new vs returning)
- Report card metadata automatically falls back to all students if none found for selected year/semester
- School years are collected from both `classSection` and `semester_scores` tables

## ProjectController
**File:** `app/Http/Controllers/Api/ProjectController.php`  
**Purpose:** Manage project scores for students, similar to homework and participation. Provides endpoints for teachers to add project columns, update scores, and view project data for their class sections.

**Endpoints:**
- `GET /api/v1/projects` (`index`) – List projects with optional filters. Query parameters:
  - `page` (integer, optional) – Page number for pagination (default: 1)
  - `per_page` (integer, optional) – Items per page (default: 20, max: 100)
  - `student_id` (integer, optional) – Filter by student ID
  - `class_section_id` (integer, optional) – Filter by class section ID
  - Returns paginated list of projects for the current semester and school year
- `GET /api/v1/projects/{id}` (`show`) – Get a single project by ID. Returns project details or 404 if not found.
- `GET /api/v1/projects/student/{id}` (`getByStudent`) – Get all projects for a specific student. Returns array of projects ordered by `project_index` for the current semester and school year.
- `POST /api/v1/projects` (`store`) – Create a new project entry. Body parameters:
  - `student_id` (integer, required) – Student ID
  - `class_section_id` (integer, required) – Class section ID
  - `project_index` (integer, required) – Project index/column number
  - `score` (float, optional) – Project score
  - `comment` (string, optional) – Project comment
  - Returns created project with 201 status
- `PATCH /api/v1/projects/{id}` (`update`) – Update an existing project. Body parameters (all optional):
  - `score` (float) – Project score
  - `comment` (string) – Project comment
  - `project_index` (integer) – Project index/column number
  - Returns updated project
- `GET /api/v1/projects/add-project` (`addProject`) – Get project data for teacher's class section. Automatically determines class section from teacher's `teacher_class` record. Returns:
  - `students` – Array of students with their project scores organized by project index
  - `project_headers` – Array of project index values (column headers)
  - `semester` – Current semester
  - `school_year` – Current school year
  - `class_section_id` – Class section ID
- `POST /api/v1/projects/update-scores` (`updateProjectScores`) – Update project scores for multiple students. Body parameters:
  - `scores` (array, required) – Nested array: `{student_id: {project_index: score}}`
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Automatically updates semester scores via `SemesterScoreService` after saving
  - Returns success message
- `POST /api/v1/projects/add-column` (`addNextProjectColumn`) – Add a new project column for all students in a class section. Body parameters:
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Creates a new project index (highest existing + 1) and inserts empty project records for all students
  - Returns:
    - `status` – "success"
    - `project_index` – The new project index that was created
    - `students_processed` – Number of students for whom records were created
- `GET /api/v1/projects/management` (`showProjectMngt`) – Get project management data for a class section. Query/body parameters:
  - `class_section_id` (integer, optional) – Can be provided via POST body, GET query, or session
  - Returns:
    - `project_scores` – Project scores organized by student ID
    - `students` – Array of students in the class section
    - `project_headers` – Array of project index values
    - `semester` – Current semester
    - `school_year` – Current school year
    - `class_section_id` – Class section ID
- `POST /api/v1/projects/update` (`updateProject`) – Update project scores (management endpoint). Body parameters:
  - `scores` (array, required) – Nested array: `{student_id: {project_index: score}}`
  - `student_ids` (array, required) – Array of student IDs
  - `semester` (string, optional) – Semester (defaults to current)
  - `school_year` (string, optional) – School year (defaults to current)
  - `teacher_id` (integer, optional) – Teacher ID (defaults to current user)
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Automatically updates semester scores via `SemesterScoreService` after saving
  - Returns success message

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Project scores are automatically scoped to the current semester and school year from configuration
- When updating scores, the system automatically recalculates semester scores using `SemesterScoreService`
- Project indices are numeric and represent column numbers in the grading interface
- The `add-project` endpoint automatically determines the teacher's class section from the `teacher_class` table
- Session is used to persist `class_section_id` for subsequent requests

## PurchaseOrderController
**File:** `app/Http/Controllers/Api/PurchaseOrderController.php`  
**Purpose:** Create and track purchase orders plus their line items. Handles PO creation, receiving items, and cancellation.

**Endpoints:**
- `GET /api/v1/purchase-orders` (`index`) – Paginated list of purchase orders with keyword search. Query parameters:
  - `page` (integer, optional) – Page number for pagination (default: 1)
  - `per_page` (integer, optional) – Items per page (default: 20, max: 100)
  - `q` (string, optional) – Keyword search for PO number or supplier name
  - Returns paginated list with supplier names joined
- `GET /api/v1/purchase-orders/{id}` (`show`) – Get a single purchase order with all line items. Returns:
  - Purchase order header with supplier name
  - Array of line items with supply names and units (if supply_id maps to inventory_items)
  - Returns 404 if not found
- `POST /api/v1/purchase-orders` (`store`) – Create a new purchase order with line items. Supports both CodeIgniter-style form arrays and modern JSON format. Body parameters:
  - `po_number` (string, required) – Purchase order number
  - `supplier_id` (integer, required) – Supplier ID
  - `order_date` (date, required) – Order date (Y-m-d format)
  - `expected_date` (date, optional) – Expected delivery date
  - `status` (string, optional) – Status: 'draft' or 'ordered' (default: 'ordered')
  - `notes` (string, optional) – Notes
  - `tax` (float, optional) – Tax amount (default: 0.00)
  - **Modern JSON format:**
    - `items` (array, required) – Array of item objects:
      - `supply_id` (integer, required) – Supply/inventory item ID
      - `description` (string, optional) – Item description
      - `quantity` (integer, required) – Quantity ordered
      - `unit_cost` (float, required) – Unit cost
  - **CodeIgniter-style form arrays:**
    - `item_supply_id[]` (array, required) – Array of supply IDs
    - `item_description[]` (array, optional) – Array of descriptions
    - `item_quantity[]` (array, required) – Array of quantities
    - `item_unit_cost[]` (array, required) – Array of unit costs
  - Automatically calculates subtotal, total, and creates all line items in a transaction
  - Returns created purchase order with 201 status
- `PATCH /api/v1/purchase-orders/{id}` (`update`) – Update purchase order fields. Body parameters (all optional):
  - `status` (string) – New status
  - `expected_date` (date) – Expected delivery date
  - `notes` (string) – Notes
  - Returns updated purchase order
- `POST /api/v1/purchase-orders/{id}/receive` (`receive`) – Receive items (partial or full) from a purchase order. Body parameters:
  - `received` (array, required) – Object mapping item IDs to quantities: `{item_id: quantity}`
  - Updates `received_qty` on purchase order items
  - Updates inventory item quantities (if `supply_id` maps to `inventory_items`)
  - Creates inventory movement records for tracking
  - Automatically sets PO status to 'received' if all items are fully received, otherwise keeps 'ordered'
  - Cannot receive items if PO status is 'canceled' or 'received'
  - Returns success message indicating full or partial receipt
- `POST /api/v1/purchase-orders/{id}/cancel` (`cancel`) – Cancel a purchase order. Body parameters: none
  - Sets PO status to 'canceled'
  - Cannot cancel a PO with status 'received'
  - Returns updated purchase order

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Purchase orders support statuses: 'draft', 'ordered', 'received', 'canceled'
- When receiving items, the system automatically updates inventory quantities and creates movement records
- The `store` method supports both legacy CodeIgniter form array format and modern JSON format for backward compatibility
- Line items are validated to ensure at least one valid item with quantity > 0
- All financial calculations (subtotal, tax, total) are performed automatically
- Database transactions ensure data consistency when creating POs or receiving items

## QuizController
**File:** `app/Http/Controllers/Api/QuizController.php`  
**Purpose:** Manage quiz scores for students, similar to homework and projects. Provides endpoints for teachers to add quiz columns, update scores, and view quiz data for their class sections.

**Endpoints:**
- `GET /api/v1/quizzes` (`index`) – List quizzes with optional filters. Query parameters:
  - `page` (integer, optional) – Page number for pagination (default: 1)
  - `per_page` (integer, optional) – Items per page (default: 20, max: 100)
  - `student_id` (integer, optional) – Filter by student ID
  - `class_section_id` (integer, optional) – Filter by class section ID
  - Returns paginated list of quizzes for the current semester and school year
- `GET /api/v1/quizzes/{id}` (`show`) – Get a single quiz by ID. Returns quiz details or 404 if not found.
- `GET /api/v1/quizzes/student/{id}` (`getByStudent`) – Get all quizzes for a specific student. Returns array of quizzes ordered by `quiz_index` for the current semester and school year.
- `POST /api/v1/quizzes` (`store`) – Create a new quiz entry. Body parameters:
  - `student_id` (integer, required) – Student ID
  - `class_section_id` (integer, required) – Class section ID
  - `quiz_index` (integer, required) – Quiz index/column number
  - `score` (float, optional) – Quiz score
  - `comment` (string, optional) – Quiz comment
  - Returns created quiz with 201 status
- `PATCH /api/v1/quizzes/{id}` (`update`) – Update an existing quiz. Body parameters (all optional):
  - `score` (float) – Quiz score
  - `comment` (string) – Quiz comment
  - `quiz_index` (integer) – Quiz index/column number
  - Returns updated quiz
- `GET /api/v1/quizzes/add-quiz` (`addQuiz`) – Get quiz data for teacher's class section. Query/body parameters:
  - `class_section_id` (integer, optional) – Can be provided via POST body, GET query, or session
  - Returns:
    - `students` – Array of students with their quiz scores organized by quiz index
    - `quiz_headers` – Array of quiz index values (column headers)
    - `semester` – Current semester
    - `school_year` – Current school year
    - `class_section_id` – Class section ID
- `POST /api/v1/quizzes/update-scores` (`updateQuizScores`) – Update quiz scores for multiple students. Body parameters:
  - `scores` (array, required) – Nested array: `{student_id: {quiz_index: score}}` or `{student_id: score}` (normalized to quiz_index 1)
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Automatically updates semester scores via `SemesterScoreService` after saving
  - Returns success message
- `POST /api/v1/quizzes/add-column` (`addNextQuizColumn`) – Add a new quiz column for all students in a class section. Body parameters:
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Creates a new quiz index (highest existing + 1) and inserts empty quiz records for all students
  - Returns:
    - `status` – "success"
    - `quiz_index` – The new quiz index that was created
    - `new_quiz_id` – ID of the first inserted quiz record
    - `students_processed` – Number of students for whom records were created
- `GET /api/v1/quizzes/management` (`showQuizMngt`) – Get quiz management data for a class section. Query/body parameters:
  - `class_section_id` (integer, optional) – Can be provided via POST body, GET query, or session
  - Returns:
    - `quiz_scores` – Quiz scores organized by student ID
    - `students` – Array of students in the class section
    - `quiz_headers` – Array of quiz index values
    - `semester` – Current semester
    - `school_year` – Current school year
    - `class_section_id` – Class section ID
- `POST /api/v1/quizzes/update` (`updateQuiz`) – Update quiz scores (management endpoint). Body parameters:
  - `scores` (array, required) – Nested array: `{student_id: {quiz_index: score}}`
  - `student_ids` (array, required) – Array of student IDs
  - `semester` (string, optional) – Semester (defaults to current)
  - `school_year` (string, optional) – School year (defaults to current)
  - `teacher_id` (integer, optional) – Teacher ID (defaults to current user)
  - `class_section_id` (integer, optional) – Class section ID (falls back to session)
  - Automatically updates semester scores via `SemesterScoreService` after saving
  - Returns success message

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Quiz scores are automatically scoped to the current semester and school year from configuration
- When updating scores, the system automatically recalculates semester scores using `SemesterScoreService`
- Quiz indices are numeric and represent column numbers in the grading interface
- The `update-scores` endpoint normalizes single score inputs to quiz_index 1 for backward compatibility
- Session is used to persist `class_section_id` for subsequent requests
- Quiz data is organized by student with scores mapped to quiz indices for easy grid display

## RefundController
**File:** `app/Http/Controllers/Api/RefundController.php`  
**Purpose:** Collect and manage refund requests tied to invoices.

- `GET /api/v1/refunds` / `GET /api/v1/refunds/{id}` – Filter by `parent_id` or `status` when reviewing requests.
- `POST /api/v1/refunds` – Auth-required; body must include `parent_id`, `invoice_id`, `refund_amount`, `reason`.
- `PATCH /api/v1/refunds/{id}` – Update status, payout amounts/methods, or notes; stamps approval/refunded timestamps when applicable.

## RegisterController
**File:** `app/Http/Controllers/Api/RegisterController.php`  
**Purpose:** Public registration portal for parents/guests, including CAPTCHA and confirmation email handling. Handles user registration with validation, role assignment, optional second parent information, and email confirmation.

**Endpoints:**
- `GET /api/v1/register` (`index`) – Generate or retrieve CAPTCHA challenge for registration form. Returns:
  - `captcha_question` – Random CAPTCHA string (4-8 characters) stored in session
  - If CAPTCHA already exists in session, returns existing value
  - Used to prevent automated registrations
- `GET /api/v1/register/success` (`registrationSuccess`) – Get registration success data. Returns:
  - `email` – Email address from registration session
  - Returns 400 error if no registration session exists
  - Used to display confirmation screen after successful registration
- `POST /api/v1/register` (`register`) – Submit registration form. Body parameters:
  - **Required fields:**
    - `firstname` (string, required) – First name (2-30 chars, letters/spaces/hyphens only)
    - `lastname` (string, required) – Last name (2-30 chars, letters/spaces/hyphens only)
    - `gender` (string, required) – Gender: 'Male' or 'Female'
    - `email` (string, required) – Email address (max 50 chars, valid email format)
    - `confirm_email` (string, required) – Must match `email`
    - `cellphone` (string, required) – Phone number (10-20 chars, digits/spaces/hyphens/parentheses/dots)
    - `city` (string, required) – City name (2-30 chars, letters/spaces only)
    - `state` (string, required) – State code: 'CT', 'ME', 'MA', 'NH', 'NY', 'RI', or 'VT'
    - `zip` (string, required) – ZIP code (exactly 5 digits)
    - `accept_school_policy` (boolean/string, required) – Must be accepted/checked
    - `captcha` (string, required) – CAPTCHA answer (4-10 alphanumeric chars, must match session)
  - **Optional fields:**
    - `address_street` (string, optional) – Street address (max 150 chars)
    - `apt` (string, optional) – Apartment/unit number (max 10 chars)
    - `is_parent` (boolean/string, optional) – If '1' or true, registers as parent; otherwise registers as guest
    - `no_second_parent_info` (boolean/string, optional) – If '1' or true, skips second parent information
  - **Second parent fields (required if `is_parent` is true and `no_second_parent_info` is false):**
    - `second_firstname` (string, required) – Second parent first name
    - `second_lastname` (string, required) – Second parent last name
    - `second_gender` (string, required) – Second parent gender: 'Male' or 'Female'
    - `second_email` (string, required) – Second parent email address
    - `second_cellphone` (string, required) – Second parent phone number
  - **Process:**
    1. Validates all input fields
    2. Checks CAPTCHA answer against session
    3. Verifies email is not already registered (checks for pending activation)
    4. Determines role ('parent' or 'guest') based on `is_parent` flag
    5. Generates unique token for email confirmation
    6. Creates user record with formatted data
    7. Assigns role to user
    8. Optionally creates second parent record if applicable
    9. Sends confirmation email with activation link
    10. Stores email in session and clears CAPTCHA
  - **Returns:**
    - Success: `{email: "user@example.com"}` with 200 status
    - Validation errors: 422 with error details
    - Duplicate email: 409 with appropriate message
    - Server errors: 500 with error message

**Notes:**
- All endpoints are **public** (no authentication required)
- CAPTCHA is stored in session and must be answered correctly
- Email addresses are normalized to lowercase
- Phone numbers are formatted using `PhoneFormatterService`
- Names and addresses are formatted (proper case)
- School ID is auto-generated using `SchoolIdService`
- Users are created with `is_verified = 0` and `status = 'Inactive'` until email confirmation
- Activation link format: `/user/confirm/{token}`
- Second parent information is optional for parent registrations
- All database operations are wrapped in transactions for data integrity
- Email confirmation is sent using `EmailService` with different templates for parents vs. guests

## ReimbursementController
**File:** `app/Http/Controllers/Api/ReimbursementController.php`  
**Purpose:** Track reimbursements for staff expenses.

- `GET /api/v1/reimbursements` / `{id}` – Scoped to the active school year/semester; filter by `status` or `reimbursed_to`.
- `POST /api/v1/reimbursements` – Requires `amount`, `reimbursed_to`, `description`, and `expense_id`.
- `PATCH /api/v1/reimbursements/{id}` – Update status/method/check information, stamping approver metadata.

## RFIDController
**File:** `app/Http/Controllers/Api/RFIDController.php`  
**Purpose:** Process RFID scans and expose scan history.

- `POST /api/v1/rfid/process` – Validates the incoming `rfid`, finds the matching user or student, logs to `scan_log`, and returns the resolved entity.
- `GET /api/v1/rfid/logs` – Paginated log with associated user/student names and timestamps.

## RolePermissionController
**File:** `app/Http/Controllers/Api/RolePermissionController.php`  
**Purpose:** Admin tooling for roles, permissions, and assignments. Provides CRUD operations for roles and permissions, role-permission mapping, and user role assignment.

**Endpoints:**
- **Roles:**
  - `GET /api/v1/rolepermission/roles` (`roles`) – List all roles with user counts. Returns array of roles with:
    - `id` – Role ID
    - `name` – Role name
    - `description` – Role description
    - `user_count` – Number of users with this role
  - `GET /api/v1/rolepermission/roles/{id}` (`showRole`) – Get a single role by ID. Returns role details or 404 if not found.
  - `POST /api/v1/rolepermission/roles` (`storeRole`) – Create a new role. Body parameters:
    - `name` (string, required) – Role name (3-255 chars, lowercase stored)
    - `slug` (string, optional) – URL-friendly slug (max 64 chars, auto-generated from name if not provided)
    - `description` (string, optional) – Role description (max 500 chars, lowercase stored)
    - `dashboard_route` (string, required) – Dashboard route path (lowercase, alphanumeric/underscores/slashes/hyphens only)
    - `priority` (integer, required) – Priority value (0-100000)
    - `is_active` (integer, required) – Active status: 0 or 1
    - Returns created role with 201 status
  - `PATCH /api/v1/rolepermission/roles/{id}` (`updateRole`) – Update an existing role. Body parameters (all optional):
    - `name` (string) – Role name
    - `slug` (string) – Role slug
    - `description` (string) – Role description
    - `dashboard_route` (string) – Dashboard route
    - `priority` (integer) – Priority value
    - `is_active` (integer) – Active status
    - Returns updated role
  - `DELETE /api/v1/rolepermission/roles/{id}` (`deleteRole`) – Delete a role. Returns success message or 404 if not found.
- **Permissions:**
  - `GET /api/v1/rolepermission/permissions` (`permissions`) – List all permissions. Returns array of permissions with:
    - `id` – Permission ID
    - `name` – Permission name
    - `description` – Permission description
    - `created_at` – Creation timestamp
    - `updated_at` – Update timestamp
  - `POST /api/v1/rolepermission/permissions` (`storePermission`) – Create a new permission. Body parameters:
    - `name` (string, required) – Permission name
    - `description` (string, optional) – Permission description
    - Returns created permission with 201 status, or existing permission if name already exists
  - `PATCH /api/v1/rolepermission/permissions/{id}` (`updatePermission`) – Update a permission. Body parameters (all optional):
    - `name` (string) – Permission name
    - `description` (string) – Permission description
    - Returns updated permission
  - `DELETE /api/v1/rolepermission/permissions/{id}` (`deletePermission`) – Delete a permission. Returns success message or 404 if not found.
- **Role Permissions:**
  - `GET /api/v1/rolepermission/roles/{roleId}/permissions` (`rolePermissions`) – Get permissions for a specific role. Returns:
    - `role` – Role details
    - `permissions` – All available permissions
    - `role_permissions` – Object mapping permission IDs to CRUD flags: `{permission_id: {can_create, can_read, can_update, can_delete}}`
  - `POST /api/v1/rolepermission/roles/{roleId}/permissions` (`saveRolePermissions`) – Save role permissions. Body parameters:
    - `permissions` (object, required) – Object mapping permission IDs to CRUD flags: `{permission_id: {create: boolean, read: boolean, update: boolean, delete: boolean}}`
    - Updates existing permissions, creates new ones, and removes permissions not in the payload
    - Returns success message
- **User Role Assignment:**
  - `GET /api/v1/rolepermission/users` (`users`) – Get all users with their assigned roles. Returns:
    - `users` – Array of users, each with:
      - `id` – User ID
      - `firstname` – First name
      - `lastname` – Last name
      - `email` – Email address
      - `account_id` – Account ID
      - `roles` – Array of role names assigned to the user
  - `POST /api/v1/rolepermission/users/{userId}/roles` (`assignRole`) – Assign roles to a user. Body parameters:
    - `role_ids` (array, required) – Array of role IDs to assign
    - Replaces all existing roles for the user with the provided roles
    - Automatically updates staff record if user has staff roles
    - Generates unique staff email if needed
    - Returns success message
  - `GET /api/v1/rolepermission/admins` (`admins`) – Get all admin users. Returns array of users with admin role.

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Role names and descriptions are stored in lowercase for consistency
- Slugs are auto-generated from role names if not provided, normalized to lowercase with underscores
- Slug uniqueness is enforced (appends `_2`, `_3`, etc. if duplicates exist)
- When assigning roles to users, the system automatically updates the `staff` table if the user has staff roles (excluding parent/student/guest)
- Staff email addresses are auto-generated using firstname + lastname pattern with uniqueness checks
- Role permissions use CRUD flags (can_create, can_read, can_update, can_delete) for fine-grained access control
- All role/permission assignments are wrapped in database transactions for data integrity
- User role assignments completely replace existing roles (not additive)

## RoleSwitcherController
**File:** `app/Http/Controllers/Api/RoleSwitcherController.php`  
**Purpose:** Allow users with multiple roles to view available roles and switch between them. Provides endpoints to retrieve user roles and switch the active role in the session.

**Endpoints:**
- `GET /api/v1/role-switcher` (`index`) – Get available roles for the current authenticated user. Returns:
  - `roles` – Array of role names assigned to the user
  - `auto_redirect` – Boolean flag indicating if user has only one role (true) or multiple roles (false)
  - `route` – Dashboard route for the role (only present when `auto_redirect` is true)
  - `role` – Role name (only present when `auto_redirect` is true)
  - `current_role` – Currently active role from session (only present when `auto_redirect` is false)
  - If user has only one role, returns `auto_redirect: true` with the route to automatically redirect
  - If user has multiple roles, returns `auto_redirect: false` with all available roles
  - Returns 404 if no roles are assigned to the user
- `POST /api/v1/role-switcher/switch` (`switchRole`) – Switch to a specific role using role name/slug in request body. Body parameters:
  - `role` (string, required) – Role name or slug to switch to
  - Validates that the user has the requested role
  - Sets `active_role` in session
  - Returns:
    - `role` – The role name that was switched to
    - `route` – Dashboard route for the role (from `RoleModel::getRouteByNameOrSlug`)
    - `message` – Success message
  - Returns 403 if user does not have the requested role
- `POST /api/v1/role-switcher/switch/{role}` (`switchTo`) – Switch to a specific role using role name/slug in URL parameter. Same behavior as `switchRole` but accepts role as URL parameter instead of request body.

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Role matching is case-insensitive and works with both role names and slugs
- Dashboard routes are retrieved from the `roles` table using `RoleModel::getRouteByNameOrSlug()`
- Default route is `/landing_page/guest_dashboard` if no route is found for the role
- Session stores the active role as `active_role` for use by other parts of the application
- The controller loads semester and school year from configuration but doesn't use them in the current implementation (available for future use)

## SchoolCalendarController
**File:** `app/Http/Controllers/Api/SchoolCalendarController.php`  
**Purpose:** Manage school calendar events with CRUD operations and audience-specific views. Supports filtering by school year and semester, and provides formatted calendar views for parents, teachers, and administrators.

**Endpoints:**
- `GET /api/v1/school-calendar` (`index`) – Get calendar events filtered by school year and optional semester. Query parameters:
  - `school_year` (optional) – Filter by school year (defaults to configured school year)
  - `semester` (optional) – Filter by semester
  - Returns array of events with `school_year` and `semester` metadata
- `GET /api/v1/school-calendar/{id}` (`show`) – Get a single calendar event by ID. Returns 404 if not found.
- `POST /api/v1/school-calendar` (`store`) – Create a new calendar event. Body parameters:
  - `title` (string, required) – Event title (minimum 3 characters)
  - `start` (string, required) – Event date in Y-m-d format
  - `description` (string, optional) – Event description
  - `notify_parent` (boolean, optional) – Whether to notify parents (default: false)
  - `notify_teacher` (boolean, optional) – Whether to notify teachers (default: false)
  - `notify_admin` (boolean, optional) – Whether to notify admins (default: false)
  - `no_school` (boolean, optional) – Whether this is a no-school day (default: false)
  - `school_year` (string, optional) – School year (defaults to configured school year)
  - `semester` (string, optional) – Semester (defaults to configured semester)
  - Returns created event with 201 status
- `PATCH /api/v1/school-calendar/{id}` (`update`) – Update an existing calendar event. Body parameters (all optional):
  - `title` (string) – Event title (minimum 3 characters if provided)
  - `start` (string) – Event date in Y-m-d format
  - `description` (string) – Event description
  - `notify_parent` (boolean) – Whether to notify parents
  - `notify_teacher` (boolean) – Whether to notify teachers
  - `notify_admin` (boolean) – Whether to notify admins
  - `no_school` (boolean) – Whether this is a no-school day
  - `school_year` (string) – School year
  - `semester` (string) – Semester
  - Returns updated event
- `DELETE /api/v1/school-calendar/{id}` (`delete`) – Delete a calendar event. Returns success message or 404 if not found.
- `GET /api/v1/school-calendar/view/parent` (`calendarParentView`) – Get calendar events formatted for parent view. Returns FullCalendar-compatible format with:
  - Events filtered by audience visibility rules
  - No-school events highlighted in yellow (#ffc107)
  - Extended properties including notification flags and metadata
- `GET /api/v1/school-calendar/view/teacher` (`calendarTeacherView`) – Get calendar events formatted for teacher view. Returns FullCalendar-compatible format with:
  - Events filtered by audience visibility rules
  - No-school events highlighted in yellow (#ffc107)
  - Staff-only events (visible to teachers/admins but not parents) highlighted in green (#28a745)
  - Extended properties including notification flags and metadata
- `GET /api/v1/school-calendar/view/admin` (`calendarAdminView`) – Get calendar events formatted for admin view. Returns FullCalendar-compatible format with:
  - Events filtered by audience visibility rules
  - Staff-only events highlighted in green (#28a745)
  - Extended properties including notification flags and metadata

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Events are filtered by the configured school year by default
- Audience visibility rules:
  - If no notification checkboxes are selected (all `notify_*` = 0), events are visible to everyone
  - If notification checkboxes are selected, events are only visible to the selected audience
  - Events marked as `no_school` are always visible to all audiences
- FullCalendar view endpoints format events with:
  - `id` – Event ID
  - `title` – Event title (with "no school" phrase title-cased if applicable)
  - `start` – Event date (FullCalendar expects 'start' field)
  - `description` – Event description
  - `extendedProps` – Object containing semester, school_year, notification flags, no_school flag, and backgroundColor
- The controller uses the `date` field in the database (not `start_date`)
- School year and semester are loaded from configuration on controller construction

## ScoreCommentController
**File:** `app/Http/Controllers/Api/ScoreCommentController.php`  
**Purpose:** Manage score comments for students across different score types (PTAP, midterm, final). Supports saving comments, updating comments and reviews, and retrieving comments for students or class sections.

**Endpoints:**
- `POST /api/v1/score-comments` (`save`) – Save comments for multiple students. Body parameters:
  - `comments` (object, required) – Object mapping student IDs to score types to comments: `{student_id: {score_type: "comment text"}}`
  - `subject_id` (integer, required) – Subject ID
  - `semester` (string, optional) – Semester (defaults to session semester or configured semester)
  - Score types are normalized to lowercase (ptap, midterm, final)
  - Uses upsert logic: updates existing comments or creates new ones
  - Returns array of saved comments
  - Returns 207 (Partial Content) if some comments failed to save, with `saved` and `errors` arrays
- `POST /api/v1/score-comments/update` (`updateComments`) – Update comments and reviews for multiple students. Body parameters:
  - `comments` (object, required) – Object mapping student IDs to score types to comment text
  - `reviews` (object, optional) – Object mapping student IDs to score types to review text (only processed if user is a reviewer)
  - `semester` (string, optional) – Semester (defaults to configured semester)
  - `school_year` (string, optional) – School year (defaults to configured school year)
  - `teacher_id` (integer, optional) – Teacher ID (defaults to current user ID)
  - Only users with reviewer roles (from `comment_reviewer` config) can update reviews
  - Returns array of updated comments
  - Returns 207 (Partial Content) if some comments failed to update
- `GET /api/v1/score-comments/management` (`viewCommentsMngt`) – Get comments management view data for a class section. Query/body parameters:
  - `class_section_id` (integer, required) – Class section ID (can be in query string, request body, or session)
  - Returns:
    - `students` – Array of students in the class section (id, firstname, lastname, school_id)
    - `comments` – Object mapping student IDs to score types to comment data: `{student_id: {score_type: {comment, comment_review, commented_by, reviewed_by}}}`
    - `semester` – Current semester
    - `school_year` – Current school year
    - `class_section_id` – Class section ID
  - Only loads comments for score types: ptap, midterm, final
  - Stores `class_section_id` in session for future requests
- `GET /api/v1/score-comments/student/{studentId}` (`getByStudent`) – Get comments for a specific student. Query parameters:
  - `subject_id` (integer, optional) – Filter by subject ID
  - `score_type` (string, optional) – Filter by score type
  - Returns comments filtered by current school year and semester
- `PATCH /api/v1/score-comments/{id}` (`update`) – Update a single comment by ID. Body parameters:
  - `comment` (string, required) – Updated comment text
  - Returns updated comment

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Comments are scoped to school year and semester
- Score types are normalized to lowercase: 'ptap', 'midterm', 'final'
- Reviewer functionality:
  - Reviewer roles are configured in the `configuration` table with key `comment_reviewer`
  - Value should be a comma-separated list of role names (e.g., "administrator,head_of_department")
  - Only reviewers can update `comment_review` and `reviewed_by` fields
  - Reviewer check is case-insensitive
- The `save()` method uses semester from session if available, otherwise falls back to configured semester
- Comments use upsert logic: if a comment exists for the same student/subject/score_type/semester/school_year combination, it's updated; otherwise, a new record is created
- Empty comment strings are skipped during save operations
- The management endpoint returns students ordered by lastname, then firstname

## ScoreController
**File:** `app/Http/Controllers/Api/ScoreController.php`  
**Purpose:** Manage student scores for teachers and parents. Provides comprehensive score calculation, retrieval, and update functionality for homework, quizzes, projects, midterm exams, final exams, attendance, and semester scores.

**Endpoints:**
- `GET /api/v1/scores` (`index`) – Get student scores for teacher's class section. Returns:
  - `students` – Array of students with all their scores (homework, quiz, project, midterm, final_exam, attendance, ptap, semester_score)
  - `teacher_name` – Name of the teacher
  - `class_section_id` – Class section ID
  - `class_section_name` – Name of the class section
  - `mains_text` – Main teachers assigned to the section
  - `tas_text` – Teaching assistants assigned to the section
  - `active_semester` – Active semester (fall/spring)
  - Automatically refreshes semester scores via SemesterScoreService
  - Students are sorted by lastname, then firstname
  - Returns 404 if teacher has no assigned class
- `GET /api/v1/scores/student/{studentId}` (`getByStudent`) – Get scores for a specific student. Returns semester scores filtered by current school year and semester.
- `GET /api/v1/scores/parent-view` (`viewStudentScore`) – Get student scores for parent view. Query parameters:
  - `school_year` (optional) – Filter by school year (defaults to configured school year)
  - Returns:
    - `organized_scores` – Scores organized by school year, semester, and student ID
    - `school_years` – Available school years from semester_scores
    - `selected_year` – Selected school year
    - `selected_semester` – Selected semester
  - Handles primary, secondary, and tertiary parent user types
  - Returns scores for all students linked to the parent
- `POST /api/v1/scores/update` (`updateScores`) – Generic method to update scores for various types. Body parameters:
  - `table` (string, required) – Table type: 'final_exam', 'midterm_exam', 'quiz', etc.
  - `final_score` (object, required) – Object mapping student IDs to score data: `{student_id: {score: float, quiz_index?: int}}`
  - `class_section_id` (integer, optional) – Class section ID (defaults to session value)
  - Uses upsert logic: updates existing records or creates new ones
  - Automatically triggers semester score recalculation for Spring final exams
  - Returns count of updated students
- `POST /api/v1/scores/update-final-exam` (`updateFinalExamScores`) – Update final exam scores. Same as `updateScores` with table='final_exam'.
- `POST /api/v1/scores/update-midterm` (`updateMidtermScores`) – Update midterm exam scores. Same as `updateScores` with table='midterm_exam'.
- `POST /api/v1/scores/recalculate` (`recalculate`) – Recalculate scores for students. Body parameters:
  - `class_section_id` (integer, optional) – Class section ID (if provided, fetches all students in that section)
  - `students` (array, optional) – Array of student data with student_id, school_id, class_section_id
  - `semester` (string, optional) – Semester (defaults to configured semester)
  - `school_year` (string, optional) – School year (defaults to configured school year)
  - Uses SemesterScoreService to recalculate scores
  - Returns count of processed students

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Score calculation formula:
  - PTAP = (homework_avg + quiz_avg + project_avg) / 3
  - Attendance = min((total_days - absences + 1) / total_days * 100, 100)
  - Fall semester: semester_score = 0.2 * PTAP + 0.2 * attendance + 0.6 * midterm
  - Spring semester: semester_score = 0.2 * PTAP + 0.2 * attendance + 0.6 * final_exam
- The controller automatically refreshes semester scores when loading teacher view
- Class section resolution:
  - First tries to find class section for active semester/school year
  - Falls back to any class section assigned to the teacher if not found
- Parent view handles three user types:
  - Primary: uses user_id directly
  - Secondary: looks up parent_id from parents table
  - Tertiary: looks up parent_id from authorized_users table
- Score types supported: homework, quiz, project, midterm, final_exam
- Comments are loaded separately for ptap, midterm, and final score types
- Semester scores are loaded from `semester_scores` table and override calculated values if present
- Total semester days are retrieved from configuration: `total_semester1_days` (Fall) or `total_semester2_days` (Spring)

## ScorePredictorController
**File:** `app/Http/Controllers/Api/ScorePredictorController.php`  
**Purpose:** Generate combined score prediction reports with statistical analysis, trophy winner determination, and risk assessment for students based on fall and spring semester scores.

**Endpoints:**
- `GET /api/v1/score-predictor/combined-report` (`combinedReport`) – Generate combined score prediction report. Query parameters:
  - `school_year` (optional) – Filter by school year (defaults to configured school year)
  - `class_section_id` (optional) – Filter by class section ID
  - Returns:
    - `students` – Array of students with predictions, each containing:
      - `student_id` – Student ID
      - `school_id` – School ID
      - `firstname` – First name
      - `lastname` – Last name
      - `class_section_id` – Class section ID
      - `fall_score` – Fall semester score or 'N/A'
      - `spring_score` – Spring semester score or 'N/A'
      - `final_average` – Final average (fall + spring) / 2, rounded to 1 decimal, or 'N/A'
      - `required_spring_score` – Required spring score to achieve trophy threshold, or 'N/A'
      - `winning_risk` – Trophy winning risk: 'Achieved Trophy', 'Unreachable', 'New student', 'Low', 'Medium', 'High'
      - `required_spring_to_pass` – Required spring score to pass, or 'N/A'
      - `failure_risk` – Failure risk: 'Unlikely to pass', 'New student', 'High', 'Medium', 'Low'
      - `status` – Overall status: 'Passed', 'Failed', 'Undetermined'
    - `school_year` – Selected school year
    - `class_sections` – All available class sections
    - `selected_class_section_id` – Selected class section ID (if filtered)
    - `semester` – Current semester
    - `stats` – Statistical data:
      - `mean` – Mean of spring semester scores
      - `std` – Standard deviation of spring semester scores
      - `target_trophy` – Trophy threshold from configuration
      - `target_pass` – Pass threshold from configuration
  - Students are sorted by final average (descending)
  - Trophy winners are determined per class section using:
    1. All students at/above trophy threshold (minimum 94.0 or configured value)
    2. Minimum trophies: max(ceil(class_size / 4), 3), capped at class size
    3. Tolerance tie-break: students within 0.2 points of last included winner

**Notes:**
- All endpoints require authentication (`auth:api` middleware)
- Trophy threshold: minimum 94.0, or higher if configured in `trophy_score`
- Pass threshold: from `pass_score` configuration (default 60.0)
- Statistical calculations:
  - Uses normal cumulative distribution function (normalCDF) for probability calculations
  - Calculates z-scores based on mean and standard deviation of spring scores
  - Probability thresholds for risk assessment:
    - High risk: probability < 0.33
    - Medium risk: 0.33 <= probability < 0.66
    - Low risk: probability >= 0.66
- Trophy winner determination:
  - Per-class section basis
  - Minimum 3 trophies per class (or 25% of class size, whichever is higher)
  - Tolerance of 0.2 points for tie-breaking
  - Students with final average >= trophy threshold are automatically included
- New students (no fall score) have 'N/A' for calculated values and 'New student' risk labels
- Final average calculation: (fall_score + spring_score) / 2, rounded to 1 decimal
- Required spring scores: calculated to achieve trophy threshold or pass threshold
- The report uses spring semester statistics for probability calculations

## SendSMSController
**File:** `app/Http/Controllers/Api/SendSMSController.php`  
**Purpose:** Trigger SMS blasts.

- `POST /api/v1/sms/send` – Validates `message` and recipients (phone numbers or parent IDs), queues the SMS, and returns the send summary.

## SessionTimeoutController
**File:** `app/Http/Controllers/Api/SessionTimeoutController.php`  
**Purpose:** Manage session timeout tracking and provide keep-alive functionality for the SPA to prevent unexpected logouts due to inactivity.

**Endpoints:**
- `GET /api/v1/session/timeout-config` (`getTimeoutConfig`) – Public endpoint that returns session timeout configuration including timeout duration, warning threshold, check interval, and related URLs (logout, keep-alive, check). Response includes `success`, `timeout`, `warning_time`, `check_interval`, `logout_url`, `keep_alive_url`, and `check_url`.
- `GET /api/v1/session/checkTimeout` (`checkTimeout`) – Requires auth. Checks the current session's last activity timestamp against the configured timeout duration. Returns `status` (`active`, `warning`, or `expired`) and `time_remaining` in seconds. If session has expired, returns `status: 'expired'` with `redirect` URL and `message`.
- `POST /api/v1/session/pingActivity` (`pingActivity`) – Requires auth. Updates the session's `last_activity` timestamp to keep the session alive. Only updates if the session is still valid (not already expired). Returns `status: 'active'` and `time_remaining` set to the full timeout duration. If session has already expired, returns the same expired response as `checkTimeout`.

**Session Management:**
- Tracks user activity via `last_activity` session key
- Automatically expires sessions that exceed `SessionTimeout::TIMEOUT_DURATION` (default: 1200 seconds / 20 minutes)
- Warns users when activity exceeds `SessionTimeout::WARNING_THRESHOLD` (default: 900 seconds / 15 minutes)
- Frontend should check session status at intervals defined by `SessionTimeout::CHECK_INTERVAL` (default: 60 seconds / 1 minute)


## SettingsController
**File:** `app/Http/Controllers/Api/SettingsController.php`  
**Purpose:** Read/update global settings.

- `GET /api/v1/settings` – Returns grouped configuration values.
- `PATCH /api/v1/settings` – Accepts a key/value object and persists changes after validation.

## SlipPrinterController
**File:** `app/Http/Controllers/Api/SlipPrinterController.php`  
**Purpose:** Generate printable late slips as PDFs, manage slip previews, and handle reprinting of previously logged slips.

**Endpoints:**
- `POST /api/v1/slips/print` (`print`) – Requires auth. Generates and returns a PDF late slip. Accepts POST data with fields: `school_year` (optional, defaults to configured year), `student_name` (required), `date` (optional, defaults to current date in m/d/Y format), `time_in` (optional, defaults to current time in h:i A format), `grade`, `reason`, `admin_name` (optional, auto-filled from logged-in user). Automatically logs the slip print to the database before generating the PDF. Returns PDF binary with appropriate headers for inline display or download. Supports optional query parameters: `paper` (card, card-portrait, receipt80, receipt58), `font_pt`, `line_h`, `rows`, `height_mm` for PDF customization.
- `GET /api/v1/slips/preview` (`preview`) – Requires auth. Returns a list of recent late slip logs with optional filtering by `school_year` and `semester` query parameters. Returns paginated list (up to 50 records) with formatted date/time fields. Each record includes: `id`, `school_year`, `semester`, `student_name`, `date`, `time_in`, `grade`, `reason`, `admin_name`.
- `POST /api/v1/slips/preview` (`preview`) – Requires auth. Returns a JSON preview of slip text formatted for printer output. Accepts the same fields as the print endpoint. Returns `ok`, `text` (formatted slip text), and `width` (characters per line) for preview purposes.
- `POST /api/v1/slips/reprint/{id}` (`reprint`) – Requires auth. Reprints a previously logged late slip by its log ID. Retrieves the slip data from the database, updates the admin name to the current logged-in user, generates a new PDF, and logs the reprint for audit purposes. Returns PDF binary response.

**Features:**
- PDF generation using DomPDF with customizable paper sizes (card, receipt formats)
- Automatic logging of all slip prints and reprints for audit trail
- ESC/POS printer support (network, USB, Windows) via Mike42\Escpos library (configured via environment variables)
- Automatic admin name resolution from logged-in user's database record
- Flexible date/time parsing and formatting
- Configurable printer settings via environment: `PRINTER_MODE`, `PRINTER_HOST`, `PRINTER_PORT`, `PRINTER_USB_VID`, `PRINTER_USB_PID`, `PRINTER_WINDOWS_NAME`, `PRINTER_CHARS_PER_LINE`, `PRINTER_FEED_LINES`

## StaffController
**File:** `app/Http/Controllers/Api/StaffController.php`  
**Purpose:** CRUD for staff/teacher records with class assignment verification and issue tracking.

**Endpoints:**
- `GET /api/v1/staff` (`index`) – Requires auth. Returns paginated list of staff members excluding roles: student, parent, guest, inactive. Optional query parameters: `page`, `per_page`, `role` (filter by role). Each staff member includes: `school_id`, `class_section` (for teachers/TAs), `verification_issue` (boolean flag for teachers/TAs without class assignments). Response includes `issues_count` (total count of teachers/TAs without class assignments), `semester`, and `school_year` metadata.
- `GET /api/v1/staff/{id}` (`show`) – Requires auth. Returns a single staff member by ID with `school_id` attached.
- `POST /api/v1/staff` (`store`) – Requires auth. Creates a new staff member. Accepts fields: `firstname`, `lastname`, `email`, `phone`, `role_name` (automatically sets `active_role` to lowercase version), `school_year` (optional, defaults to configured year), `status` (optional, defaults to 'Active'). Automatically sets `created_at` and `updated_at` timestamps.
- `PATCH /api/v1/staff/{id}` (`update`) – Requires auth. Updates a staff member. Accepts any of: `firstname`, `lastname`, `email`, `phone`, `role_name` (updates `active_role` if provided), `school_year`, `status`. Only updates fields that are provided and non-empty. Automatically updates `updated_at` timestamp.
- `DELETE /api/v1/staff/{id}` (`destroy`) – Requires auth. Deletes a staff member by ID.

**Features:**
- Automatic class assignment verification for teachers and teacher assistants
- Issue tracking for teachers/TAs without class assignments
- School ID resolution from user records
- Role-based filtering and exclusion

## StatsController
**File:** `app/Http/Controllers/Api/StatsController.php`  
**Purpose:** Aggregate metrics for dashboards (attendance, enrollment, finances).

- Endpoints like `GET /api/v1/stats/attendance|students|financial` query reporting tables and return summary numbers + breakdowns for the admin UI.

## StudentController
**File:** `app/Http/Controllers/Api/StudentController.php`  
**Purpose:** Full REST API for managing students, class assignments, health information, and automated distribution.

**Endpoints:**
- `GET /api/v1/students` (`index`) – Requires auth. Supports pagination and filters (`parent_id`, `class_id`). Augments each record with the student's current class info. Returns paginated list of students.
- `GET /api/v1/students/{id}` (`show`) – Requires auth. Returns detailed student profile plus current class section information.
- `POST /api/v1/students` (`store`) – Requires auth. Creates a new student. Validates demographic fields (firstname, lastname, dob, gender, parent_id) and automatically stamps school year, semester, and registration date.
- `PATCH /api/v1/students/{id}` (`update`) – Requires auth. Updates student information. Accepts fields: `school_id`, `firstname`, `lastname`, `dob` (automatically calculates age), `gender`, `registration_grade`, `photo_consent`, `parent_id`, `registration_date`, `tuition_paid`, `year_of_registration`, `school_year`, `rfid_tag`, `semester`, `is_new`. Also supports health information: `medical_conditions` (comma/semicolon/newline separated list), `allergies` (comma/semicolon/newline separated list), `medical_touched`, `allergies_touched` (flags to indicate user interaction). Health lists are normalized, deduplicated, and synced with related tables. Only updates fields that are provided and non-empty.
- `DELETE /api/v1/students/{id}` (`destroy`) – Requires auth. Removes the student record.
- `POST /api/v1/students/assign-class` (`assignClassStudent`) – Requires auth. Assigns a student to a class section. Accepts `student_id` and `class_section_id`. Updates `student_class` table, enrollment records, attendance data/records, and all score-related tables (homework, quiz, project, participation, midterm_exam, final_exam, final_score, semester_scores) for the current semester/year. Returns assignment details including attendance and score update statistics.
- `POST /api/v1/students/auto-distribute` (`autoDistributeSections`) – Requires auth. Automatically distributes students from promotion queue into lettered sections for a class. Accepts `class_id` (or `class_section_id` to derive class_id), `students_per_section`, and optional `school_year`. Balances male/female distribution across sections. Only considers students with enrollment status 'payment pending' or 'enrolled'. Updates promotion queue and student_class records. Returns summary with distribution details per section (total, male, female counts).
- `GET /api/v1/students/promotion-totals` (`promotionTotalsApi`) – Requires auth. Returns promotion totals per base class (KG, 1-9, Youth) for the selected school year. Optional query parameter: `school_year` (defaults to configured year). Only counts students with enrollment status 'payment pending' or 'enrolled'. Returns array with class_id, class_section_id, class_section_name, and total count per class.

**Features:**
- Comprehensive student data management with health information (allergies, medical conditions)
- Automatic class assignment with cascading updates to attendance and score tables
- Gender-balanced automated section distribution
- Promotion queue integration
- Health list normalization (removes duplicates, filters placeholders like "none", "n/a")
- Transaction-safe operations with rollback on errors

## SupplierController
**File:** `app/Http/Controllers/Api/SupplierController.php`  
**Purpose:** CRUD for supplier/vendor records used by purchase orders and inventory management.

**Endpoints:**
- `GET /api/v1/suppliers` (`index`) – Requires auth. Returns paginated list of suppliers. Optional query parameters: `page`, `per_page`, `q` (search keyword). Search filters by name, email, or phone. Results ordered by name ascending.
- `GET /api/v1/suppliers/{id}` (`show`) – Requires auth. Returns a single supplier by ID.
- `POST /api/v1/suppliers` (`store`) – Requires auth. Creates a new supplier. Accepts fields: `name` (required), `email` (optional, validated as email), `phone` (optional), `address` (optional), `notes` (optional). Returns created supplier record.
- `PATCH /api/v1/suppliers/{id}` (`update`) – Requires auth. Updates a supplier. Accepts any of: `name`, `email`, `phone`, `address`, `notes`. Only updates fields that are provided. Returns updated supplier record.
- `DELETE /api/v1/suppliers/{id}` (`destroy`) – Requires auth. Deletes a supplier by ID.

**Features:**
- Search functionality across name, email, and phone fields
- Standard RESTful CRUD operations
- Validation for required fields and email format


## SupportController
**File:** `app/Http/Controllers/Api/SupportController.php`  
**Purpose:** Ticket management for internal support.

- `GET /api/v1/support/tickets` / `{id}` – List or fetch support tickets.
- `POST /api/v1/support/tickets` / `PATCH /api/v1/support/tickets/{id}` – Create/update ticket records with status notes.

## TeacherController
**File:** `app/Http/Controllers/Api/TeacherController.php`  
**Purpose:** Manage teacher records, class assignments, class view data, and absence/vacation requests.

**Endpoints:**
- `GET /api/v1/teachers` (`index`) – Requires auth. Returns paginated list of teachers and teacher assistants. Optional query parameters: `page`, `per_page`. Results include class assignments for the current school year and semester.
- `GET /api/v1/teachers/{id}` (`show`) – Requires auth. Returns detailed information about a specific teacher including their class assignments for the current term.
- `GET /api/v1/teachers/{id}/classes` (`getClasses`) – Requires auth. Returns the class sections currently assigned to the teacher for the current school year and semester.
- `GET /api/v1/teachers/class-view` (`getClassView`) – Requires auth. Returns comprehensive class view data for the logged-in teacher including:
  - Teacher name and role
  - All assigned classes (as main teacher or TA)
  - Students grouped by class section with medical conditions and allergies
  - Assigned staff names (main teachers and TAs) per class section
- `GET /api/v1/teachers/class-assignments` (`getTeacherClassAssignments`) – Requires auth. Returns teacher class assignment data for administration. Optional query parameter: `schoolYear` or `school_year` to filter by specific year. Returns:
  - List of all teachers and TAs
  - Their main and TA assignments
  - Available class sections
- `POST /api/v1/teachers/class-assignments/assign` (`assignClassTeacher`) – Requires auth. Assigns a teacher to a class. Body requires:
  - `teacher_id` (required) – ID of the teacher to assign
  - `class_section_id` (required) – ID of the class section
  - `teacher_role` (optional) – Role of the teacher (if not provided, will be determined from user roles)
  - Optional: `semester`, `school_year` (defaults to configured values)
  - Returns success/error status with message
- `DELETE /api/v1/teachers/class-assignments` (`deleteAssignment`) – Requires auth. Removes a teacher class assignment. Body requires:
  - `teacher_id` (required)
  - `class_section_id` (required)
  - `position` (required) – Either "main" or "ta"
  - Optional: `semester`, `school_year` (defaults to configured values)
- `GET /api/v1/teachers/absence-form` (`getAbsenceForm`) – Requires auth. Returns absence form data for the logged-in teacher including:
  - Teacher name
  - Current semester and school year
  - Existing absence records for the current term
  - Available dates (future Sundays within the school year range)
- `POST /api/v1/teachers/absence` (`submitAbsence`) – Requires auth. Submits an absence/vacation request. Body requires:
  - `dates` (required) – Array of date strings in Y-m-d format (must be valid Sundays)
  - `reason` (required) – Text description of the absence reason
  - `reason_type` (optional) – Category of the reason
  - Validates dates against allowed Sundays list, creates/updates staff attendance records, and sends notification email to principal. Returns count of saved dates.

## TestDBController
**File:** `app/Http/Controllers/Api/TestDBController.php`  
**Purpose:** Simple health endpoint for confirming DB connectivity/migrations.

- `GET /api/v1/test-db` – Returns the status of DB checks used in diagnostics.

## TimeController
**File:** `app/Http/Controllers/Api/TimeController.php`  
**Purpose:** Share the server’s current timestamp/timezone for client sync logic.

- `GET /api/v1/time` – Responds with ISO timestamp and timezone metadata.

## UiController
**File:** `app/Http/Controllers/Api/UiController.php`  
**Purpose:** Store and return UI palette preferences that drive the SPA theme.

**Endpoints:**
- `GET /api/v1/ui/style` (`style`) – Requires auth. Returns the user's current accent/menu colors plus the available palettes defined in config. Can also accept query parameters to update style preferences:
  - `accent` (optional) – Accent color palette name from available palettes
  - `menu` (optional) – Menu color palette name from available palettes, or `custom` for custom colors
  - `menu_bg` (optional) – Custom menu background color in hex format (e.g., `#0f172a` or `0f172a`)
  - `menu_text` (optional) – Custom menu text color in hex format (e.g., `#ffffff` or `ffffff`)
  - `menu_mode` (optional) – Menu mode: `light`, `dark`, or `auto` (defaults to auto-detect based on background luminance)
  - When query parameters are provided, updates the style preferences and returns the updated values. When no parameters are provided, returns current preferences.
- `POST /api/v1/ui/style` (`updateStyle`) – Requires auth. Accepts JSON body with style preferences:
  - `accent` (optional) – Accent color palette name
  - `menu` (optional) – Menu color palette name or `custom`
  - `menu_bg` (optional) – Custom menu background color in hex format
  - `menu_text` (optional) – Custom menu text color in hex format
  - `menu_mode` (optional) – Menu mode: `light`, `dark`, or `auto`
  - Persists the preferences in the session and returns the updated values.

**Features:**
- Supports both predefined palettes and custom hex colors
- Automatically detects light/dark mode based on background color luminance when `menu_mode` is `auto`
- Normalizes hex color formats (supports both 3-digit and 6-digit hex codes)
- All preferences are stored in session and persist across requests

## UserController
**File:** `app/Http/Controllers/Api/UserController.php`  
**Purpose:** Manage user accounts, profile data, login activity, password reset/activation flows, and role management.

**Endpoints:**
- `GET /api/v1/users` (`index`) – Auth required. Returns users with optional `sort=id|firstname|lastname|email|role` and `order` query params plus the role catalog for dropdowns.
- `GET /api/v1/users/with-roles` (`listWithRoles`) – Auth required. Expands every user with their roles, guardian type, and whether they exist as a secondary parent in legacy tables.
- `POST /api/v1/users` (`store`) – Auth required. Creates a user with PBKDF2 password hashing and immediately assigns the provided `role_id`. Body requires: `firstname`, `lastname`, `email`, `password`, `cellphone`, `address_street`, `city`, `state`, `zip`, `accept_school_policy`, `role_id`.
- `PATCH /api/v1/users/{id}` (`updateUser`) – Auth required. Updates the account/profile metadata fields used by the admin UI (status flags, address, contact info, tokens, etc.). Accepts partial updates for any user field.
- `DELETE /api/v1/users/{id}` (`destroy`) – Auth required. Removes the user and associated `user_roles` rows.
- `GET /api/v1/users/login-activity` (`loginActivity`) – Auth required. Paginated login history with metadata and pager info. Query params: `per_page` (default 25), `page` (default 1).
- `POST /api/v1/users/select-role` (`selectRole`) – Auth required. Selects and updates the logged-in user's role in the database. Body requires: `role` (role name or slug). Returns the role and dashboard route.
- `DELETE /api/v1/users/roles/{roleId}` (`deleteRole`) – Auth required. Deletes a role and reassigns all users with that role to the "Guest" role. Users are set to Inactive status.
- `POST /api/v1/users/password/forgot` (`requestPasswordReset`) – Public. Throttles by IP (max 3 attempts per 24 hours), verifies account status, creates a one-hour reset token, and emails the link. Body requires: `email`.
- `GET /api/v1/users/password/reset/{token}` (`validateResetToken`) – Public. Validates whether the token is still usable and returns the associated email.
- `POST /api/v1/users/password/reset` (`completePasswordReset`) – Public. Consumes the token, hashes the new password, clears lockouts, and deletes the reset entry. Body requires: `token`, `password`, `password_confirmation`. Password must meet complexity requirements.
- `GET /api/v1/user/confirm/{token}` (`confirm`) – Public. Validates the registration token so the UI can prompt for an initial password. Returns user_id and email if valid.
- `POST /api/v1/user/set-password` (`setPassword`) – Public. Finalizes onboarding by setting the password, activating the user, and clearing the token. Body requires: `user_id`, `token`, `password`, `password_confirm`. Generates account_id if not present.
- `GET /api/v1/user/profile` (`profile`) – Auth required. Returns the signed-in user's details with any attached roles.
- `POST /api/v1/user/profile-auth` (`profileAuth`) – Public. PBKDF2 credential check for legacy clients that need a session+JWT payload. Body requires: `email`, `password`.
- `PATCH /api/v1/user/profile` (`updateProfile`) – Auth required. Updates contact info and optionally email/password after verifying the current password. Body can include: `firstname`, `lastname`, `cellphone`, `address_street`, `apt`, `city`, `state`, `zip`, `gender`, `email` (requires `current_password`), `password` (requires `current_password`).

## WhatsappController
**File:** `app/Http/Controllers/Api/WhatsappController.php`  
**Purpose:** Manage WhatsApp invite links per class section, send invites to parents, and track parent participation.

### Endpoints:

- `GET /api/v1/whatsapp/` (`index`) – Get initial data for WhatsApp management page. Returns sections, parents, links by section, school year, and semester.

- `GET /api/v1/whatsapp/links` (`links`) – Lists links for the active school year/semester sorted by class section.

- `POST /api/v1/whatsapp/links` (`saveLink`) – Upserts the invite link for a class section. Body requires:
  - `class_section_id` (integer, required)
  - `invite_link` (string, required)
  - `active` (integer, optional: 0 or 1)
  - `class_section_name` (string, optional)

- `POST /api/v1/whatsapp/send-invites` (`sendInvites`) – Send WhatsApp invites in three modes. Body requires:
  - `mode` (string, required: 'parents' | 'class' | 'all')
  - `parent_ids` (array of integers, required if mode='parents')
  - `class_section_id` (integer, required if mode='class')
  
  Modes:
  - `parents`: Send invites to selected parents (requires `parent_ids` array)
  - `class`: Send invites to all parents in a specific class (requires `class_section_id`)
  - `all`: Send invites to all parents across all classes
  
  Triggers `whatsapp_invites.send` event for each recipient group with parent info, sections, links, teachers, and email addresses (TO and CC).

- `GET /api/v1/whatsapp/parent-contacts` (`parentContacts`) – Get a flat list of all parent contacts (primary and secondary parents) for the current term. Optional query parameters:
  - `school_year` (string)
  - `semester` (string)
  
  Returns contacts sorted by last name, first name, and type (Primary before Second).

- `GET /api/v1/whatsapp/parent-contacts-by-class` (`parentContactsByClass`) – Get parent contacts grouped by class section. Optional query parameters:
  - `school_year` (string)
  - `semester` (string)
  
  Returns class sections with associated parents (primary and secondary), teachers (main and assistants), and membership flags indicating if parents have joined the WhatsApp groups.

- `POST /api/v1/whatsapp/memberships` (`updateMembership`) – Update WhatsApp group membership flags for a class/parent(s). Body requires:
  - `class_section_id` (integer, required)
  - `primary_id` (integer, optional)
  - `second_id` (integer, optional)
  - `primary_member` (string/integer, optional: '1' or 'on' if checked)
  - `second_member` (string/integer, optional: '1' or 'on' if checked)
  - `school_year` (string, optional)
  - `semester` (string, optional)
  
  Updates membership status for primary and/or secondary parents in a class section. Uses `subject_type` ('primary' or 'second') and `subject_id` to track membership.

## GradingController
**File:** `app/Http/Controllers/Api/GradingController.php`  
**Purpose:** Unified interface for CRUD operations across scores (homework, quizzes, projects, midterms, finals, semester tests, comments) and comprehensive grading management.

**Endpoints:**
- `GET /api/v1/grading/{type}/{class_section_id?}/{student_id?}` (`show`) – Retrieves scores for a specific type. `type` must be one of `homework|quiz|project|midterm|final|test|comments`. Optional `class_section_id` and `student_id` parameters. Filters by student, semester, and school year. Returns student information, scores array, type, class section ID, semester, and school year.
- `POST /api/v1/grading` (`update`) – Updates scores for various types. Body requires:
  - `type` (required) – One of: `homework`, `quiz`, `project`, `midterm`, `final`, `test`, `comments`
  - `student_id` (required for midterm/final/test/comments)
  - `class_section_id` (required for midterm/final/test)
  - For `homework|quiz|project`: `score_ids` (array), `scores` (array), `comments` (optional array)
  - For `midterm|final|test`: `score` (numeric)
  - For `comments`: `comment` (string)
  
  After updating scores, automatically triggers semester score recalculation for all students in the class section via `SemesterScoreService`.
- `GET /api/v1/grading` (`grading`) – Returns comprehensive grading data for all students grouped by class sections. Returns:
  - `grades` – Array grouped by class_id, containing class sections with `class_section_id` and `class_section_name`
  - `students_by_section` – Array keyed by section_id, containing students with their PTAP scores and semester scores
  - `semester` – Current semester
  - `school_year` – Current school year
  
  Uses dual-join strategy to match semester_scores by both business ID (class_section_id) and primary key (id) for compatibility.
- `GET /api/v1/grading/scores-comments` (`getScoreComment`) – Retrieves all scores and comments for all students in the current semester and school year. Returns a comprehensive dataset including:
  - Student basic information (school_id, firstname, lastname, class_name)
  - Attendance data (score, absences, total_days)
  - All score types: `final_exam`, `homework`, `midterm`, `project`, `quiz`, `semester_score`
  - Comments array for each student
  - Semester scores with all averages (homework_avg, quiz_avg, project_avg, midterm_exam_score, final_exam_score, attendance_score, participation_score, ptap_score, test_avg, semester_score)

## HealthController
**File:** `app/Http/Controllers/Api/HealthController.php`  
**Purpose:** Operational health check combining filesystem permissions and database schema sanity.

- `GET /api/v1/health` (`index`) – Public endpoint (no auth required) that performs health checks on:
  - **Filesystem paths**: Checks if upload directories exist and are writable:
    - `uploads` base directory
    - `uploads/reimbursements`
    - `uploads/receipts`
    - `uploads/checks`
  - **Database schema**: Verifies existence of critical tables and required columns:
    - `user_preferences` table and `timezone` column
    - `settings` table and `timezone` column
    - `migrations` table
  
  Returns JSON payload with:
  - `ok` (boolean) – Overall health status
  - `paths` (array) – Status of each filesystem path with `label`, `path`, `exists`, `writable`
  - `database` (object) – Database schema checks with table/column existence flags
  - `write_path` (string) – Base storage path
  - `timestamp` (string) – ISO 8601 timestamp of the check
  
  HTTP Status Codes:
  - `200 OK` – All checks passed
  - `503 Service Unavailable` – One or more checks failed

## HomeworkController
**File:** `app/Http/Controllers/Api/HomeworkController.php`  
**Purpose:** CRUD for per-student homework scores scoped by class section, with bulk update and column management capabilities.

**Endpoints:**
- `GET /api/v1/homework` (`index`) – Paginated list with optional `class_section_id` query parameter. Filters by current semester and school year.
- `GET /api/v1/homework/student/{id}` (`getByStudent`) – Returns all homework entries for a specific student, filtered by current semester and school year.
- `GET /api/v1/homework/class-section` (`getByClassSection`) – Retrieves comprehensive homework data for a class section. Query param `class_section_id` (required). Returns:
  - `homework_scores` – Scores grouped by student ID
  - `students` – List of students in the class section
  - `homework_headers` – Array of unique homework_index values
  - `semester`, `school_year`, `class_section_id`
  - `has_homework_cols` – Boolean indicating if any homework columns exist
- `GET /api/v1/homework/students-with-scores` (`getStudentsWithScores`) – Returns students with their homework scores. Query param `class_section_id` (required). Returns students array with `student_id`, `firstname`, `lastname`, and `scores` object keyed by homework_index.
- `POST /api/v1/homework` (`create`) – Creates a single homework entry. Body requires:
  - `student_id` (integer, required)
  - `class_section_id` (integer, required)
  - `homework_index` (integer, required)
  - `score` (numeric, optional)
  - `comment` (string, optional)
- `POST /api/v1/homework/update-scores` (`updateScores`) – Bulk update homework scores for multiple students. Body requires:
  - `scores` (object, required) – Nested object: `{student_id: {homework_index: score}}`
  - `class_section_id` (integer, required)
  
  Behavior:
  - Updates existing scores if record exists
  - Creates new records if they don't exist
  - Deletes records if score is blank/null
  - Automatically triggers semester score recalculation for all students in the class section
- `POST /api/v1/homework/add-column` (`addNextColumn`) – Adds a new homework column (index) for all students in a class section. Body requires:
  - `class_section_id` (integer, required)
  
  Returns:
  - `homework_index` – The new homework index number
  - `new_homework_id` – ID of the first inserted record
  - `students_processed` – Number of students for whom the column was added
- `PATCH /api/v1/homework/{id}` (`update`) – Updates a single homework entry. Allows updating `score` and/or `comment` fields.

## HomeworkTrackingController
**File:** `app/Http/Controllers/Api/HomeworkTrackingController.php`  
**Purpose:** Build a Sunday-based schedule of homework indices for dashboards with comprehensive tracking data.

- `GET /api/v1/homework/tracking` (`index`) – Returns comprehensive homework tracking data. Query parameters:
  - `start_date` (optional, default: '2025-09-21') – Start date for the tracking period
  - `end_date` (optional, default: '2026-01-18') – End date for the tracking period
  - `page` (optional, default: 1) – Page number for paginated teacher/section results (8 per page)
  
  Returns:
  - `semester` – Current semester
  - `school_year` – Current school year
  - `sundays` – Array of all Sunday dates (Y-m-d format) within the date range, starting from the first Sunday on/after start_date
  - `event_days` – Object mapping dates (Y-m-d) to boolean true for no-school days from calendar events
  - `date_to_index` – Object mapping Sunday dates to homework_index numbers (null for no-school Sundays)
  - `teachers` – Paginated array of class sections with:
    - `class_section_id` – Class section ID
    - `class_id` – Class ID (for sorting: KG=13, Grades 1-11, Youth=12)
    - `class_section_name` – Name of the class section
    - `teachers` – Array of main teacher names
    - `tas` – Array of TA names
  - `has_homework` – Nested object: `{class_section_id: {homework_index: true}}` indicating which sections have homework for which indices
  - `hw_entered_at` – Nested object: `{class_section_id: {homework_index: 'Y-m-d'}}` with first creation date per homework index
  - `has_homework_by_date` – Nested object: `{class_section_id: {sunday_date: true}}` mapping homework to the nearest prior non-no-school Sunday
  - `hw_entered_at_by_date` – Nested object: `{class_section_id: {sunday_date: 'Y-m-d'}}` with earliest entry date per Sunday
  - `pagination` – Pagination metadata:
    - `page` – Current page number
    - `total_pages` – Total number of pages
    - `per_page` – Items per page (8)
    - `total_rows` – Total number of class sections
  
  The endpoint calculates Sunday-based homework indices, skips no-school days, aggregates homework data by both index and date, and provides teacher/TA information for each class section. Results are sorted by grade order (KG first, then Grades 1-11, then Youth).

## InventoryController
**File:** `app/Http/Controllers/Api/InventoryController.php`  
**Purpose:** Comprehensive inventory management system for tracking items, categories, stock movements, classroom audits, book distribution, and inventory summaries.

**Item Management:**
- `GET /api/v1/inventory` (`index`) – List inventory items filtered by `type` (classroom/book/office/kitchen), `school_year`, and `semester`. Returns items with categories, condition options, school years, and user names for updated_by fields.
- `GET /api/v1/inventory/create` (`create`) – Get form data for creating a new item, including categories and condition options for the specified type.
- `GET /api/v1/inventory/{id}` (`show`) – Retrieve a single inventory item with its categories and condition options.
- `POST /api/v1/inventory` (`store`) – Create a new inventory item. Automatically records initial stock movement if quantity > 0. Body requires `name`, `type`, optional `category_id`, `quantity`, `description`, `unit`, `sku`, `notes`. For books: `isbn`, `edition`. For classroom: `condition`.
- `PATCH /api/v1/inventory/{id}` (`update`) – Update an inventory item. Type is locked to the original item type. Updates `updated_by` automatically.
- `DELETE /api/v1/inventory/{id}` (`delete`) – Remove an inventory item.

**Category Management:**
- `POST /api/v1/inventory/categories` (`saveCategory`) – Create or update a category. Body requires `type`, `name`, optional `description`. For book categories: `grade_min`, `grade_max` (validated: min ≤ max, both ≤ 13). Prevents duplicate (type, name) combinations. Returns conflict error if duplicate exists.

**Classroom Audit:**
- `GET /api/v1/inventory/classroom/{id}/audit` (`auditClassroomForm`) – Get audit form data for a classroom item, including current good/needs_repair/need_replace/cannot_find quantities.
- `POST /api/v1/inventory/classroom/{id}/audit` (`auditClassroomStore`) – Save audit data. Calculates deltas for loss/missing quantities and records stock movements (out for increases, in for decreases). Updates good_qty as on-hand minus needs_repair. Body requires `needs_repair_qty`, `need_replace_qty`, `cannot_find_qty`.

**Summary & Reports:**
- `GET /api/v1/inventory/summary/{type?}` (`summary`) – Get inventory summary for a type (default: classroom). Returns items with aggregated received/issued quantities from movements, filtered by optional `school_year` query param.
- `GET /api/v1/inventory/summary-all` (`summaryAll`) – Comprehensive summary across all types or a selected type. Query params: `school_year` (default: current), `type` (all/book/classroom/office/kitchen). Returns rows with opening, received, distributed, other_out, adjust_net, ending, onhand, variance calculations. Includes totals row.

**Stock Adjustments:**
- `GET /api/v1/inventory/{id}/adjust` (`adjustForm`) – Get form data for adjusting stock.
- `POST /api/v1/inventory/{id}/adjust` (`adjustStore`) – Adjust inventory quantity. Body requires `mode` (in/out/adjust), `quantity` (> 0), optional `reason`, `note`. Records movement and prevents negative stock for out/adjust modes.

**Book Distribution (Teacher):**
- `GET /api/v1/inventory/books/distribute` (`teacherDistributeForm`) – Get book distribution form for teachers. Requires authentication and teacher/TA role. Filters books by category grade range matching the selected class section's grade level. Groups books by category with grade range labels. Returns students in the class, prior distribution history for selected book, and on-hand quantity. Query params: `class_section_id` (optional if teacher has only one class), `item_id` (optional, for showing history).
- `POST /api/v1/inventory/books/distribute` (`teacherDistributeStore`) – Distribute books to students. Body requires `item_id`, `class_section_id`, `student_ids` (array), optional `note`. Validates students belong to teacher's class, checks stock availability, prevents duplicate distributions (only assigns to students who don't already have the book this school year). Records one movement per student with type 'distribution'. Returns count of successfully distributed books.

**Movement Management:**
- `GET /api/v1/inventory/movements` (`movementsIndex`) – List all inventory movements with enriched data (item name, performed_by name, teacher name, student name, class section name). Query params: `school_year`, `semester` for filtering. Ordered by ID descending.
- `GET /api/v1/inventory/movements/create` (`movementsCreate`) – Get form data for creating a movement, including movement types, semesters, items, class sections, teachers, and students.
- `GET /api/v1/inventory/movements/{id}` (`movementsEdit`) – Get a single movement with hydrated display names.
- `POST /api/v1/inventory/movements` (`movementsStore`) – Create a movement. Body requires `item_id`, `qty_change`, `movement_type` (initial/in/out/adjust/distribution), optional `reason`, `note`, `semester`, `school_year`, `teacher_id`, `student_id`, `class_section_id`. Automatically sets `performed_by` from session.
- `PATCH /api/v1/inventory/movements/{id}` (`movementsUpdate`) – Update a movement. Allows updating all fields except `item_id` (immutable after creation).
- `DELETE /api/v1/inventory/movements/{id}` (`movementsDelete`) – Delete a movement.
- `POST /api/v1/inventory/movements/bulk-delete` (`movementsBulkDelete`) – Bulk delete movements. Body requires `ids` (array of movement IDs).

**Movement Types:** `initial`, `in`, `out`, `adjust`, `distribution`

**Inventory Types:** `classroom`, `book`, `office`, `kitchen`

**Key Features:**
- Automatic quantity recalculation from movement history
- Legacy item support: ensures initial movements exist for items without movement history
- School year opening balance tracking: ensures initial movement exists for each school year
- Classroom-specific: good_qty automatically calculated as on-hand minus needs_repair_qty
- Book distribution: grade range filtering, prevents duplicate distributions, stock validation
- Comprehensive audit trail with user tracking (updated_by, performed_by)

## InvoiceController
**File:** `app/Http/Controllers/Api/InvoiceController.php`  
**Purpose:** Comprehensive invoice management including generation, calculation, PDF export, and parent invoice tracking.

**Endpoints:**
- `GET /api/v1/invoices` (`index`) – Paginated list of invoices with optional filters: `parent_id`, `status`, `school_year`, `page`, `per_page`. Returns invoices with pagination metadata.
- `GET /api/v1/invoices/{id}` (`show`) – Returns a single invoice by ID or `404` if not found.
- `GET /api/v1/invoices/management-data` (`managementData`) – Composite data for invoice management view. Returns:
  - `schoolYears` – Available school years from invoices
  - `invoices` – Array of parent invoice summaries with:
    - `parent_name`, `parent_id`
    - `enrolledKids` – Array of enrolled students with name, grade, tuition_fee
    - `withdrawnKids` – Array of withdrawn students
    - `invoice_amount`, `refund_amount`, `invoice_date`, `last_updated`, `invoice_id`
  - Query params: `schoolYear` or `year` (defaults to configured year)
- `GET /api/v1/invoices/unpaid` (`unpaidInvoices`) – Returns all unpaid invoices ordered by due date ascending.
- `GET /api/v1/invoices/parent/{parentId}` (`getByParent`) – Returns all invoices for a specific parent. Optional query param: `school_year` (defaults to configured year).
- `POST /api/v1/invoices/generate` (`generateInvoice`) – Generates or updates invoice for a parent. Body requires `parent_id`. Calculates tuition fees based on enrolled/withdrawn students, applies discounts, includes event charges, and handles refunds. Creates new invoice if none exists, or updates existing invoices with recalculated totals. Returns `{ok, updated, updated_ids, insert_id}`.
- `POST /api/v1/invoices/check-and-generate` (`checkAndGenerateInvoice`) – Checks enrollment status and generates invoice if needed. Body requires `parent_id` and `status`. Only generates for statuses: `payment pending`, `withdrawn`, `enrolled`.
- `GET /api/v1/invoices/{id}/pdf` (`generatePdfInvoice`) – Prepares invoice data for PDF generation. Returns comprehensive invoice data including parent info, students (enrolled/withdrawn), charges, payments, discounts, refunds, and additional charges. Note: Actual PDF rendering would need separate implementation.
- `POST /api/v1/invoices` (`store`) – Creates a new invoice. Body should include invoice fields (see InvoiceModel fillable). Automatically sets `school_year` and `semester` from configuration if not provided.
- `PATCH /api/v1/invoices/{id}` (`update`) – Updates invoice fields. Allowed fields: `status`, `paid_amount`, `balance`, `description`.
- `PATCH /api/v1/invoices/{id}/status` (`updateStatus`) – Updates invoice status only. Body requires `status` field.
- `DELETE /api/v1/invoices/{id}` (`delete`) – Deletes an invoice.

**Invoice Calculation Logic:**
- Tuition fees calculated based on student grade levels (regular vs youth)
- Regular students (grade <= configured `grade_fee`): First student full price, additional students discounted
- Youth students (grade > `grade_fee`): Flat youth fee per student
- Refund window: Withdrawn students excluded from billing if within refund deadline, otherwise billed
- Event charges: Summed from event charges table
- Discounts: Recalculated based on voucher type (percent or fixed)
- Additional charges: Applied from `additional_charges` table (add/deduct types)
- Balance calculation: `total_amount - discounts - refunds_paid - payments_received`

**Helper Methods:**
- `calculateTuitionFee()` – Calculates tuition based on enrolled/withdrawn students and refund window
- `calculateTotalTuitionFee()` – Computes total tuition with regular/youth fee logic
- `recalculateAndUpdateDiscount()` – Recalculates and updates discount amounts for invoices
- `prepareInvoiceData()` – Prepares comprehensive invoice data for display/PDF
- `getGradeLevel()` – Parses grade names to numeric levels (handles KG, Pre-K, Youth, numeric grades)
- `compareGrades()` – Compares grade levels for sorting

## IpBanController
**File:** `app/Http/Controllers/Api/IpBanController.php`  
**Purpose:** Manage IP lockouts resulting from failed login attempts.

**Endpoints:**
- `GET /api/v1/ip-bans` (`index`) – Paginated list of IP ban records filtered by `status=all|active|expired`. Query params: `page`, `per_page`, `status`. Returns paginated results with IP addresses, attempt counts, blocked_until dates, and timestamps.
- `GET /api/v1/ip-bans/{id}` (`show`) – Fetch a specific ban record by ID or `404` if not found.
- `POST /api/v1/ip-bans/unban` (`unban`) – Unban IP(s). Body accepts:
  - `id` (integer) – Unban by record ID
  - `ip` (string) – Unban by IP address
  - `all` (integer, 1) – Unban all active bans (sets `blocked_until` to null and `attempts` to 0 for all active bans)
  - Returns count of unbanned IPs and success message
- `POST /api/v1/ip-bans` (`ban`) – Ban an IP with explicit date. Body requires:
  - `ip` (string, required) – IP address to ban
  - `blocked_until` (date, required) – Ban expiration date/time
  - Optional: `attempts` (integer, default 5), `reason` (string)
  - Creates new record if IP doesn't exist, or updates existing record
  - Returns the ban record
- `POST /api/v1/ip-bans/ban-now` (`banNow`) – Ban an IP immediately for specified hours. Body accepts:
  - `id` (integer) – Ban by record ID, or
  - `ip` (string) – Ban by IP address
  - `hours` (integer, default 24) – Number of hours to ban (minimum 1, defaults to 24)
  - Calculates `blocked_until` automatically from current time + hours
  - Sets attempts to max(current attempts, 10) for clarity
  - Returns IP address, blocked_until date, and hours
