# Change Log — SchoolPlus (Laravel-SMS adaptation for a Nigerian school)

Cumulative record of every modification made, phase by phase.
Each entry: **File → Change → Reason → Impact → How to test.**

> **Verification note.** No database or web server was available while this
> work was done, so nothing has been run end-to-end. What *was* checked:
> PHP syntax (`php -l`) on every PHP file (232 files, final count); from
> Phase 11 onward, every Blade template is compiled with Laravel 8's real
> `BladeCompiler` and the output linted (114 templates, final count, 0
> failures); route names, controller methods and view references were
> cross-checked by script; and pure logic (fee SQL, date normalisation,
> contrast colours) was tested against known data. **A real click-through on
> a staging copy is still required before going live.** See
> `DEPLOYMENT_GUIDE.md` for how to actually put this on a server, including
> a step-by-step post-deploy test checklist.

## Round 3 — portal fixes (Oct 2026)

See WHATS-NEW.txt. New migrations: `2019_10_07_000000_add_t_remark_to_marks`,
`2019_10_08_000000_create_assignments_table`, `2019_10_09_000000_create_online_payments_table`.
Main files: MarkController, UserController, AssignmentController, OnlinePaymentController,
app/Helpers/OnlinePay.php, app/Helpers/Qs.php (class/subject teacher helpers), routes/web.php,
marks/show + marks/print sheet views, payments/online views.

## Round 4 — mobile + report-card + payment features (Oct 2026)

See WHATS-NEW.txt items 13-18. New migrations: `2019_10_10_000000_convert_skill_ratings_to_ids`,
`2019_10_11_000000_add_signature_stamp_settings`, `2019_10_12_000000_add_receipts_and_alerts`.
Main files: SkillController, AlertController, OnlinePaymentController@receipt, OnlinePay (alerts), Skill model,
SettingController (signature/stamp), marks views (edit, show/print skills, comments, tabulation print), qs.css,
top_menu, dashboard.

## Round 5 — subjects, class teachers, teaching assignment (Oct 2026)

See WHATS-NEW.txt items 19-21. New migration: `2019_10_13_000000_class_teachers_and_unassigned_subjects`
(subjects.teacher_id becomes nullable; my_classes.class_teacher_id / class_teacher2_id).
Main files: SubjectController, MyClassController, UserController@assign_teaching, Qs (class-level class teachers),
subjects / classes / users show views.

## Migrations to run (in order, after the original ones)

```
2019_09_27_000000_create_departments_table
2019_09_27_000001_add_department_id_to_staff_records_table
2019_09_27_000002_add_mark_max_settings
2019_09_28_000000_create_attendances_table
2019_09_28_000001_create_staff_attendances_table
2019_09_28_000002_add_attendance_fks
2019_09_29_000000_remove_dead_email_credential_settings
2019_09_30_000000_add_library_enabled_setting
2019_10_01_000000_add_branding_settings
2019_10_02_000000_add_favicon_setting
2019_10_03_000000_add_school_color_dashboard_setting
2019_10_04_000000_set_schoolplus_branding
2019_10_05_000000_create_announcements_table
2019_10_05_000001_add_announcement_fks
```

`php artisan migrate` runs these in the correct order automatically — this
list is for reference only. All are additive except
`2019_10_04_000000_set_schoolplus_branding`, which deliberately **overwrites**
`system_name`/`motto` (see its own file header for why).

---

## Phase 2 — Deployment readiness
| File | Change | Reason |
|---|---|---|
| `routes/web.php` | `Auth::routes(['register' => false])` | Public self-registration created role-less accounts on a page with missing assets |
| `config/app.php` | Timezone → `env('APP_TIMEZONE','Africa/Lagos')` | Was UTC; attendance/payment times were wrong |
| `.gitignore` | Replaced bare `storage` rule with scoped Laravel rules | Old rule dropped `storage/framework/*` on a git clone, so Laravel couldn't boot |
| `.env.production.example` | **New** cPanel-oriented env template | `APP_DEBUG=false`, file drivers, secure cookies |

Not changed: `composer.json` (dompdf `^0.8`). It cannot be verified without Composer;
test PDF receipts on PHP 8.x and report back.

## Phase 3 — Database & installation
- `database/seeders/AdminUserSeeder.php` **(new)**, `DatabaseSeeder.php`, `UsersTableSeeder.php`:
  the seeder shipped public demo logins (password `cj`). Production now seeds
  reference data + one Super Admin (credentials from `ADMIN_*` in `.env`, or a
  random password printed once). Demo classes/users/students only seed outside production.
  Also fixes a crash where `SubjectsTableSeeder` needed a teacher that no longer existed.

## Phase 4 — Stability
- Added missing `index()` to `StudentRecordController` and `create()/show()` redirect stubs to
  `MyClass`, `Section`, `Subject`, `Grade`, `Exam`, `Dorm` controllers (registered routes with no method).
- `StudentRecordController`: `not_graduated` restricted to admin/super admin; `listByClass` to admin/super admin/teacher.

## Phase 5 — School structure
- **Departments module** (model, repo, requests, controller, 2 views, menu, migrations); department stored on `staff_records`
  via existing `Qs::getStaffRecord()`; dropdown on Add/Edit User.
- Reviewed and left unchanged: admission numbers, parent linkage, sessions, sections, subjects (already complete).

## Phase 6 — Results
- `MarkController::update`: blank score fields crashed the whole batch under PHP 8 (`"" + int` TypeError) — fixed; NULL still stored for "not entered".
- `MarkUpdate` request wired up: per-field validation replaces the old silent "wipe row if total > 100".
- CA/Exam maximums now configurable (`ca1_max`, `ca2_max`, `exam_max`) in Settings; entry form uses them.
- `MarkRepo::getClassAvg` now scopes by section (position was per-section, average was per-class).
- `Qs::getSetting()` no longer fatals on a missing key.
- Not done: cumulative/annual (3rd-term) scoring remains dormant.

## Phase 7 — Attendance (new module)
Tables `attendances`, `staff_attendances`; student daily attendance (present/absent/late/excused) per class/section/term/date,
history + class report, staff attendance (admin only); students/parents view their own via ownership checks.

## Phase 8 — Fees & payments
- `Qs::naira()` currency formatting on receipts, invoices, lists, report card (and the AJAX payment JS).
- "Next Term Fees" settings never saved (form field names didn't match DB keys) — fixed.
- Payment amount validation (`PaymentCreate`, `pay_now` max = outstanding balance); `reset_record` now super-admin only.
- Parents/students can view their own invoices and receipts (ownership-checked); staff-only actions hidden from them.

## Phase 9 — Roles & permissions
- Removed dead `email_host`/`email_pass` settings (never used; mail is configured via `.env`).
- **Timetable record delete** had no role check (any student could delete a class timetable) — now super admin only (matches the UI).
- `MarkController::year_selector/year_selected` now ownership-checked.
- Decision: no separate "Principal" role — `admin` already fills it (only admins can write the Head Teacher's comment).

## Library module (new)
Books catalog + borrow workflow (pending → issued → returned/rejected); librarian/admin manage, everyone browses/requests;
Settings → Modules toggle turns the whole section off for everyone. The original controllers were unroutable stubs with a wrong namespace and were replaced.

## Phase 10 — Settings & branding
- New settings: current term, motto, website, principal/head teacher, school colours; motto/principal shown on report cards/receipts;
  current term defaults the term dropdowns.
- Seeded demo school identity (name, phone, address, fees) replaced with blank/generic placeholders.
- Page `<title>` now uses the configured school name (login page hardcoded "CJ INSPIRED ACADEMY" as a fallback).

## Favicon & colours
- Favicon upload; primary/secondary colours now actually applied to printable documents; optional "Apply to Dashboard".
  Colour values are strictly validated as `#RRGGBB` (they are written into inline CSS).

## Phase 11 — Dashboard & UI/UX
| File | Change | Reason |
|---|---|---|
| `HomeController.php`, `Repositories/DashboardRepo.php` **(new)** | Role-specific statistics using aggregate queries | Old dashboard loaded every user into memory to count them; teachers/students/parents/accountants/librarians saw no statistics |
| `pages/support_team/dashboard.blade.php` | Rebuilt: welcome banner (school, session, term), stat cards, quick actions, attendance/fees/library panels, parent children table | Useful, role-aware landing page |
| Dashboard calendar | Now shows real term start/end dates from Settings | Was an empty calendar |
| `partials/inc_top.blade.php`, `layouts/*` | Single favicon tag (custom or default) | **Bug from the previous step:** the theme's default favicon tag came *after* the uploaded one and could override it |
| `Qs::normalizeDate` + `AttendanceSelector`, `BorrowCreate`, `StaffAttendanceController`; 3 date inputs get `data-date-format="yyyy-mm-dd"` | Normalise dates | **Bug in Phases 7 & library:** the datepicker emits `mm/dd/yyyy`; that puts slashes in a URL segment and is rejected by MySQL `DATE` columns, so picking a date from the calendar would have failed |
| `public/assets/css/qs.css` | Dashboard styles; mobile table scrolling, scrollable tab strips | Phone usability |
| `HomeController` privacy/terms | Use configured school name | Was framework `APP_NAME` |

**Testing:** log in as each role and confirm the dashboard shows that role's cards/panels; take attendance choosing the date with the calendar picker;
create a library request with the picker; upload a favicon and confirm it (not the default) appears; open the app on a phone-width window and check tables scroll sideways.

## Phase 12 — Security review
The most serious finding of the whole engagement: four places set a password
to a fixed, guessable string rather than something random. This is an
open-source project, so these exact strings were visible on GitHub.

| File | Was | Now |
|---|---|---|
| `StudentRecordController::store()` | Every new student got password `"student"` | Random password, shown once to the admin |
| `StudentRecordController::reset_pass()` | Reset set it back to `"student"` | Random password, shown once |
| `UserController::store()` | Blank password defaulted to the account's own role name (a new teacher's password was literally `"teacher"`) | Random password if left blank; an explicitly-set password is respected |
| `UserController::reset_pass()` | Reset set it to `"user"` | Random password, shown once |

`Qs::generateTempPassword()` (new) is the shared generator — deliberately
excludes `&` from its symbol set, because that character gets HTML-escaped
to `&amp;` when shown to the admin, which would make a copied password not
match what's actually stored. `custom_js.blade.php`'s `submitForm()` shows
the password in a dismiss-required modal, not an auto-hiding toast.

Also: `UserRequest`'s password rule raised from `min:3` to `min:8`.

**If Phases 3–11 were ever deployed live before this fix, any account
created or reset during that window may still have one of the old
defaults — this fix is not retroactive.**

Checked and found clean: SQL injection (no raw/concatenated queries with
user input), mass assignment (no `::create($request->all())` anywhere), XSS
(every unescaped `{!! !!}` output traced to a helper that only ever wraps a
server-computed integer), CSRF/login throttling/session regeneration (all
stock Laravel, unmodified).

## Demo/branding setup (user request, between Phases 12 and 13)
- `system_name` default → **"SchoolPlus"**, `motto` default → **"Smart School
  Management made simple"**. New migration `set_schoolplus_branding`
  deliberately *overwrites* these two (unlike every other settings
  migration) since this was an explicit rebrand request.
- Demo seeder passwords (never used in production — gated by
  `app()->environment('production')` since Phase 3) changed to `123456` for
  every demo role. Added a demo **Librarian** account (didn't exist before).
  Linked the demo Student to the demo Parent account, so the Parent
  dashboard/fees/attendance views have something to show out of the box —
  this was silently broken before (the demo parent had no children).
- The real production temporary-password generator from Phase 12 was **not**
  touched by this — it still always generates a random password.

## Phase 13 — Final testing
Full project re-verified: 225 PHP files linted, 111 Blade templates compiled
with Laravel's real compiler, all route/controller references checked. One
module on the original testing checklist — **Announcements** — turned out
to not exist anywhere in the codebase; flagged rather than built
unprompted (see below for where it was then built).

**Found and fixed real mobile-responsiveness bugs:**
- `marks/edit.blade.php`, `attendance/edit.blade.php`,
  `staff_attendance/manage.blade.php` — the three live data-entry grids
  (exactly what a teacher is most likely to use on a phone) had **no**
  responsive wrapper at all; on a narrow screen they would simply overflow
  with no way to reach hidden columns. Fixed with `<div class="table-responsive">`.
- `marks/tabulation/index.blade.php`, `marks/show/sheet.blade.php`,
  `timetables/show.blade.php`, `payments/invoice.blade.php` (×2) — had
  `table-responsive` applied directly to the `<table>` element, which does
  nothing in Bootstrap 4 (it only works on a wrapping `<div>`). Three were
  genuinely broken and fixed with a real wrapper; the two in the payments
  invoice already get proper handling via DataTables' own wrapper, so there
  the dead class was just removed.

## Announcements module (user request, built after Phase 13)
New feature, not assigned to any of the original 14 phases. Admin-only
posting (mirrors how Departments/Settings work — not opened to teachers, to
keep the permission model simple); targeted at everyone, a specific role
(students/parents/teachers/staff), or a specific class. A board page open to
every role shows only what's currently published, not expired, and matches
their audience — `AnnouncementRepo::getVisibleFor()` does the matching,
including a parent seeing "class" announcements for any of their children's
classes. Also added a "Recent Announcements" panel to the Phase 11
dashboard. One icon class (`icon-bullhorn`) didn't exist in this theme's
icon font — checked against the actual stylesheet before shipping and used
`icon-megaphone` instead.

New: `create_announcements_table`, `add_announcement_fks` migrations;
`Announcement` model; `AnnouncementRepo`; `AnnouncementCreate`/`Update`
requests; `AnnouncementController`; `announcements/{index,edit,board}.blade.php`.
Modified: `routes/web.php`, `partials/menu.blade.php`, `HomeController.php`,
`dashboard.blade.php`.

## Phase 14 — Deployment guide
`DEPLOYMENT_GUIDE.md` (new, project root) — a complete, non-technical,
step-by-step cPanel deployment walkthrough covering PHP version, MySQL
database/user setup, file upload, the public-folder document-root problem
(with two methods, since cPanel hosts vary in whether the main domain's
document root can be changed), Composer, `.env` setup, `APP_KEY`,
migrations/seeding, the storage symlink, file permissions, cron, SMTP, SSL,
a 14-point post-deploy test checklist, and a troubleshooting section for the
specific errors this project is most likely to produce. Every Terminal step
has a "No Terminal" cPanel-only alternative. Also fixed: `.env.production.example`
still said `APP_NAME="CJ Inspired Academy"` from before the SchoolPlus
rebrand — updated for consistency.
