# Laravel-Compatible Financial System Plan With Stakeholder Reporting

## 1. Purpose

This document converts the uploaded financial-system remediation plan into a Laravel-compatible implementation plan for this project.

The previous plan was written mostly in CodeIgniter terms: `app/Config/Routes.php`, Spark commands, `app/Libraries`, and CI-style controllers. This project is Laravel, so implementation must use Laravel routes, controllers, services, form requests, resources, middleware, policies, migrations, console commands, storage, and Eloquent/DB transactions.

The goal is not to win a framework cosplay contest. The goal is to fix finance without breaking finance, which is a surprisingly radical position in software.

## 2. Non-Negotiable Compatibility Rule

The existing financial system must keep working while improvements are added.

That means:

```text
Do not delete existing finance routes in the first pass.
Do not remove existing PayPal records or controllers until a separate decommission is approved.
Do not rewrite payment, invoice, refund, discount, expense, or reimbursement flows destructively.
Do not mutate historical invoice math during reporting.
Do not silently reinterpret old statuses.
Add new services and reporting endpoints beside the old flow first.
```

The original plan says to remove PayPal completely. That may still be a valid future phase, but it is not compatible with the current instruction to keep the old financial system working. Therefore, in this Laravel-compatible version, PayPal removal becomes a later controlled decommission phase, not a first-pass code deletion.

## 3. Laravel Mapping

| Old plan concept | Laravel-compatible location |
|---|---|
| `app/Config/Routes.php` | `routes/api.php`, `routes/web.php` |
| `app/Libraries/*` | `app/Services/*` or `app/Support/*` |
| CI controller validation | `app/Http/Requests/*` FormRequest classes |
| CI controllers | `app/Http/Controllers/Api/*` |
| Spark command | Laravel Artisan command in `app/Console/Commands` |
| CI filters | Laravel middleware in `app/Http/Middleware` |
| CI models | Eloquent models in `app/Models` |
| CI views | API resources, frontend pages, or Blade/Inertia only if used |
| `WRITEPATH/uploads` | Laravel `storage/app` or `storage/app/private` |
| Direct route filters | `auth:api`, custom middleware, policies, and gates |

## 4. First-Pass Implementation Strategy

Use an additive approach.

Phase 1 should add:

```text
StakeholderFinancialAnalysisService
StakeholderFinancialAnalysisRequest
GET /api/v1/finance/stakeholder-analysis
GET /api/v1/finance/stakeholder-analysis/csv
administrator aliases for the same stakeholder analysis endpoints
Laravel-compatible documentation
```

Phase 1 must not remove:

```text
existing payment routes
existing invoice routes
existing PayPal routes
existing PayPal transaction exports
existing payment sync code
existing invoice generation code
existing financial summary/report endpoints
```

This lets leadership inspect finance without detonating operations. Boring, safe, good. A rare trilogy.

## 5. Stakeholder Financial Analysis And Reporting

Stakeholders need management-level financial visibility, not raw operational dumps.

The report should be read-only and expose:

```text
gross revenue
net revenue
cash collected
refunds
discounts
expenses
reimbursements
net cash
open receivables
collection rate
expense ratio
operating margin
invoice count
paying family count
payment method mix
monthly charge/collection/expense trend
receivables aging
largest open invoices
risk flags
school-year comparison
CSV export
```

The stakeholder report must not mutate invoices or trigger recalculation. Reports that change accounting are not reports. They are traps with charts.

## 6. Laravel Endpoint Design

Add routes under the existing authenticated finance group:

```php
Route::prefix('finance')->group(function () {
    Route::get('stakeholder-analysis', [FinancialController::class, 'stakeholderAnalysis']);
    Route::get('stakeholder-analysis/csv', [FinancialController::class, 'stakeholderAnalysisCsv']);
});
```

Also add administrator aliases if the admin dashboard uses `/administrator/...` finance paths:

```php
Route::prefix('reports')->group(function () {
    Route::get('stakeholder-analysis', [FinancialController::class, 'stakeholderAnalysis']);
    Route::get('stakeholder-analysis/csv', [FinancialController::class, 'stakeholderAnalysisCsv']);
});
```

The routes should remain inside existing `auth:api` and `admin.access` protected groups where applicable.

## 7. Request Validation

Create:

```text
app/Http/Requests/Finance/StakeholderFinancialAnalysisRequest.php
```

Allowed inputs:

```text
date_from: nullable YYYY-MM-DD
date_to: nullable YYYY-MM-DD, after or equal to date_from
school_year: nullable string
compare_school_year: nullable string
include_monthly_trend: nullable boolean
include_parent_risk: nullable boolean
```

The request should extend the existing `FinancialReportRequest` to preserve behavior and avoid yet another tiny validation kingdom.

## 8. Service Design

Create:

```text
app/Services/Finance/StakeholderFinancialAnalysisService.php
```

Responsibilities:

```text
read existing invoices, payments, refunds, discounts, expenses, reimbursements, extra charges, and event charges
exclude voided/failed/canceled records from management totals
calculate management KPIs
build monthly trends
build receivables aging
build payment method mix
build expense category mix
compare selected school year against another school year
produce CSV rows
return JSON-ready arrays
```

The service must use `Schema::hasTable()` and `Schema::hasColumn()` guards where the legacy schema is inconsistent. This project has legacy compatibility layers, so brittle assumptions are how bugs sneak in wearing a school hoodie.

## 9. Keep Old Financial System Working

In the first pass, do not replace the existing `FinancialSummaryService`, `FinancialReportService`, `PaymentController`, `InvoiceController`, `RefundController`, or PayPal controllers.

Instead:

```text
Reuse existing financial tables.
Reuse existing auth groups.
Add read-only reporting beside the current operations.
Leave existing payment/invoice mutation paths untouched.
Do not force status normalization yet.
Do not force invoice recalculation yet.
```

Once stakeholder reporting is stable, a later phase can introduce a centralized Laravel ledger service behind feature flags.

## 10. Later Laravel Ledger Phase

The old CodeIgniter-style `InvoiceLedgerService` recommendation should become:

```text
app/Services/Finance/InvoiceLedgerService.php
```

It should be introduced behind a config flag:

```text
finance.ledger_mode = legacy|centralized
```

When `legacy`, existing controllers continue using current behavior.
When `centralized`, payment/refund/discount/additional-charge writes call the ledger service after database writes.

Every write should eventually use:

```php
DB::transaction(function () use ($invoiceId) {
    Invoice::query()->whereKey($invoiceId)->lockForUpdate()->firstOrFail();
    // create/update financial record
    $this->invoiceLedgerService->recalculate($invoiceId);
});
```

But do not force that transition before tests exist. Accounting rewrites without tests are just gambling with extra syntax.

## 11. Tuition Forecast Compatibility

The tuition forecast idea from the uploaded plan is valid, but it should be Laravelized:

```text
app/Services/Finance/Tuition/OldTuitionCalculatorService.php
app/Services/Finance/Tuition/NewTuitionCalculatorService.php
app/Services/Finance/Tuition/TuitionForecastService.php
app/Http/Requests/Finance/TuitionForecastRequest.php
app/Http/Controllers/Api/Finance/TuitionForecastController.php
```

The old calculator must remain the default until leadership approves switching invoice generation.

Use:

```text
tuition_calculator_version = old|new
```

stored in the existing `configuration` table through `Configuration::getConfigValueByKey()` / `setConfigValueByKey()`.

## 12. PayPal Compatibility Position

Because the current instruction is to keep the old financial system working, PayPal is not removed in this pass.

Instead:

```text
Keep existing PayPal routes/controllers/models active.
Keep historical PayPal reports available.
Do not drop or rename PayPal tables.
Document PayPal as legacy.
Plan separate decommission only after staff confirms no operational dependency remains.
```

Future PayPal decommission should be its own release with:

```text
route inventory
frontend dependency scan
data archive migration
rollback plan
parent communication
post-release monitoring
```

## 13. Reporting Security

Stakeholder financial reporting should be restricted to authenticated admin/finance users.

Minimum:

```text
Route inside auth:api group.
Route inside administrator/admin finance area when exposed to admin UI.
Add permission middleware later if the permission catalog has a finance-report permission.
```

Preferred future middleware:

```php
->middleware(['auth:api', 'permission:view_financial_reports'])
```

Do not expose stakeholder reports to ordinary parent users. This should not need saying, but software history suggests otherwise.

## 14. Acceptance Criteria

This Laravel-compatible phase is complete when:

```text
Existing finance endpoints still work.
Existing PayPal endpoints still exist.
New stakeholder analysis JSON endpoint works.
New stakeholder analysis CSV endpoint works.
No financial write flow is changed.
No invoice is recalculated by the stakeholder report.
The report handles missing optional legacy columns safely.
The code passes PHP lint.
The plan is stored in docs/ for future implementation work.
```

## 15. Next Hardening Steps

After the additive reporting phase:

```text
Add tests for stakeholder report calculations.
Add permission middleware for finance reports.
Add centralized InvoiceLedgerService behind a feature flag.
Add a dry-run invoice recalculation Artisan command.
Add tuition forecast services.
Normalize financial statuses through migrations only after database backup.
Design a separate PayPal decommission release.
```

The brutal truth: the uploaded plan had the right instincts, but it assumed the wrong framework and moved too aggressively on removal. In this project, compatibility wins first. Then hardening. Then cleanup. In that order, unless the goal is accounting chaos with a nicer namespace.
