# ECOLE ECOIN Architecture Audit

Date: 2026-05-21

Scope: repository-wide architecture audit before new CRM, marketing automation, or WhatsApp AI work. This document is evidence-led and does not prescribe an immediate large refactor.

## Executive Summary

ECOLE ECOIN is no longer a small Laravel CRUD app. The repository already contains:

- 164 migrations and 62 seeders.
- 1,994 indexed source files in CodeGraph.
- 144 Admin Livewire PHP components.
- Three active business-layer styles at once: `app/Application`, `app/Domain`, and global `app/Services`.
- Large mature subsystems for Corporate, WhatsApp, Messaging, Attendance, Finance, SEO/GEO, public enrollment, student portal, org portal, teacher/instructor portals, and admin operations.

The project has meaningful strengths:

- Newer Corporate flows are concentrated under `app/Domain/Corporate/*`.
- Scheduling generation has a domain service in `app/Domain/Scheduling/SessionScheduleGenerator.php`.
- WhatsApp enrollment journey work has a domain service and listeners in `app/Domain/WhatsApp/*` and `app/Listeners/WhatsApp/*`.
- Many sensitive routes already use `auth`, `can:*`, policies, private disks, and tenancy middleware.
- Organization is now the documented canonical corporate tenant model.

The main architectural risk is not lack of code. It is pattern drift. The same repository currently allows workflow logic to live in Domain services, Application use cases, global Services, model hooks, and Livewire components. If CRM and automation are added without a boundary standard, cross-module coupling will accelerate and security review will become expensive.

## Current Module Map

| Area | Main Runtime Surfaces | Main Business Code Today | Notes |
| --- | --- | --- | --- |
| Courses and Cohorts | `app/Livewire/Admin/Courses`, `app/Livewire/Admin/Cohorts`, public catalog | `app/Application/UseCases`, `app/Services`, `app/Domain/Scheduling` | Cohort naming still crosses `Session`, `Cohort`, and `AttendanceSession`. |
| Enrollments | public registration Livewire, student portal, WhatsApp journey | `app/Application/UseCases/Registration`, `app/Services/Workflow`, `app/Domain/WhatsApp` | Public and WhatsApp entry paths converge on registration/payment concerns. |
| Payments and Finance | finance/admin Livewire, payment policies, receipts | `app/Services/Finance`, `app/Domain/Finance`, Corporate invoicing services | Individual and corporate finance are related but must remain separate ownership tracks. |
| Attendance | teacher/admin Livewire | `app/Domain/Attendance`, `app/Domain/Scheduling`, models and policies | Corporate and student attendance share sessions but not all record types. |
| WhatsApp and Messaging | webhook, inbox, outbox, templates | `app/Domain/WhatsApp`, `app/Domain/Messaging`, jobs/listeners | Good service base exists; admin conversation UI still performs some writes directly. |
| Corporate | B2B, organizations, proposals, agreements, cohorts, reports, invoices | `app/Domain/Corporate/*` | Strongest current modular grouping. Must remain anchored to `Organization`. |
| SEO/GEO/Tracking | public layouts, controllers, support builders | `app/Support/Seo`, tracking controllers/models | Cross-cutting concern; should not leak into domain workflows as ad hoc script calls. |
| Portals | Student, Org, Teacher/Instructor, Admin | route files, middleware, policies, Livewire | Route/middleware quality varies by portal and namespace. |

## Evidence Gathered

### Structural counts

- `app/Domain`: 89 PHP files.
- `app/Services`: 88 PHP files.
- `app/Application`: 206 PHP files.
- `app/Domain/Corporate`: 32 PHP files with grouped Agreement, Proposal, Cohort, Trainee, Report, and Invoicing services.
- `app/Events`: only six formal event classes in `Finance` and `Messaging`, while several workflows already have notifications, activities, jobs, and observers.

### Large files requiring attention

| File | Approx. lines | Why it matters |
| --- | ---: | --- |
| `app/Support/Rbac/PermissionCatalog.php` | 842 | Security vocabulary is centralized but now large enough to need domain grouping and architecture checks. |
| `app/Livewire/Admin/Cohorts/AdminCohortSessionsPage.php` | 593 | UI component owns form state, preview orchestration, manual session persistence, conflict checks, audit, cancellation, and earning trigger. |
| `app/Livewire/Admin/Registrations/AdminRegistrationCreatePage.php` | 590 | High-risk enrollment creation surface with broad form/workflow responsibilities. |
| `app/Livewire/Admin/Finance/ExpensesManagerPage.php` | 425 | Finance UI component is becoming a workflow boundary. |
| `app/Services/Billing/BillingPlanFactory.php` | 416 | Billing policy complexity is concentrated in a general service. |
| `app/Livewire/Registration/RegistrationStepper.php` | 415 | Public registration workflow surface remains large. |
| `app/Domain/Scheduling/SessionScheduleGenerator.php` | 363 | This is domain logic and size is more acceptable, but it is a central dependency. |
| `app/Domain/WhatsApp/WhatsAppEnrollmentJourneyService.php` | 325 | Important cross-module orchestration point. |

## Findings

### 1. Business logic still exists directly inside Livewire

Confirmed examples:

- `app/Livewire/Admin/B2B/B2BLeadDetailPage.php`
  - `convert()` performs organization matching, creates an `Organization`, mutates the lead, records activity, and writes `OrgAuditLog` inside a `DB::transaction`.
- `app/Livewire/Admin/WhatsApp/ConversationPage.php`
  - `createB2bLead()` creates B2B leads and changes conversation linkage.
  - `sendReply()` writes `WhatsAppMessage`, calls a provider, handles hybrid fallback, creates outbox records, and updates conversation/contact timestamps.
- `app/Livewire/Admin/Cohorts/AdminCohortSessionsPage.php`
  - `saveSession()` calculates datetimes/duration, validates conflicts, resolves labels/rooms, persists `AttendanceSession`, and audits.
  - `cancelSession()` and `reopenSession()` enforce workflow rules in the component.
- Additional write-heavy UI examples exist in `Instructor/AttendanceTakingPage`, `Instructor/CourseMaterialsManager`, `Student/Payments`, Org member/enrollment pages, and some finance pages.

Risk:

- UI behavior, transactions, auditing, and business workflow can diverge between web UI, future APIs, jobs, and automation.

Recommended boundary:

- Keep Livewire limited to authorization, validation, UI state, and calls into Actions/Services.

### 2. The service layer is not yet canonical

Current coexistence:

- `app/Application/UseCases/*`
- `app/UseCases/*`
- `app/Domain/*`
- `app/Services/*`

Examples of pattern drift:

- Registration logic exists across `app/Application/UseCases/Registration`, `app/Services/Workflow`, `app/Domain/Services/RegistrationStatusMachine.php`, and public Livewire surfaces.
- Finance work is split across `app/Domain/Finance`, `app/Services/Finance`, `app/Livewire/Finance`, `app/Livewire/Admin/Finance`, and `app/Livewire/FinanceReports`.
- Conflict detection has both `app/Services/InstructorConflictDetector.php` and `app/Services/TeacherConflictDetector.php`, with overlapping instructor schedule responsibilities but different APIs and policies.

Risk:

- New engineers will add the next workflow wherever a nearby example happens to live.

### 3. Cross-module coupling is already real

Expected coupling is present, but it needs ownership rules:

- WhatsApp journey touches contacts, conversations, courses, course sessions, registrations, enrollments, deposits, outbox, and payment confirmation listeners.
- Corporate invoicing touches organization, agreement, corporate cohort, report, payment allocation, receivables, financial transactions, PDFs, and notifications.
- Scheduling touches cohort/session ownership, rooms, instructors, attendance sessions, attendance locking, and instructor earnings.

Positive evidence:

- Corporate largely centralizes this orchestration under `app/Domain/Corporate/*`.
- WhatsApp has domain services and listener entry points for payment confirmation.

Gap:

- Coupling is not yet governed by a published rule saying which module owns writes and which modules may only call public Actions or react to Events.

### 4. Query scoping and authorization responsibility still lives in UI in places

This audit found strong authorization patterns and several hotspots to standardize.

Good examples:

- Corporate document download routes in `routes/web.php` use policy middleware such as `can:download,invoice`, `can:downloadFiles,agreement`, and `can:downloadReceipt,payment`.
- Org routes in `routes/org.php` run behind `auth`, `org_portal`, and `tenancy` through `bootstrap/app.php`.
- Teacher routes in `routes/instructor.php` run behind `auth` and `InstructorPortalMiddleware`.

Hotspots:

- `app/Livewire/Admin/Events/EventIndexPage.php` authorizes in `mount()` and then applies host scoping inside `render()`, while the corresponding route in `routes/web.php` is only in an `auth` admin group and does not show the permission in the route declaration.
- `app/Livewire/Admin/CorporateInvoices/CorporateInvoiceCreatePage.php` selects organizations, agreements, and corporate cohorts directly inside the component render path.
- `app/Livewire/Admin/WhatsApp/ConversationPage.php` loads up to 150 cohorts directly for recommendations.
- A repository scan lists many Admin, Org, Instructor, and Student Livewire components with no literal `authorize()` or `Gate::authorize()` call. Some are legitimately protected by route middleware or are read-only, but the absence of a consistent page/action authorization convention makes review harder.

Risk:

- IDOR review becomes component-by-component instead of being enforced by query objects, policies, and tests.

### 5. Route security is improving but inconsistent

Observed route styles:

- Many routes use explicit `auth + can:*`.
- Some admin routes use `EnsureUserIsAdmin` and then no route-level permission for settings, reports, guide, audit, health, and notification surfaces.
- Some admin event routes rely on policy checks in Livewire rather than an explicit route permission.
- Student and Org route files depend on route-group middleware configured in `bootstrap/app.php`; this is valid but less obvious during isolated route-file review.

Risk:

- Security review must understand three route protection styles instead of one baseline.

### 6. Naming and portal surfaces are not fully unified

Examples:

- Teacher operations exist in both `teacher` and `instructor` route prefixes and in both `app/Livewire/Teacher` and `app/Livewire/Instructor`.
- Cohort ownership is represented through `Session`, `Cohort`, `course_sessions`, and `AttendanceSession` names.
- Corporate uses `Organization` as canonical tenant, but old `companies` migrations still exist for compatibility.

Risk:

- Domain language drift leaks into routes, policies, DTOs, reports, and tests.

### 7. Legacy references still exist and must stay quarantined

Confirmed legacy artifacts:

- `database/migrations/2026_02_14_143000_create_companies_table.php`
- `database/migrations/2026_02_14_143500_add_company_id_to_users_table.php`
- `companies.agreement_path`

Current audit did not find active new Corporate runtime use of `App\Models\Company`. Existing Corporate documentation already states:

- `Organization` is canonical.
- `companies`, `users.company_id`, and `agreement_path` are compatibility-only.

Required guard:

- Automated checks should keep these strings out of new application code except migrations/docs/explicit compatibility tests.

### 8. Hardcoded UI text remains a material cleanup stream

`docs/i18n_coverage_report.md` currently reports:

- Missing AR/FR keys: 0.
- Empty/TODO keys: 0.
- Potential hardcoded UI strings: 678.
- Potential unused keys: 1,947.

High-signal examples in the report still include Admin audit, notification, finance, certificate designer, export, and payment review views.

Risk:

- UX consistency and localization quality degrade even when key coverage is technically complete.

### 9. Jobs, Events, and listeners are useful but not yet a domain-event architecture

Observed:

- Jobs are split under generic root jobs and domain-ish folders such as `Jobs/Messaging`, `Jobs/WhatsApp`, and `Jobs/Events`.
- Events currently cover messaging and instructor earnings only.
- Corporate workflows often use activities/notifications/services directly instead of formal domain events.

Missing readiness items for future automation:

- Stable domain event contracts for core lifecycle milestones.
- After-commit dispatch rule for transactional workflows.
- Listener ownership and idempotency requirements.
- Event payload privacy rule.

### 10. Test coverage exists, but architecture guardrails are still mostly implicit

Existing feature suites cover Corporate, WhatsApp, RBAC, SEO, student, teacher, and admin surfaces. The repository also already has `I18nCheckCommand`.

Missing architecture-specific guardrails:

- Legacy reference check for forbidden Corporate model terms.
- Admin route permission baseline check.
- Livewire sensitive action authorization inventory.
- Module dependency/boundary smoke tests.
- Sensitive file delivery inventory check.

## Risk Register

| Risk | Severity | Evidence | Immediate action |
| --- | --- | --- | --- |
| Sensitive workflow logic in UI components | High | B2B convert, WhatsApp reply, session manual persistence | Move future workflows into Actions first; refactor existing high-risk pages by roadmap. |
| Mixed business-layer conventions | High | `Application`, `Domain`, `Services`, `UseCases` coexist | Publish one standard now and require new work to follow it. |
| Route permission style drift | High | multiple admin route groups with different protection patterns | Add route baseline and architecture check before new admin modules. |
| Portal naming drift | Medium | Teacher/Instructor and Session/Cohort overlap | Declare canonical naming and migration policy. |
| Automation without stable events | Medium | few formal domain events despite many workflows | Introduce event catalog before marketing automation. |
| Localization debt | Medium | hardcoded report still has 678 candidates | Keep i18n cleanup as bounded roadmap stream. |

## Audit Conclusion

The platform is feature-rich and has several modern module foundations already. The safest next move is not a rewrite. It is to freeze architectural rules for new work, protect security boundaries with automated checks, and refactor the highest-risk UI workflows into explicit Actions in narrow slices.
