# 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text description registration_starts_on registration_ends_on previous_school_year_id next_school_year_id ``` Recommended model validation: ```php 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. ```php 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: ```text 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: ```text 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: ```text family source balance credits adjustments net carry-forward target year proposed target record ``` The preview must classify findings as: ```text blocking warning informational ``` Closure cannot proceed while blocking findings exist. --- ## 11. Closing Batch Tables Create: ```text school_year_closing_batches ``` Suggested columns: ```text 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: ```text school_year_closing_items ``` Suggested columns: ```text 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: ```text school_year_transition_logs ``` Suggested columns: ```text 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: ```text 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: ```text 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: ```text opening_balance_transactions ``` or a dedicated invoice type: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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 ```text School Year Dates Status Students Families Financial Balance Last Transition Updated Actions ``` ### Status badges Use distinct styles: ```php $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: ```text 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: ```text 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: ```text school_year_semesters ``` Columns: ```text 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: ```text The selected school year is closed and cannot be modified. ``` --- ## 29. Routes Recommended routes: ```php $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: ```text 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.