Files
alrahma_sunday_school/full_school_year_management_interface_plan.md
T
root e06ccc9cc0
Tests / PHPUnit (push) Failing after 40s
ADD SCHOOL YEAR MANAGEMENT
2026-07-12 02:21:39 -04:00

26 KiB

Full School Year Management Interface Implementation Plan

1. Objective

Replace the current basic school-year CRUD screen with a complete School Year Management module for CodeIgniter 4.

The final module must support the full lifecycle of a school year:

draft → active → closing → closed → archived

It must also support:

  • creating and editing school years
  • activating exactly one year
  • validating year dates and naming
  • previewing school-year closure
  • identifying blocking issues
  • calculating balances to carry forward
  • executing closure safely
  • recording closure batches and results
  • reopening a closed year only under strict rules
  • archiving historical years
  • preventing unsafe deletion
  • enforcing permissions
  • showing read-only behavior for closed years
  • auditing every critical action

The interface must not allow arbitrary status changes through a general edit form.


2. Current Screen Assessment

The current view already provides:

  • school-year listing
  • create form
  • edit modal
  • status display
  • activate action
  • delete action
  • CSRF protection
  • DataTables sorting and pagination
  • flash messages

The current view must be treated as the starting point only.

The following current behaviors must be removed or restricted:

  • direct status editing from the edit modal
  • deleting every non-active year
  • activating a year without validation
  • relying on frontend checks for business rules
  • showing all actions to every user
  • treating all non-active statuses the same
  • allowing closure state to be changed through CRUD

3. School-Year Lifecycle Rules

Use these statuses:

draft
active
closing
closed
archived

Draft

Allowed:

  • edit name and dates
  • configure semesters
  • initialize classes, fees, and assignments
  • activate
  • delete only when no dependent records exist

Not allowed:

  • carry-forward execution
  • closure execution
  • archive

Active

Allowed:

  • normal school operations
  • edit limited metadata where safe
  • start closing preview
  • begin closing

Not allowed:

  • delete
  • archive directly
  • activate another year without a controlled transition

Closing

Allowed:

  • run closing validations
  • refresh preview
  • resolve blocking issues
  • execute carry-forward
  • complete closure
  • cancel closing only before irreversible actions

Not allowed:

  • normal unrestricted edits
  • delete
  • archive
  • reactivate another year without resolving the closing state

Closed

Allowed:

  • historical viewing
  • closing report viewing
  • audit review
  • controlled reopen action if policy permits
  • archive

Not allowed:

  • normal inserts, updates, or deletes
  • direct activation
  • deletion

Archived

Allowed:

  • read-only viewing
  • audit and report access

Not allowed:

  • edits
  • reopening by normal administrators
  • deletion
  • activation

4. Replace Direct Status Editing

Remove the status dropdown from the general edit modal.

The edit form should only update:

  • school-year name
  • start date
  • end date
  • optional description
  • optional registration start/end dates

Status changes must use dedicated actions:

Activate
Begin Closing
Cancel Closing
Complete Closing
Reopen
Archive

Each action must call a dedicated controller method and service method.

Do not use a generic update action to change lifecycle state.


5. Backend Architecture

Create or update these components:

app/Controllers/Administrator/SchoolYearController.php
app/Controllers/Administrator/SchoolYearClosingController.php

app/Models/SchoolYearModel.php
app/Models/SchoolYearClosingBatchModel.php
app/Models/SchoolYearClosingItemModel.php
app/Models/SchoolYearTransitionLogModel.php

app/Services/SchoolYearManagementService.php
app/Services/SchoolYearClosingService.php
app/Services/SchoolYearContextService.php
app/Services/SchoolYearWriteGuard.php
app/Services/SchoolYearValidationService.php

app/Repositories/SchoolYearRepository.php
app/Repositories/SchoolYearClosingRepository.php

app/Support/SchoolYear/SchoolYearStatus.php
app/Support/SchoolYear/SchoolYearTableRegistry.php

Responsibilities must be separated:

  • controller: request/response handling
  • model: table persistence
  • repository: complex database queries
  • service: business rules and transactions
  • write guard: read-only enforcement
  • validation service: date, status, and dependency checks

Controllers must remain thin.


6. School-Year Model

The school_years table should include at least:

id
name
starts_on
ends_on
status
activated_at
closing_started_at
closed_at
archived_at
created_by
updated_by
created_at
updated_at

Optional fields:

description
registration_starts_on
registration_ends_on
previous_school_year_id
next_school_year_id

Recommended model validation:

protected $validationRules = [
    'name'      => 'required|regex_match[/^\d{4}-\d{4}$/]',
    'starts_on' => 'required|valid_date[Y-m-d]',
    'ends_on'   => 'required|valid_date[Y-m-d]',
];

Add service-level validation for rules CodeIgniter validation cannot safely express:

  • second year equals first year plus one
  • start date is before end date
  • duplicate name is rejected
  • date overlap is rejected where prohibited
  • only one active year exists
  • lifecycle transition is allowed
  • target year is valid for carry-forward

7. Status Transition Service

Create a status transition map.

private const ALLOWED_TRANSITIONS = [
    'draft' => ['active'],
    'active' => ['closing'],
    'closing' => ['active', 'closed'],
    'closed' => ['active', 'archived'],
    'archived' => [],
];

Do not rely only on this map. Additional business validation is required.

Examples:

  • closed → active requires reopen permission and dependency checks
  • closing → active is allowed only before closing batches are finalized
  • active → closing requires a closing target year
  • closed → archived requires all closing records to be finalized

Every transition must:

  1. validate current status
  2. validate target status
  3. validate permissions
  4. validate dependencies
  5. run inside a database transaction
  6. record the transition
  7. write an audit log
  8. return a structured result

8. Activation Workflow

Add an activation modal and dedicated endpoint.

Activation must:

  • verify the selected year exists
  • require status draft or approved reopened state
  • verify start and end dates
  • verify no conflicting active year
  • identify the current active year
  • require confirmation when replacing an active year
  • avoid automatically closing the previous active year
  • update the selected year to active
  • update any previous active year according to policy
  • initialize required year-owned records
  • save activated_at
  • record the user performing activation
  • write an audit entry

Recommended policy:

Only one school year may have status active.
Activating a draft year changes the previous active year to closing,
not closed.

This forces the previous year through a real closure process instead of pretending changing a label completed accounting.


9. School-Year Initialization

When a year is activated, optionally initialize selected data from the previous year.

Allow the administrator to choose:

  • copy class definitions
  • copy section definitions
  • copy tuition and fee templates
  • copy event charge templates
  • copy teacher-class assignment templates
  • copy calendar templates
  • copy communication templates
  • create standard semesters
  • carry selected active family/student relationships

Do not copy:

  • invoices
  • payments
  • attendance
  • grades
  • finalized events
  • messages
  • audit logs
  • historical transactions

Initialization must be idempotent. Re-running it must not create duplicate records.


10. Closing Preview

Add a dedicated Closing Preview page.

Route:

GET administrator/school-years/{id}/closing/preview

The preview must display:

School-year summary

  • school-year name
  • status
  • start and end dates
  • number of students
  • number of families
  • number of classes
  • number of teachers
  • number of invoices
  • total invoiced
  • total paid
  • total outstanding

Financial validation

  • families with positive balances
  • families with negative balances or credits
  • unpaid invoices
  • partially paid invoices
  • unapplied payments
  • failed payment transactions
  • pending reimbursements
  • pending refunds
  • unresolved discounts
  • unposted event charges
  • mismatched invoice/payment school years

Academic validation

  • students without final class placement
  • students without promotion decisions
  • missing final scores
  • incomplete report cards
  • incomplete attendance records
  • teachers with incomplete class submissions
  • orphaned class or enrollment records

Operational validation

  • active events extending beyond the year
  • unclosed parent meetings
  • pending print requests
  • pending acknowledgements
  • inconsistent school-year relationships
  • records missing school_year_id

Carry-forward preview

For each family:

family
source balance
credits
adjustments
net carry-forward
target year
proposed target record

The preview must classify findings as:

blocking
warning
informational

Closure cannot proceed while blocking findings exist.


11. Closing Batch Tables

Create:

school_year_closing_batches

Suggested columns:

id
source_school_year_id
target_school_year_id
status
preview_hash
total_families
total_positive_balance
total_credit_balance
started_by
completed_by
started_at
completed_at
failed_at
failure_message
created_at
updated_at

Create:

school_year_closing_items

Suggested columns:

id
closing_batch_id
family_id
source_balance
credit_amount
adjustment_amount
carry_forward_amount
target_invoice_id
target_adjustment_id
status
error_message
created_at
updated_at

Create:

school_year_transition_logs

Suggested columns:

id
school_year_id
from_status
to_status
action
performed_by
metadata_json
created_at

Add unique constraints to prevent duplicate finalized carry-forward for the same source year, target year, and family.


12. Begin Closing Workflow

Add a dedicated action:

POST administrator/school-years/{id}/closing/start

The service must:

  • require current status active
  • require a valid target year
  • verify target year is draft or active according to policy
  • run the latest preview
  • reject when blocking issues exist
  • create a closing batch
  • save preview totals and hash
  • change source status to closing
  • record closing_started_at
  • write transition and audit logs

The preview hash should detect material data changes between preview and execution.


13. Carry-Forward Execution

Add:

POST administrator/school-years/{id}/closing/execute

Execution must run inside one database transaction.

For each family with a carry-forward amount:

  • calculate final source-year balance
  • compare it with previewed balance
  • reject if materially changed
  • create a target-year opening balance or carry-forward invoice
  • link it to the closing batch item
  • avoid duplicate creation
  • record credits separately from debt if accounting rules require it
  • preserve source-year records unchanged

Recommended target records:

opening_balance_transactions

or a dedicated invoice type:

invoice_type = carry_forward

Do not silently merge historical debt into normal tuition invoices.

Execution must be idempotent:

  • rerunning a completed batch does nothing
  • rerunning a partially failed batch must safely resume or fully roll back
  • duplicate carry-forward records must be prevented by database constraints

14. Complete Closing

Add:

POST administrator/school-years/{id}/closing/complete

Completion must verify:

  • source status is closing
  • closing batch exists
  • all batch items succeeded
  • no new blocking issues appeared
  • carry-forward totals match
  • all required reports are generated
  • target-year links are valid

Then:

  • set source status to closed
  • set closed_at
  • finalize closing batch
  • make source year read-only
  • create final closing report
  • write transition and audit logs

15. Cancel Closing

Add:

POST administrator/school-years/{id}/closing/cancel

Allow cancellation only when:

  • status is closing
  • no finalized carry-forward records exist
  • no irreversible batch step has completed

Cancellation must:

  • mark the batch cancelled
  • restore status to active
  • preserve audit history
  • not delete logs

If carry-forward records already exist, require rollback through a dedicated recovery operation instead of pretending a status change reverses accounting.


16. Reopen Workflow

Add:

POST administrator/school-years/{id}/reopen

Reopening must require a high-level permission.

Validate:

  • year status is closed
  • target/current year financial activity will not be corrupted
  • no dependent archived reports prohibit changes
  • no carry-forward records would become inconsistent
  • administrator provides a reason

Recommended reopening behavior:

  • set status to active or closing according to policy
  • invalidate affected closing reports
  • preserve the original closing batch
  • create a reopen transition record
  • require a new closing batch before closing again

Archived years should not be reopened through the normal interface.


17. Archive Workflow

Add:

POST administrator/school-years/{id}/archive

Allow only when:

  • status is closed
  • closing batch is finalized
  • no pending reopen operation exists
  • administrator has archive permission

Archiving must:

  • set status archived
  • set archived_at
  • remove it from default operational selectors
  • keep it available in historical search
  • enforce strict read-only access

18. Delete Rules

Replace the current generic delete action with deleteDraft.

Route:

POST administrator/school-years/{id}/delete-draft

Allow deletion only when:

  • status is draft
  • the year is not active
  • no year-owned records exist
  • no semester records exist
  • no closing batches exist
  • no transition history other than creation exists
  • user has delete permission

Otherwise show:

This school year cannot be deleted because related data exists.
Archive historical years instead.

Never delete active, closing, closed, or archived years.


19. Permissions

Add permissions such as:

school_year.view
school_year.create
school_year.edit
school_year.activate
school_year.close.preview
school_year.close.start
school_year.close.execute
school_year.close.complete
school_year.close.cancel
school_year.reopen
school_year.archive
school_year.delete_draft
school_year.view_financial_closing

Enforce permissions:

  • in routes or filters
  • in controllers
  • in services for critical actions
  • in the view for button visibility

Hiding a button is user experience. It is not authorization.


20. Interface Redesign

Replace the current single-table CRUD experience with the following sections.

Header

Display:

  • page title
  • active year badge
  • selected year
  • add school year button
  • historical years filter

Summary cards

Show:

  • active year
  • next draft year
  • year currently closing
  • total archived years

Main table columns

School Year
Dates
Status
Students
Families
Financial Balance
Last Transition
Updated
Actions

Status badges

Use distinct styles:

$statusClasses = [
    'draft' => 'bg-info text-dark',
    'active' => 'bg-success',
    'closing' => 'bg-warning text-dark',
    'closed' => 'bg-secondary',
    'archived' => 'bg-dark',
];

Action menu by status

Draft

  • Edit
  • Configure semesters
  • Initialize
  • Activate
  • Delete draft

Active

  • View dashboard
  • Edit limited metadata
  • Preview closing
  • Begin closing

Closing

  • View closing preview
  • Resolve issues
  • Execute carry-forward
  • Complete closing
  • Cancel closing when allowed

Closed

  • View closing report
  • View historical data
  • Reopen when authorized
  • Archive

Archived

  • View historical data
  • View audit trail

Use a dropdown action menu instead of displaying every action inline.


21. Create School Year Modal

The create form should include:

  • school-year name
  • starts on
  • ends on
  • optional description
  • optional registration period
  • initialize semesters checkbox
  • copy configuration from previous year checkbox
  • source year selector when copying

Do not allow the user to create a year directly as active, closing, closed, or archived.

All new years start as:

draft

Activation is a separate operation.


22. Edit School Year Modal

Allow editing only:

  • name
  • dates
  • description
  • registration dates

Restrictions:

  • draft: full metadata editing
  • active: limited editing with warnings
  • closing: mostly locked
  • closed: read-only
  • archived: read-only

Remove status from the modal.


23. Activation Confirmation Modal

Display:

  • year being activated
  • current active year
  • resulting status of current active year
  • initialization options
  • explicit confirmation checkbox
  • warning that activation does not complete closure

Require a typed confirmation only if activation affects a currently active year.


24. Closing Preview Page

Create a dedicated full-page interface rather than a small modal.

Sections:

  1. overview
  2. blocking issues
  3. warnings
  4. finance summary
  5. carry-forward families
  6. academic completion
  7. operational completion
  8. target-year validation
  9. audit and preview timestamp

Actions:

  • refresh preview
  • export preview
  • return to management
  • begin closing
  • execute carry-forward
  • complete closing

Disable actions based on current status and validation results.


25. Closing Progress Interface

During closing, show a deterministic progress state based on persisted batch data:

Preview generated
Validation passed
Closing batch created
Carry-forward prepared
Carry-forward completed
Final validation passed
Year closed

Do not fake progress with a frontend timer.

The page should reload or poll the batch status only when the backend supports resumable execution.


26. Closing Report

Generate a final report containing:

  • source school year
  • target school year
  • completion date
  • administrator
  • total families processed
  • total balances carried
  • total credits carried
  • failed or skipped items
  • financial reconciliation totals
  • academic completion summary
  • warning acknowledgements
  • transition history

Allow export to CSV or PDF later, but the database report must exist first.


27. Semester Management

Add a school-year detail tab for semesters.

Suggested table:

school_year_semesters

Columns:

id
school_year_id
name
code
starts_on
ends_on
status
sort_order
created_at
updated_at

Rules:

  • semester dates must fall within the school year
  • semesters must not overlap
  • semester order must be explicit
  • closed semesters reject grade and attendance edits where required
  • not every year-owned table needs a semester column

Do not use a global configuration.semester value as the sole source of truth.


28. Read-Only Enforcement

Use SchoolYearWriteGuard in every year-owned write path.

The interface should disable or hide mutation controls for closed and archived years, but backend enforcement remains mandatory.

API and controller errors should return clear messages such as:

The selected school year is closed and cannot be modified.

29. Routes

Recommended routes:

$routes->group(
    'administrator/school-years',
    ['filter' => 'auth'],
    static function ($routes) {
        $routes->get('/', 'Administrator\SchoolYearController::index');
        $routes->post('store', 'Administrator\SchoolYearController::store');
        $routes->post(
            '(:num)/update',
            'Administrator\SchoolYearController::update/$1'
        );
        $routes->post(
            '(:num)/activate',
            'Administrator\SchoolYearController::activate/$1'
        );
        $routes->post(
            '(:num)/delete-draft',
            'Administrator\SchoolYearController::deleteDraft/$1'
        );
        $routes->post(
            '(:num)/archive',
            'Administrator\SchoolYearController::archive/$1'
        );
        $routes->post(
            '(:num)/reopen',
            'Administrator\SchoolYearController::reopen/$1'
        );

        $routes->get(
            '(:num)/closing/preview',
            'Administrator\SchoolYearClosingController::preview/$1'
        );
        $routes->post(
            '(:num)/closing/start',
            'Administrator\SchoolYearClosingController::start/$1'
        );
        $routes->post(
            '(:num)/closing/execute',
            'Administrator\SchoolYearClosingController::execute/$1'
        );
        $routes->post(
            '(:num)/closing/complete',
            'Administrator\SchoolYearClosingController::complete/$1'
        );
        $routes->post(
            '(:num)/closing/cancel',
            'Administrator\SchoolYearClosingController::cancel/$1'
        );
    }
);

Use exact route names consistently. The existing route ending in /active should be replaced with /activate.


30. Transactions and Locking

Use database transactions for:

  • activation
  • starting closing
  • carry-forward execution
  • completing closing
  • reopening
  • archiving

Where supported, lock the school-year row during critical transitions.

Before each operation, re-read the current status inside the transaction.

Do not trust the status that was displayed when the page loaded. Another administrator may have changed it since then, because concurrency exists even when the interface would prefer otherwise.


31. Audit Logging

Record:

  • creation
  • metadata edits
  • activation
  • initialization
  • closing preview generation
  • closing start
  • carry-forward execution
  • closing completion
  • cancellation
  • reopening
  • archive
  • failed transition attempts

Audit metadata should include:

user_id
school_year_id
action
from_status
to_status
reason
request_id
ip_address
summary
created_at

Do not store sensitive payment data in audit metadata.


32. Validation and Error Handling

Return clear messages for:

  • duplicate year
  • invalid name sequence
  • date overlap
  • invalid status transition
  • second active year attempt
  • unresolved closing blockers
  • changed data since preview
  • missing target year
  • target year invalid
  • duplicate carry-forward
  • unauthorized operation
  • deletion blocked by dependencies

Use flash messages for normal web redirects and structured errors for API endpoints.


33. Testing Plan

Unit tests

Test:

  • valid and invalid status transitions
  • name validation
  • date validation
  • overlap detection
  • activation policy
  • write guard
  • carry-forward calculation
  • permission checks
  • reopening rules

Database tests

Test:

  • only one active year
  • draft deletion with no dependencies
  • deletion blocked with dependencies
  • closing batch creation
  • unique carry-forward records
  • transaction rollback
  • status transition logs

Feature tests

Test every route:

  • create
  • edit
  • activate
  • preview
  • start closing
  • execute
  • complete
  • cancel
  • reopen
  • archive
  • delete draft

Verify CSRF and permission enforcement.

Finance regression tests

Test:

  • old-year debt does not leak into current-year totals
  • carry-forward matches source closing balance
  • credits are handled correctly
  • preview and execution totals match
  • duplicate execution is blocked
  • rollback leaves no partial target-year records

UI tests

Test:

  • buttons appear by status and permission
  • closed years are read-only
  • correct status badges display
  • preview blockers disable execution
  • action confirmation modals show correct year
  • table sorting works
  • dropdowns are alphabetically sorted
  • mobile layout remains usable

34. Implementation Phases

Phase 1: Foundation

  • add lifecycle status constants
  • update database schema
  • update model validation
  • add management and validation services
  • add transition logging
  • remove direct status editing

Phase 2: CRUD Hardening

  • force new years to draft
  • validate dates and names
  • restrict editing by status
  • replace delete with delete-draft
  • add permissions

Phase 3: Activation

  • implement activation service
  • enforce one active year
  • add initialization options
  • add activation modal
  • add tests

Phase 4: Closing Preview

  • build preview queries
  • classify blockers and warnings
  • add closing preview page
  • add target-year validation
  • add export-ready result structure

Phase 5: Closing Execution

  • create batch tables
  • implement start closing
  • implement carry-forward
  • add idempotency and constraints
  • implement completion and cancellation
  • add reconciliation tests

Phase 6: Historical Management

  • implement reopen
  • implement archive
  • add closing reports
  • add historical filters
  • enforce read-only behavior

Phase 7: Semester Management

  • create year-semester table
  • add semester configuration tab
  • validate semester dates
  • integrate semester write guards

Phase 8: Final Hardening

  • add audit coverage
  • add concurrency checks
  • add static checks
  • run full regression tests
  • review production migration and rollback steps

35. Acceptance Criteria

The School Year Management module is complete only when:

  • every new school year starts as draft
  • status cannot be changed from the edit form
  • only one school year can be active
  • activation is transactional and audited
  • the previous year is not silently considered closed
  • closing preview shows financial, academic, and operational blockers
  • closure cannot proceed with blocking issues
  • carry-forward is transactional and idempotent
  • source-year financial records remain unchanged
  • closed and archived years are read-only
  • deletion is limited to unused draft years
  • reopen and archive actions are permission-controlled
  • all critical transitions are logged
  • conflicting concurrent transitions are rejected
  • semester configuration is tied to the correct school year
  • tests cover the full lifecycle
  • the interface exposes only valid actions for each status

36. Non-Negotiable Rules

  1. Do not allow arbitrary status editing.
  2. Do not close a school year by changing one database field.
  3. Do not carry balances forward without a persisted batch.
  4. Do not trust frontend school-year values.
  5. Do not delete historical school years.
  6. Do not allow closed-year writes without an explicit reopen workflow.
  7. Do not mix source-year and target-year financial records.
  8. Do not execute closure without a fresh validated preview.
  9. Do not use UI button visibility as authorization.
  10. Do not mark work complete until lifecycle, accounting, permissions, audit, and regression tests all pass.