# 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.
