# Corporate / Institutions Module Audit

Date: 2026-05-08

Scope: current code evidence only. No new implementation was performed.

## Executive Status

Overall status: **Partial**

The platform already contains a meaningful B2B foundation under the `organizations` and `org_*` tables, plus an authenticated organization portal under `/org/*` and a public lead capture page under `/b2b`. However, the corporate module is not yet a complete business workflow. It currently has two competing data tracks:

- **Current B2B track:** `organizations`, `organization_members`, `org_employees`, `org_enrollments`, `org_audit_logs`.
- **Legacy/incomplete company track:** `companies`, `users.company_id`, `agreement_path`, and `Admin\Corporate\CorporateDashboard` referencing `App\Models\Company`, but no `Company` model was found.

Recommendation: continue from the `Organization` model as the source of truth and migrate/retire the legacy `Company` track instead of building new features on both.

## Feature Matrix

| Feature | Status | Evidence | Assessment |
|---|---:|---|---|
| 1. Institutions / Organizations | Partial | `database/migrations/2026_01_19_163000_create_b2b_portal_tables.php`, `app/Models/Organization.php`, `app/Livewire/Org/*` | Core organization table, members, employees, enrollments, and audit logs exist. Admin CRUD is fragmented because `Admin\Corporate\CorporateDashboard` uses `Company`, not `Organization`. |
| 2. Corporate Requests | Partial | `database/migrations/2026_01_26_000000_create_b2b_leads_table.php`, `app/Models/B2bLead.php`, `app/Livewire/Public/B2B/LandingPage.php` | Public request form exists and saves leads. No admin lifecycle for lead qualification, conversion to organization, proposal, agreement, or follow-up workflow was found. |
| 3. Proposals / Offers | Partial | `database/migrations/2026_04_10_110100_create_event_offers_table.php`, `app/Models/EventOffer.php`, `app/Livewire/Admin/Events/EventOfferBuilderPage.php` | Event offers exist, but they are marketing/event offers, not corporate proposals. No B2B proposal model/table/workflow found. |
| 4. Agreements | Partial/Missing | `database/migrations/2026_02_14_143000_create_companies_table.php` has `agreement_path` | Only a single `agreement_path` exists on legacy `companies`. No `agreements` table, status lifecycle, signature metadata, versioning, renewal, or relation to `organizations`. |
| 5. Corporate Cohorts | Partial | `org_enrollments.cohort_id` references `course_sessions`; `app/Models/OrgEnrollment.php`; `app/Livewire/Org/OrgEnrollmentsIndexPage.php` | Org employees can be mapped to courses/cohorts, but there is no explicit corporate cohort type, organization-owned cohort relation, quota, pricing, corporate schedule, or attendance/report bridge. |
| 6. Excel Trainee Upload | Missing | `resources/views/livewire/org/org-employees-index-page.blade.php` shows disabled `import_csv`; `composer.json` has no Excel package | UI mentions CSV import as coming soon. No Maatwebsite Excel / PhpSpreadsheet dependency and no import service/job was found. |
| 7. Corporate Reports | Partial/Missing | `app/Livewire/Org/OrgReportsPage.php` returns placeholder features; `resources/views/livewire/org/org-placeholder-page.blade.php` | Reports page exists as placeholder only. No real attendance/progress/certificate/export reports scoped to organization were found. |
| 8. Finance & Payments | Partial/Missing | `app/Livewire/Org/OrgInvoicesPage.php` placeholder; `companies.agreement_path`; payment module exists globally | Core payment/finance module exists for individual enrollments, but no corporate invoice, organization payment plan, contract billing, purchase order, receivable, or organization payment linkage found. |
| 9. Telegram / WhatsApp Notifications | Reusable but not integrated | `app/Domain/WhatsApp/*`, `app/Domain/Messaging/*`, `app/Jobs/Messaging/*` | Messaging infrastructure exists for enrollments/events. No organization-specific message contexts/templates/triggers for B2B leads, invitations, invoices, employee enrollments, or reports found. |
| 10. Public Pages `/corporate` | Partial | `/b2b` route and `/institutions` redirect in `routes/web.php`; `lang/ar/b2b.php`, `lang/fr/b2b.php` | Public B2B page exists at `/b2b`; `/institutions` redirects to it. No `/corporate` route was found. |

## Existing Files By Area

### Database

- `database/migrations/2026_01_19_163000_create_b2b_portal_tables.php`
- `database/migrations/2026_01_25_132612_create_org_dsar_tables.php`
- `database/migrations/2026_01_25_163515_add_encrypted_fields_to_users_and_employees.php`
- `database/migrations/2026_01_26_000000_create_b2b_leads_table.php`
- `database/migrations/2026_02_14_143000_create_companies_table.php`
- `database/migrations/2026_02_14_143500_add_company_id_to_users_table.php`
- `database/migrations/2026_04_10_110100_create_event_offers_table.php`

### Models

- `app/Models/Organization.php`
- `app/Models/OrganizationMember.php`
- `app/Models/OrganizationInvitation.php`
- `app/Models/OrgEmployee.php`
- `app/Models/OrgEnrollment.php`
- `app/Models/OrgAuditLog.php`
- `app/Models/B2bLead.php`
- `app/Models/EventOffer.php`

Important gap: `app/Models/Company.php` was not found, although `app/Livewire/Admin/Corporate/CorporateDashboard.php` imports `App\Models\Company`.

### Routes

- Public B2B: `routes/web.php` defines `/b2b` and redirects `/institutions`.
- Organization portal: `routes/org.php`, loaded from `bootstrap/app.php` with `web`, `auth`, and `org_portal`.
- API: `routes/api.php` currently exposes event endpoints only; no corporate API endpoints found.

### Livewire / Views

- Public lead page: `app/Livewire/Public/B2B/LandingPage.php`, `resources/views/livewire/public/b2b/landing-page.blade.php`.
- Organization portal: `app/Livewire/Org/*`, `resources/views/livewire/org/*`.
- Organization layout: `resources/views/layouts/org.blade.php`, `resources/views/layouts/partials/org-sidebar.blade.php`, `resources/views/layouts/partials/org-topbar.blade.php`.
- Admin corporate page: `app/Livewire/Admin/Corporate/CorporateDashboard.php`, `resources/views/livewire/admin/corporate/corporate-dashboard.blade.php`.

### Security / Tenancy

- `app/Http/Middleware/OrgPortalMiddleware.php`
- `app/Http/Middleware/TenancyMiddleware.php`
- `app/Traits/BelongsToOrganization.php`
- `app/Providers/OrgServiceProvider.php`
- `app/Helpers/org_helper.php`

No dedicated policies were found for `Organization`, `OrgEmployee`, `OrgEnrollment`, `OrganizationMember`, `OrganizationInvitation`, or `B2bLead`.

### Seeders / i18n

- `database/seeders/OrgPermissionsSeeder.php`
- `database/seeders/B2BPortalSeeder.php`
- `lang/ar/org.php`, `lang/fr/org.php`
- `lang/ar/b2b.php`, `lang/fr/b2b.php`

## Workflow As-Is

### Public Corporate Request

1. Visitor opens `/b2b`.
2. `App\Livewire\Public\B2B\LandingPage` validates institution/contact fields.
3. It creates a `B2bLead` with `meta_json` containing source, user agent, and IP address.
4. The page displays a success state.

Gaps:

- No phone normalization.
- No rate limit specific to B2B lead submission.
- No duplicate lead policy.
- No admin queue to qualify/contact/convert the lead.
- No WhatsApp/Telegram notification trigger to staff or requester.

### Organization Portal

1. Authenticated member enters `/org/select`.
2. `OrgSelectOrganizationPage` stores `selected_organization_id` and `org_member_role` in session.
3. `/org/*` routes are protected by `OrgPortalMiddleware` and `TenancyMiddleware`.
4. Models using `BelongsToOrganization` are globally scoped by `app('organization_id')`.
5. Pages support dashboard stats, employee CRUD, enrollment requests, member invitations, and organization settings.

Gaps:

- `TenancyMiddleware` only checks that an organization ID exists in session; it does not verify that the authenticated user still belongs to that organization.
- `OrgSelectOrganizationPage::selectOrganization($id, $role)` accepts IDs from the UI and does not re-check membership before setting the session.
- Permissions are checked on some actions, but page-level authorization is incomplete.
- Many strings remain hardcoded in English/French in Livewire components and Blade.
- Reports, invoices, support, and programs are placeholders.

### Admin Corporate

`Admin\Corporate\CorporateDashboard` appears intended for company management, but:

- It imports `App\Models\Company`.
- No `Company` model was found.
- No route to this component was found in `routes/web.php`.
- It uses `companies` instead of `organizations`.

Assessment: this area needs redesign or migration to the `Organization` model before use.

## Reusable Building Blocks

Reuse these instead of rebuilding:

- **Organization tenant base:** `Organization`, `OrganizationMember`, `OrgEmployee`, `OrgEnrollment`, `BelongsToOrganization`.
- **Audit style:** `OrgAuditLog`, global `AuditService`, security event logger.
- **Messaging stack:** `Domain\WhatsApp`, `Domain\Messaging`, `MessageTemplate`, `OutboxMessage`, `MessageLog`.
- **Finance stack:** `Payment`, `PaymentPlan`, `PaymentScheduleItem`, `FinancialTransaction`, finance audit logs.
- **Scheduling/attendance:** `Session`, `AttendanceSession`, scheduling generator, attendance workflow.
- **RBAC:** Spatie roles/permissions and current `OrgPermissionsSeeder`.
- **Public SEO/i18n:** `b2b.php`, `seo.php`, public layout.

## What Must Not Be Broken

- Do not break the existing individual enrollment/payment workflow.
- Do not remove `org_*` data or the `BelongsToOrganization` global scope without a migration plan.
- Do not build corporate billing on the legacy `companies` table until the `Company` vs `Organization` decision is resolved.
- Do not expose org employees without tenant membership verification.
- Do not make Excel upload write directly to `users` unless the business decision is to convert employees into student accounts.
- Do not reuse event offers as corporate proposals without separating naming and lifecycle.

## UX / Design Audit

### Public B2B Page

Status: Partial, visually present.

Strengths:

- Strong marketing page with AR/FR content.
- Lead form exists.
- Clear positioning for institutions.

Gaps:

- Uses external images from `freepik.com`, which is risky for performance, privacy, and branding consistency.
- Form does not use strong phone normalization or duplicate feedback.
- No `/corporate` alias.

### Organization Portal

Status: Partial.

Strengths:

- Dedicated `layouts.org`.
- Sidebar/topbar exist.
- Employee and enrollment pages exist.

Gaps:

- Several pages are placeholders.
- Some hardcoded strings exist, such as `Switch Org`, `Logout`, `Employee status updated.`, `Enrollment request submitted.`, and placeholder feature text.
- Import button is disabled.
- Reports/invoices/programs are not operational.

### Admin Corporate

Status: Missing/needs redesign.

The page is not integrated with current `Organization` architecture and likely cannot run because `Company` model is absent.

## Security / RBAC Audit

Status: Partial.

What exists:

- `OrgPermissionsSeeder` defines `org.dashboard.view`, `org.employees.*`, `org.members.*`, `org.enrollments.*`, `org.reports.*`, `org.invoices.*`, `org.settings.*`.
- `OrgPortalMiddleware` requires an organization membership or legacy `super_admin`.
- `BelongsToOrganization` scopes org models by selected organization ID.

Risks:

- Session-selected organization ID must be validated against active membership on every request.
- `OrgSelectOrganizationPage::selectOrganization($id, $role)` can trust UI-provided parameters too much.
- No model policies found for org entities.
- Some list/detail routes rely on global scope, but `OrganizationMember` and invitation workflows need explicit access rules.
- `OrgAuditLog::log()` does not always include `organization_id` unless tenant context is correctly set.

Required security baseline before expansion:

- Add policies for `Organization`, `OrgEmployee`, `OrgEnrollment`, `OrganizationMember`, `OrganizationInvitation`, `B2bLead`.
- Harden `TenancyMiddleware` to verify selected org membership and status.
- Do page-level `authorize()` in every Org Livewire page.
- Add rate limiting to B2B lead form.
- Normalize and validate phone numbers.

## Recommended Data Direction

Use this as source of truth:

- `organizations`
- `organization_members`
- `org_employees`
- `org_enrollments`

Avoid expanding this without a migration decision:

- `companies`
- `users.company_id`
- `agreement_path` on `companies`

Recommended consolidation:

- Create a migration path from `companies` to `organizations` if production data exists.
- Add agreement/proposal/invoice tables linked to `organization_id`.
- Keep `Company` only as a temporary compatibility model if needed, not as the future design.

## Proposed Implementation Plan

### Phase 1: Stabilize Existing B2B Core

- Add `Company` compatibility decision: either create a read-only compatibility model or migrate `companies` into `organizations`.
- Add `/corporate` route alias to `/b2b`.
- Harden org tenancy membership checks.
- Add policies for organization entities.
- Localize hardcoded org UI strings.
- Add admin B2B lead queue: list, status, notes, assign owner.

### Phase 2: Convert Lead to Organization

- Add admin workflow to convert `B2bLead` into `Organization`.
- Add lead statuses: new, contacted, qualified, proposal_sent, converted, lost.
- Add audit logs and notifications for lead lifecycle.
- Add duplicate detection by professional email, phone, institution name.

### Phase 3: Corporate Proposals and Agreements

- Add `corporate_proposals` linked to `organization_id` and/or `b2b_lead_id`.
- Add proposal lines: courses, cohorts, custom training, seat count, price, validity.
- Add `corporate_agreements` with files, versions, status, signed dates, expiry.
- Keep event offers separate.

### Phase 4: Corporate Cohorts and Bulk Trainees

- Add `organization_id` to corporate cohorts or add a pivot `organization_cohorts`.
- Implement Excel import with validation preview, duplicate report, and rollback-safe import.
- Decide whether `OrgEmployee` becomes a `User` only after enrollment confirmation or remains a directory-only entity.

### Phase 5: Finance Integration

- Add corporate invoice model and organization receivables.
- Link invoice lines to org enrollments/cohorts.
- Reuse existing payment/financial transaction services where possible.
- Add corporate payment status and receipt upload/review if needed.

### Phase 6: Reports, Messaging, and Portal Polish

- Build real organization reports: attendance, progress, certificates, financial status.
- Add Excel/PDF exports.
- Add WhatsApp/Telegram/email templates for B2B leads, proposals, invitations, invoice reminders, employee enrollments.
- Replace placeholders in programs/reports/invoices/support.

## Immediate Next-Step Recommendation

Before implementing new B2B features, fix the foundation:

1. Decide and document **Organization vs Company** as the canonical model.
2. Harden tenancy authorization.
3. Add admin management for B2B leads.
4. Implement corporate agreements/proposals as new `organization_id`-based modules.
5. Only after that, implement Excel trainee upload and corporate billing.

