# FieldOps Cloud Business Logic and Testing Dossier

Date: 2026-06-15

## 1. Product Summary

FieldOps Cloud is a PHP 8+ SaaS field service management platform for trade businesses such as plumbers, electricians, HVAC teams, facilities teams, maintenance contractors, cleaning operators, and similar service organisations.

The current build is a local XAMPP-friendly product prototype with a SaaS platform-admin layer. It has a dense operations-console UI inspired by a prior Replit interface and competitor research against products such as ServiceM8, Jobber, Housecall Pro, ServiceTitan, FieldPulse, Workiz, Fieldpoint, and other field-service platforms.

The current app supports:

- Public landing, auth/demo entry, and pricing pages.
- Company-admin demo operations: dashboard, clients, jobs, schedule, quotes, invoices, AI assistant, import/export, team, reports, settings, accounting settings, calendar settings, LLM settings, workspace, and subscription.
- Platform-admin console under `/platform-admin`: dashboard, tenants, tenant detail, billing, plans, audit logs, demo management, import/export admin, system health, support access, security admin, and mobile/offline status.
- Local demo/session-backed create/edit behavior for quotes, invoices, clients, team members, schedule appointments, pricing settings, platform tenant overrides, platform billing overrides, and platform accounting sandbox links.
- Production-oriented database schema foundations for tenants, users, clients, jobs, quotes, invoices, inventory, customer portal tokens, automation rules, reports, accounting, calendar, LLM, imports, exports, audit logs, and SaaS billing.

The testing architect should treat this as a hybrid state:

- Some user journeys are implemented and testable end to end locally.
- Some areas are UI and service-contract foundations only.
- Some capabilities exist only as database schema and roadmap intent.

Do not write a test plan that assumes live third-party provider calls or production billing unless the corresponding controller/service/worker path is implemented.

## 2. Repository Classification

This is a custom PHP MVC app that uses Yii3 components where useful. It is not a full Yii app skeleton.

Key traits:

- Front controller and route table: `public/index.php`.
- Runtime router: `src/Application/App.php`.
- HTTP request abstraction: `src/Infrastructure/Http/Request.php`.
- HTTP response abstraction: `src/Infrastructure/Http/Response.php`.
- Renderer: `src/Infrastructure/View/PhpRenderer.php`.
- Session auth and support impersonation: `src/Infrastructure/Security/SessionAuth.php`.
- RBAC: `src/Infrastructure/Security/RbacService.php`.
- CSRF: `src/Infrastructure/Security/CsrfService.php`.
- Business logic: `src/*/Service`.
- Persistence boundaries: `src/*/Repository`.
- Bootstrap 5 templates: `templates`.
- Production schema: `database/schema.sql`.
- Local seed data: `database/seed.sql`.
- Current tests: `tests/run.php`.

Required architecture from `AGENTS.md`:

- Controller -> Service -> Repository -> Database.
- HTTP handling belongs in controllers.
- Business validation and rules belong in services.
- Persistence belongs in repositories.
- Views/templates must not perform database queries or business logic.

## 3. Local Environment and Setup

Primary local stack:

- Windows
- XAMPP Apache/MySQL
- PHP 8.1+
- Composer
- MySQL database `fieldops_cloud`

Recommended served path:

```text
C:\xampp\htdocs\FieldOps-Cloud
```

Recommended local URL:

```text
http://localhost/FieldOps-Cloud/public/
```

Fresh setup:

1. Copy or clone the project to `C:\xampp\htdocs\FieldOps-Cloud`.
2. Run `composer install`.
3. Copy `.env.example` to `.env`.
4. Configure DB credentials.
5. Create database `fieldops_cloud`.
6. Import `database/schema.sql`.
7. Import `database/seed.sql`.
8. Open the app at the local URL above.

Local documented seed credentials:

- `owner@example.test` / `FieldOps123`
- `demo@example.test` / `FieldOps123`

Important current behavior: `public/index.php` auto-logs in a demo owner if there is no session user. That is acceptable for local demo but must be disabled or production-gated before production release. A test plan should include both the current demo-mode expectation and a future production-mode no-auto-login gate.

## 4. Request Lifecycle

1. Apache routes all app requests to `public/index.php`.
2. `session_start()` runs.
3. `APP_BASE_URL` is derived from `SCRIPT_NAME`, for example `/FieldOps-Cloud/public` under XAMPP.
4. Composer autoload is used when `vendor/autoload.php` exists; otherwise a fallback PSR-4 autoloader loads `FieldOps\` classes from `src/`.
5. Security headers are sent:
   - `X-Frame-Options: SAMEORIGIN`
   - `X-Content-Type-Options: nosniff`
   - `Referrer-Policy: strict-origin-when-cross-origin`
6. If no user is in session, demo owner is auto-logged in.
7. Controllers, services, and repositories are constructed.
8. `App` receives a map of exact `METHOD /path` route keys.
9. `Request::path()` strips the XAMPP base path.
10. `App` rejects non-API POST requests with missing or invalid `_csrf`.
11. The controller action is invoked.
12. Controllers call `SessionAuth::requirePermission()` for RBAC.
13. `RuntimeException('Forbidden')` is converted to a 403 response.
14. Templates are rendered by `PhpRenderer`.
15. `PhpRenderer` prefixes root-relative `href="/..."` and `action="/..."` with `APP_BASE_URL` so links/forms work under XAMPP.

Testing implications:

- Base-path handling is a critical regression area.
- Every POST form must include a valid CSRF token.
- Every protected route must be tested with allowed and denied roles.
- Every link and form action must be tested under `/FieldOps-Cloud/public`, not only at web root.

## 5. Current Roles and Permissions

RBAC is implemented in `src/Infrastructure/Security/RbacService.php`.

Roles:

- `owner`
- `platform_admin`
- `manager`
- `supervisor`
- `team_member`
- `trainee`
- `demo_user`

Important permission groups:

- Operations: `view_dashboard`, `view_clients`, `create_clients`, `update_clients`, `delete_clients`, `view_jobs`, `create_jobs`, `update_jobs`, `delete_jobs`, `view_quotes`, `create_quotes`, `update_quotes`, `delete_quotes`, `view_invoices`, `create_invoices`, `update_invoices`, `delete_invoices`, `view_schedule`, `view_team`, `manage_team`, `view_reports`, `view_settings`, `manage_settings`.
- AI and data: `use_ai_assistant`, `import_csv`, `export_csv`.
- Integrations: `view_accounting_integrations`, `view_calendar_integrations`, `manage_calendar_integrations`, `view_llm_settings`, `manage_llm_settings`.
- SaaS tenant controls: `manage_tenant`, `manage_billing`.
- Platform controls: `view_platform_admin`, `manage_platform_admin`, `manage_platform_pricing`, `manage_platform_billing`, `impersonate_tenants`.
- API: `access_api`.

High-level permission expectations:

- `owner` has broad tenant operations permissions but must not have platform-admin permissions.
- `platform_admin` has platform, pricing, billing, impersonation, integration, and SaaS permissions but does not have the full normal tenant CRUD permission set.
- `manager` has broad tenant operations and integration-management permissions but not platform-admin permissions.
- `supervisor` can view/create/update selected operational records and export CSV, but not delete or manage platform admin.
- `team_member` can view most operational screens and update jobs, but cannot create quotes/invoices/clients or manage team.
- `trainee` is highly restricted to dashboard, clients, and jobs view.
- `demo_user` has a broad demo showcase set but still does not have platform-admin permissions.

Test plan must verify server-side permission behavior, not only hidden UI links.

## 6. Current Route Map

All routes are defined in `public/index.php`. Current active routes:

| Method | Route | Main handler | Business purpose |
| --- | --- | --- | --- |
| GET | `/` | `SiteController::landing` | Public landing page |
| GET | `/auth` | `SiteController::auth` | Demo/auth entry page |
| GET | `/demo` | `SiteController::demo` | Demo overview |
| GET | `/demo/company-admin` | inline session switch | Switch to tenant company admin demo |
| GET | `/demo/platform-admin` | inline session switch | Switch to platform admin demo |
| GET | `/pricing` | `SaasController::pricing` | Public pricing by currency |
| GET | `/dashboard` | `DashboardController::index` | Operations dashboard |
| GET | `/platform-admin` | `PlatformAdminController::index` | Platform admin dashboard |
| POST | `/platform-admin/tenants/login` | `PlatformAdminController::loginAsTenant` | Support login-as tenant admin |
| GET | `/platform-admin/tenants` | `PlatformAdminController::tenants` | Tenant admin list |
| GET | `/platform-admin/tenants/view` | `PlatformAdminController::tenantView` | Tenant detail |
| POST | `/platform-admin/tenants/action` | `PlatformAdminController::tenantAction` | Suspend/resume/trial/plan/grace/payment/export actions |
| GET | `/platform-admin/tenants/return` | `PlatformAdminController::returnToPlatformAdmin` | Return from support session |
| POST | `/platform-admin/tenants/return` | `PlatformAdminController::returnToPlatformAdmin` | Return from support session |
| POST | `/platform-admin/billing/override` | `PlatformAdminController::applyBillingOverride` | Manual platform invoice override |
| POST | `/platform-admin/accounting/connect` | `PlatformAdminController::connectAccounting` | Platform accounting sandbox connection |
| POST | `/platform-admin/demo/reset` | `PlatformAdminController::resetDemo` | Explicit demo reset |
| GET | `/platform-admin/health` | `PlatformAdminController::health` | Dedicated system health |
| GET | `/platform-admin/security` | `PlatformAdminController::security` | Security admin |
| GET | `/platform-admin/mobile-offline` | `PlatformAdminController::mobileOffline` | Mobile/offline admin status |
| GET | `/clients` | `OperationsController::clients` | Client list/search |
| GET | `/clients/view` | `ClientController::view` | Client detail by query name |
| GET | `/clients/edit` | `ClientController::edit` | Client edit form by query name |
| GET | `/clients/create` | `ClientController::create` | New client form |
| POST | `/clients` | `ClientController::store` | Create demo/session client |
| POST | `/clients/update` | `ClientController::update` | Save demo/session client edit |
| GET | `/jobs` | `JobController::index` | Job list/search/filter |
| GET | `/jobs/view` | `JobController::view` | Job detail by query id |
| GET | `/jobs/create` | `JobController::create` | New job form |
| GET | `/jobs/new` | `JobController::create` | New job alias |
| POST | `/jobs` | `JobController::store` | Create job through job service |
| GET | `/schedule` | `ScheduleController::index` | Week calendar and booking panel |
| POST | `/schedule/appointments` | `ScheduleController::store` | Book appointment/meeting/reminder/job |
| GET | `/quotes` | `OperationsController::quotes` | Quote list/search/filter |
| GET | `/quotes/view` | `QuoteController::view` | Quote detail by number |
| GET | `/quotes/edit` | `QuoteController::edit` | Draft quote edit form |
| GET | `/quotes/create` | `QuoteController::create` | New quote form |
| GET | `/quotes/new` | `QuoteController::create` | New quote alias |
| POST | `/quotes` | `QuoteController::store` | Create demo/session quote |
| POST | `/quotes/update` | `QuoteController::update` | Save draft quote edit |
| GET | `/invoices` | `OperationsController::invoices` | Invoice list/search/filter |
| GET | `/invoices/view` | `InvoiceController::view` | Invoice detail by number |
| GET | `/invoices/edit` | `InvoiceController::edit` | Unpaid invoice edit form |
| GET | `/invoices/create` | `InvoiceController::create` | New invoice form |
| GET | `/invoices/new` | `InvoiceController::create` | New invoice alias |
| POST | `/invoices` | `InvoiceController::store` | Create demo/session invoice |
| POST | `/invoices/update` | `InvoiceController::update` | Save unpaid invoice edit |
| GET | `/ai-assistant` | `OperationsController::aiAssistant` | Demo AI assistant UI |
| GET | `/import-data` | `OperationsController::importData` | Import schema/validation workflow |
| GET | `/import` | `OperationsController::importData` | Import alias |
| GET | `/import-data/sample` | `OperationsController::importSampleCsv` | Sample CSV download |
| GET | `/export-data` | `OperationsController::exportData` | Export selection UI |
| GET | `/export` | `OperationsController::exportData` | Export alias |
| GET | `/export-data/download` | `OperationsController::exportDownload` | CSV download |
| GET | `/team` | `OperationsController::team` | Team list |
| GET | `/team/view` | `TeamController::view` | Team member detail by email |
| GET | `/team/edit` | `TeamController::edit` | Team member edit form by email |
| POST | `/team/update` | `TeamController::update` | Save team member edit |
| GET | `/reports` | `OperationsController::reports` | Report cards |
| GET | `/settings` | `OperationsController::settings` | Profile/settings UI |
| POST | `/logout` | inline handler | Logout |
| GET | `/settings/accounting` | `AccountingSettingsController::index` | Accounting provider settings foundation |
| GET | `/settings/calendar` | `CalendarSettingsController::index` | Google/Outlook settings foundation |
| GET | `/settings/llm` | `LlmSettingsController::index` | LLM API settings foundation |
| GET | `/settings/pricing` | `PricingAdminController::index` | Global pricing admin |
| POST | `/settings/pricing` | `PricingAdminController::save` | Save global pricing settings |
| POST | `/settings/pricing/reset` | `PricingAdminController::reset` | Reset global pricing settings |
| GET | `/workspace` | `SaasController::workspace` | Tenant workspace/subscription overview |
| GET | `/subscription` | `SaasController::subscription` | Tenant subscription/usage page |

## 7. Module Business Logic

### 7.1 Public Landing, Auth, Demo Entry, Pricing

Business logic:

- Landing/auth/demo are public-facing.
- Demo routes switch session identity.
- `/demo/company-admin` logs in a tenant owner for tenant id 2.
- `/demo/platform-admin` logs in platform admin for tenant id 0.
- `/pricing` renders pricing plans from `TradePlanCatalog` and `PricingConfigurationService`.
- Pricing supports currency selection and fallback to default currency.
- Default currency is GBP.
- Default trial is 30 days.
- Global discount can affect effective price.

Test priorities:

- Public pages load without special setup.
- Demo switch routes redirect correctly.
- Pricing handles supported and unsupported `currency` query values.
- Prices, trial days, discounts, tax behavior, features, and plan names render correctly.
- Production auth tests must verify demo auto-login cannot leak into production mode.

### 7.2 Dashboard

Business logic:

- Dashboard requires `view_dashboard`.
- It shows total jobs, pending quotes, unpaid invoices, monthly revenue, recent jobs, pending quotes, and quick actions.
- Uses `OperationsDemoData` and `JobWorkflowService`.

Test priorities:

- KPI values match demo data.
- Quick actions navigate to valid routes.
- Dashboard renders under XAMPP base path.
- Unauthorized roles receive 403.
- Cards remain responsive and non-overlapping.

### 7.3 Clients

Business logic:

- `/clients` list is currently demo/session backed through `OperationsController`.
- `ClientController` supports create, view, edit, and update.
- `DemoClientSessionRepository` stores demo-created and demo-edited records in session.
- `ClientService` validates name, email, status, phone/address lengths, and maps seeded demo clients to form values.
- Existing seeded clients can be edited without duplicating the original record.
- Client identity for edits is `original_client_name`.

Important test cases:

- Drill into a seeded client from `/clients`.
- Edit a seeded client and save.
- Save an edited client over seed data without duplicates.
- Create client with valid fields.
- Reject empty name, invalid email, unsupported status, overlong fields.
- Verify search finds by name/email/phone/address.
- Verify escaped output for malicious client names and notes.
- Verify server-side `view_clients`, `create_clients`, and `update_clients`.

### 7.4 Jobs

Business logic:

- Jobs list and detail are handled by `JobController`.
- `JobWorkflowService` owns statuses, priorities, validation, demo search/filter, detail lookup, checklist, timeline, financial snapshot, related actions, workflow stats, and default checklist.
- Current local app uses demo data through `JobWorkflowService(null, OperationsDemoData)`.
- `JobRepository` exists for future PDO persistence.
- Job create validates title, client, status, priority, date/time fields, and creates via repository only when repository is present.
- Jobs include workflow-stage style fields in database schema: `blocker_reason`, `workflow_stage`, `customer_visible_status`, `completion_percent`.

Test priorities:

- Search by title/client/location.
- Filter by pending/scheduled/in-progress/completed/cancelled or available statuses.
- Drill into every status type.
- Unknown/missing job id should return not-found behavior.
- Checklist, timeline, financials, and related actions render.
- Create form validates required fields and enum values.
- Future DB-backed tests must verify tenant-scoped reads and writes.

### 7.5 Schedule and Appointments

Business logic:

- `/schedule` renders a week calendar.
- Week starts Monday.
- Time slots are 08:00 through 17:00.
- Appointment types: `job`, `appointment`, `meeting`, `reminder`.
- Durations: 30, 45, 60, 90, 120, 180, 240 minutes in UI; service validation allows 15 to 480 minutes.
- Customer/attendee source can be `existing`, `potential`, or `internal`.
- Potential-client bookings require potential client name and can include email/phone.
- Internal bookings become `Internal Team`.
- Same-technician overlapping bookings conflict.
- Different technician or non-overlapping bookings should not conflict.
- Created appointments are stored in session by `DemoAppointmentSessionRepository`.
- Calendar slots are clickable and populate the booking form.

Test priorities:

- Click a time slot and verify form date/time updates.
- Book an appointment with existing client.
- Book a meeting.
- Book an internal reminder.
- Book with a potential client and verify potential status.
- Reject invalid type, missing title, missing client, invalid potential email, invalid date, invalid time, invalid duration, missing technician, long location/notes.
- Detect overlap: candidateStart < existingEnd and candidateEnd > existingStart for same technician/date.
- Verify week previous/today/next navigation.

### 7.6 Quotes

Business logic:

- Quote statuses: `draft`, `sent`, `approved`.
- Only draft quotes can be edited.
- Seeded and session quotes are merged by uppercase quote number with session data winning.
- New quotes default to draft, valid for +14 days, tax rate 10 percent, first line quantity 1.
- Quote numbers are uppercase; empty numbers generate `Q-DEMO-YYYYMMDD-HHMMSS`.
- Validation:
  - optional quote number length <= 40.
  - required client name <= 120.
  - supported status only.
  - valid ISO `valid_until`.
  - tax rate 0 to 100.
  - at least one valid line item.
  - line item description required and <= 160.
  - quantity > 0 and <= 10000.
  - unit price >= 0 and <= 1000000.
- `QuoteCalculator` calculates subtotal, tax, and total.
- Quote detail displays line items and summary.
- Update posts `original_quote_number`.

Test priorities:

- Drill into each quote.
- Draft quote edit link appears; sent/approved edit should be locked.
- Edit draft quote and verify persistence without duplicate.
- Create quote with one and two line items.
- Validate all boundary values.
- Verify money calculations and rounding.
- Verify tax 0, 10, 100.
- Verify overlarge quantity/price rejected.
- Verify sent quote edit route returns forbidden/locked behavior.

### 7.7 Invoices

Business logic:

- Invoice statuses: `unpaid`, `paid`, `overdue`.
- Only unpaid invoices can be edited.
- Seeded and session invoices are merged by uppercase invoice number with session data winning.
- New invoices default to unpaid, due +14 days, tax rate 10 percent, first line quantity 1.
- Empty invoice number generates `INV-DEMO-YYYYMMDD-HHMMSS`.
- Validation mirrors quote validation with due date and invoice statuses.
- Invoice detail displays line items and summary.
- Update posts `original_invoice_number`.

Test priorities:

- Drill into each invoice.
- Unpaid invoice edit link appears; paid/overdue edit should be locked.
- Edit unpaid invoice and verify persistence without duplicate.
- Create invoice with one/two line items.
- Validate amount, date, tax, status, line-item boundaries.
- Verify paid invoice cannot be edited.

### 7.8 Team

Business logic:

- `/team` lists team members.
- `/team/view?email=...` drills into a member.
- `/team/edit?email=...` opens edit form when the user has `manage_team`.
- `/team/update` saves changes.
- Editable fields: name, email, role, phone, status.
- Team roles: `Trainee`, `Team Member`, `Supervisor`, `Manager`, `Owner`.
- Team statuses: `active`, `invited`, `inactive`.
- Seeded and session team members are merged by lowercase email with session data winning.
- Editing a seeded member does not duplicate it.
- Team edit identity is `original_member_email`.

Test priorities:

- Drill into every seeded member.
- Edit role, phone, email, and status.
- Verify invalid email, missing name, unsupported role/status, long phone rejected.
- Verify `manage_team` required for edit/save.
- Verify `view_team` sufficient for detail.
- Verify no duplicate rows after edit.

### 7.9 Reports

Business logic:

- Reports are demo summaries: jobs by status, quote funnel, revenue, jobs per technician placeholder.
- Numbers come from `OperationsDemoData`.

Test priorities:

- Numbers match demo data.
- Empty states render.
- Role `view_reports` required.
- Future tests: saved reports, scheduled reports, date filters, exports, profitability, utilization.

### 7.10 Import Data

Business logic:

- Import page lets customer select data type before validation.
- Supported types: `customers`, `jobs`, `quotes`, `invoices`, `team`.
- Import schemas define required, recommended, optional, and alias fields.
- Validation preview normalizes headers, applies aliases, reports missing required fields, recognized fields, unknown fields, errors, and warnings.
- Sample CSV downloads use schema sample headers.
- Protocol rules require RFC 4180-style CSV, UTF-8, file/header/row validation before writing, dry run first, auditability, and formula neutralisation.

Required fields:

| Type | Required fields |
| --- | --- |
| customers | `name` |
| jobs | `title`, `client_name`, `status` |
| quotes | `quote_number`, `client_name`, `status`, `subtotal`, `total` |
| invoices | `invoice_number`, `client_name`, `status`, `total`, `due_date` |
| team | `name`, `email`, `role` |

Test priorities:

- Type selector changes schema.
- Sample CSV header correctness.
- Alias normalization, for example `Customer Name` -> `name`.
- Missing required fields produce errors.
- Unknown fields produce warnings.
- Current implementation is validation/sample foundation, not full upload/commit.
- Future file upload tests must include MIME, extension, size, row count, CSV formula injection, duplicate rows, rollback, idempotency, row errors, and audit logs.

### 7.11 Export Data

Business logic:

- Export UI supports all data, clients, jobs, quotes, and invoices.
- `/export-data/download?dataset=...` returns CSV.
- CSV cells are neutralised for spreadsheet formula-safety in `OperationsController::csvSafeCell`.

Test priorities:

- Dataset selection works.
- CSV headers and row counts match selected dataset.
- Invalid dataset falls back safely.
- Formula-leading cells are safely escaped.
- `export_csv` permission required.
- Future export jobs should be tenant-isolated and audited.

### 7.12 AI Assistant and LLM Settings

Business logic:

- AI assistant is currently UI/demo mode.
- LLM provider settings foundation lives at `/settings/llm`.
- `LlmProviderRegistry` includes common provider options.
- `LlmConnectionService` validates provider, display name, model, API key length, and HTTPS custom endpoint.
- Secret masking exposes only safe edges.
- Import safety rules exist.

Test priorities:

- LLM settings visible only to permitted roles.
- Unsupported provider rejected.
- Custom endpoint must be HTTPS.
- Short/missing key rejected.
- Masking never reveals full API key.
- Future LLM import mapping tests must mock responses and require validation/human approval before mutation.

### 7.13 Accounting Integrations

Business logic:

- Tenant accounting settings page at `/settings/accounting`.
- Platform accounting sandbox link under `/platform-admin/accounting/connect`.
- Providers:
  - Xero
  - Intuit QuickBooks Online
  - Sage Accounting
  - Zoho Books
  - MYOB
  - FreeAgent
  - Reckon
  - SMEPlus
  - FreshBooks
  - Wave Accounting
- `AccountingProviderRegistry` defines auth mode, status, regions, capabilities, docs URL, and notes.
- SMEPlus is `owner_confirmation_required` and `requires_owner_confirmation`.
- Platform accounting connection validation supports sandbox mode only in current demo.
- Live mode is intentionally rejected.

Test priorities:

- All 10 providers render.
- Capabilities are correct per provider.
- Unknown provider rejected.
- SMEPlus live setup blocked unless owner confirmation path exists.
- Sandbox link works for all providers.
- Live OAuth, token storage, webhooks, and sync workers are not implemented and must be contract-tested/mocked in a future plan.

### 7.14 Calendar Integrations

Business logic:

- Calendar settings page at `/settings/calendar`.
- Providers:
  - Google Calendar
  - Outlook / Microsoft 365 Calendar
- Both are OAuth2 foundations.
- Google strategy: sync token and push channel.
- Outlook strategy: delta link and Microsoft Graph subscription.
- Sync direction validation supports two-way setup.
- Conflict rule: queue conflict if both FieldOps and provider changed; no conflict if only FieldOps changed.

Test priorities:

- Both providers render.
- Provider capabilities and scopes render.
- Unsupported provider rejected.
- Invalid sync direction rejected.
- Two-way Google and Outlook validation accepted.
- Conflict logic tests.
- Future tests: OAuth state, webhook signature, replay, delta tokens, push expiration, recurring events, timezones, daylight saving, external deletions, provider rate limits.

### 7.15 SaaS Workspace and Subscription

Business logic:

- Tenant workspace route `/workspace` requires `manage_tenant`.
- Subscription route `/subscription` requires `manage_billing`.
- `TenantSubscriptionService` provides demo workspace, usage snapshot, onboarding checklist, usage percent, trade segments, and seat limit checks.
- `TradePlanCatalog` wraps pricing settings and plan feature checks.
- Basic plan is GBP 0.
- Pro plan is GBP 30/month and includes 30-day trial through pricing settings.
- Plans include seat, job, AI credit, storage, and feature limits.

Test priorities:

- Workspace/subscription pages require correct permissions.
- Usage percentages calculate correctly.
- Seat limit logic blocks over-limit additions.
- Feature gates match plan catalog.
- Trial days and currency display match pricing config.

### 7.16 Platform Admin

Business logic:

- `/platform-admin` is the only admin root. Do not create `/admin`.
- Platform admin navigation includes:
  - Dashboard
  - Tenants
  - Billing
  - Plans
  - Audit Logs
  - Demo Management
  - Import/Export
  - System Health
  - Support Access
  - Security
  - Mobile/Offline Status
- Platform-admin routes require `view_platform_admin`, `manage_platform_admin`, `manage_platform_billing`, `manage_platform_pricing`, or `impersonate_tenants` depending on action.
- Tenant detail includes overview, users, billing, plan/trial, usage, imports/exports, audit log, and support notes.
- Tenant actions require confirmation and are audited:
  - suspend
  - resume
  - start trial
  - extend trial
  - end trial
  - change plan
  - add grace period
  - mark payment failed
  - mark account active
  - export data
- Support access requires reason, supports time-limited sessions of 15/30/60/120 minutes, records audit history, and provides return-to-platform-admin.
- Demo reset is explicit only and audited.

Test priorities:

- Tenant roles cannot access platform admin.
- Platform admin can access all platform pages.
- Navigation has no dead buttons or `/admin` routes.
- Sensitive actions require confirmation.
- Trial/grace period input boundaries.
- Change plan validates plan key.
- Support login requires reason and valid tenant/duration.
- Support session expires and restores platform admin.
- Return button restores platform admin.
- Audit log entries are append-only in app logic.

### 7.17 Platform Billing

Business logic:

- `PlatformBillingService` builds a tenant billing ledger from demo contracts plus current pricing settings.
- It shows tenant name, owner email, plan/package, billing cycle, currency, contract start/end, contract value, billed-to-date, remaining-to-bill, next invoice amount/date, latest invoice number, auto invoice status, and override state.
- Monthly contracts schedule min(monthly price, remaining).
- Annual contracts schedule remaining amount.
- Annual discount can reduce contract value.
- Manual overrides:
  - `override_next_invoice`
  - `pause_auto_invoice`
  - `resume_auto_invoice`
- Override validation:
  - valid tenant id required.
  - override mode must be supported.
  - amount required for override amount and must be 0 to 1,000,000.
  - reason of at least 10 characters is required except resume.
- Override persistence is session-backed via `DemoPlatformBillingRepository`.

Test priorities:

- Ledger values per tenant and currency.
- Remaining-to-bill never negative.
- Annual discount math.
- Next invoice amount for monthly/annual.
- Override amount changes next invoice.
- Pause sets next invoice amount to 0 and status paused.
- Resume clears override.
- Invalid tenant/mode/amount/reason rejected.
- Platform accounting sandbox link works for all 10 accounting providers.

### 7.18 Pricing Admin

Business logic:

- `/settings/pricing` requires `manage_platform_pricing`.
- Supports global default currency, supported currencies, supported regions, trial days, global discount percent/label/expiry, tax behavior, and plan prices.
- Available currencies: USD, GBP, EUR, AUD, CAD, NZD, SGD, INR, LKR, ZAR, AED.
- Available regions include North America, UK, Europe, Australia/NZ, Asia Pacific, Middle East/Africa, South Asia.
- Tax behaviors: exclusive, inclusive, not_collected.
- Trial days: 0 to 365.
- Discount percent: 0 to 95.
- Discount expiry must be ISO date if present.
- Basic plan can be zero; other plans require > 0.
- `selectedCurrency()` falls back to default if requested currency is not enabled.

Test priorities:

- Save valid pricing settings.
- Reject unsupported default currency.
- Reject default currency not in supported list.
- Reject empty supported currencies/regions.
- Reject unsupported regions.
- Reject invalid trial/discount/date/tax behavior.
- Reject invalid plan prices.
- Reset restores defaults.
- Public pricing reflects saved settings.

### 7.19 Audit Logging

Business logic:

- `AuditLogService` records normalized action/entity strings, tenant, user, actor email, actor role, metadata, and timestamp.
- Demo audit repository is session-backed.
- Database audit repository exists for `AUDIT_REPOSITORY=database`.
- Tracked helper events include tenant suspend/resume, plan change, trial extension, support login, import run, export run, demo reset, role change, settings change.

Test priorities:

- Audit action/entity normalization.
- Recent events order newest-first.
- Tenant filter.
- Support access history filters support_login.
- Sensitive platform actions write audit events.
- App logic should not expose edit/delete audit routes.

### 7.20 Demo Management

Business logic:

- Demo management reports seeded status, last seeded time, reset count, usage count, data counts, route health, and cleanup preview.
- Demo does not reseed automatically on entry.
- Reset happens only when clicked and is audited.

Test priorities:

- Route health values.
- Usage counter.
- Reset count.
- Cleanup scoped to demo tenant only.
- Reset requires platform admin permission and CSRF.

### 7.21 System Health, Security Admin, Mobile/Offline Status

Business logic:

- `/platform-admin/health` shows app, database, session/auth, background jobs, API health, storage, and mobile/offline sync placeholders.
- `/platform-admin/security` shows session policy, MFA, password policy, CSP checklist, export/deletion request logs, tenant isolation checklist.
- `/platform-admin/mobile-offline` shows PWA, Capacitor Android, Capacitor iOS, offline sync queue, failed sync, app version placeholders.

Test priorities:

- Pages require platform admin.
- All expected cards/checks render.
- Placeholder statuses are clearly labelled as readiness/future state.
- No false live-production claims.

## 8. Data Model

`database/schema.sql` defines these groups:

SaaS and billing:

- `tenants`
- `subscription_plans`
- `tenant_subscriptions`
- `tenant_usage_counters`
- `tenant_invitations`
- `tenant_feature_flags`
- `billing_events`

Users and access:

- `users`
- `api_tokens`
- `audit_logs`

Field operations:

- `clients`
- `jobs`
- `job_checklist_items`
- `job_timeline_events`
- `quotes`
- `quote_items`
- `invoices`
- `invoice_items`

Inventory and stock:

- `inventory_items`
- `truck_stock`

Customer portal, automation, reporting:

- `customer_portal_tokens`
- `automation_rules`
- `saved_reports`

Accounting:

- `accounting_connections`
- `accounting_sync_jobs`
- `accounting_sync_logs`

Calendar:

- `calendar_connections`
- `calendar_event_links`
- `calendar_sync_jobs`
- `calendar_webhook_events`

LLM/import/export:

- `llm_connections`
- `llm_usage_logs`
- `import_jobs`
- `import_mapping_profiles`
- `import_validation_reports`
- `export_jobs`
- `demo_data_registry`

Settings:

- `settings`

Current persistence reality:

- Most operational demo screens are session/demo backed.
- `ClientRepository`, `JobRepository`, `AccountingConnectionRepository`, and `DatabaseAuditLogRepository` exist as production-oriented PDO/repository boundaries.
- Full database-backed controller flows and tenant-isolation integration tests are not complete.

Testing implication:

- Current behavior tests must cover session-backed workflows.
- Production-readiness tests must separately cover schema import, repository contracts, tenant-scoped queries, foreign keys/indexes, and migrations.

## 9. Current Automated Coverage

`composer.json` scripts:

- `composer test`: `php tests/run.php`
- `composer check`: `php tests/run.php && php tools/lint-php.php`

Current `tests/run.php` covers:

- Password hashing and policy.
- Auth hash/register/verify.
- RBAC permissions.
- XAMPP base-path stripping.
- Forbidden response for protected platform admin.
- POST CSRF failure.
- Client validation, find/edit mapping, update merge, template drill-in/edit markers.
- Job validation, search/filter, detail lookup, checklist, timeline, financials, related actions.
- Operations demo data scale.
- Team validation, seeded member lookup, edit merge, template drill-in/edit markers.
- SaaS plan catalog, Basic and Pro price/trial checks.
- Pricing settings validation, persistence, currency fallback, discount math.
- Platform admin overview, nav, tenant actions, billing health, system health, support access UI/history, security/mobile placeholders.
- Platform billing ledger and overrides.
- Quote totals, validation, line items, create mapping, draft edit, sent lock, edit merge, templates.
- Invoice totals, validation, line items, create mapping, unpaid edit, paid lock, edit merge, templates.
- Demo reset scoping and audit.
- JSON responder.
- Accounting registry and platform sandbox links for all 10 providers.
- Calendar registry/sync validation/conflict logic.
- Schedule week calculation, clickable slot template markers, validation, appointment build, conflict detection, potential client support.
- Import schema registry, header normalization, validation preview, protocol rules.
- LLM provider validation, HTTPS custom endpoint, secret masking, safety rules.

Current coverage gaps:

- No proper PHPUnit suite despite PHPUnit dependency.
- No browser E2E suite committed.
- No automated accessibility or visual regression tests.
- No database integration tests against MySQL.
- No API endpoint implementation despite docs mentioning future `/api/*`.
- No upload processing tests because real CSV upload/commit is not implemented.
- No live OAuth/accounting/calendar/LLM/billing provider tests.
- No CI pipeline is documented as active.

## 10. High-Risk Business Logic Questions For Claude

Ask Claude to explicitly inspect and answer:

1. Are all protected routes covered by server-side RBAC checks?
2. Are there any POST routes missing CSRF protection or valid tokens in forms?
3. Are quote/invoice edit-lock rules correct for the target business?
4. Should overdue invoices be editable or locked?
5. Should sent quotes be editable via revision/version flow rather than completely locked?
6. Does schedule conflict logic handle exact boundary cases correctly?
7. Should schedule appointments allow 15-minute durations if the UI only lists 30+ minutes?
8. Are pricing and billing calculations correct for annual discounts, multi-currency, and remaining-to-bill?
9. Are manual billing overrides sufficiently audited?
10. Does support login-as require enough context and prevent platform/admin leakage?
11. Is demo auto-login safely gated before production?
12. Are session-backed repositories acceptable for demo or should more flows be wired to DB before test plan execution?
13. Are import CSV safety rules sufficient for spreadsheet formula injection and malicious headers?
14. Are external integration pages clear that they are sandbox/foundation only?
15. Are audit logs append-only enough in app logic, and should database constraints enforce immutability?
16. Do generated links/forms work under XAMPP base path?
17. Are all buttons/links on primary screens working after recent UI changes?
18. Is tenant isolation testable with current schema and repositories, or blocked until DB-backed controllers are wired?

## 11. Known Limitations and Blockers

Current limitations:

- Demo auto-login is active in `public/index.php`.
- Many CRUD flows use demo/session repositories rather than database persistence.
- Client/job production repository contracts exist, but most UI is still demo/session-backed.
- API routes documented in `docs/API_REFERENCE.md` are not currently routed in `public/index.php`.
- Live accounting OAuth and sync are not implemented.
- Live Google/Outlook OAuth, webhooks, and event mutation are not implemented.
- Live LLM calls, encrypted key storage, and AI mapping execution are not implemented.
- Real file upload/import commit is not implemented.
- Live Stripe/billing provider checkout and webhooks are not implemented.
- Background job worker is not implemented.
- Full mobile app/PWA/Capacitor implementation is not present; admin visibility placeholders exist.

Do not mark these as bugs unless the intended release scope requires them. They are product maturity gaps and should become test-plan sections or release gates.

## 12. Recommended Test Architecture

Layered strategy:

1. Static/build checks:
   - Composer validate.
   - PHP lint.
   - Autoload check.
   - Future PHPStan/Psalm only after owner approval.
2. Unit/service tests:
   - Services, calculators, registries, validators, RBAC, CSRF, request, renderer.
3. Controller/HTTP integration tests:
   - Route status, permissions, CSRF, redirects, validation errors, XAMPP base path.
4. Repository/database tests:
   - Schema import, seed import, PDO repository tenant scope, money/date precision.
5. Browser E2E tests:
   - Navigation, forms, drill-in, edit/save, CSV downloads, pricing admin, platform admin.
6. Security tests:
   - Auth, RBAC, CSRF, XSS, tenant isolation, secrets, uploads, support impersonation, audit.
7. Integration contract tests:
   - Mock accounting, calendar, LLM, billing provider behavior.
8. Accessibility/responsive:
   - Keyboard, labels, focus, contrast, text overflow, mobile/tablet/desktop.
9. Performance/reliability:
   - Large clients/jobs/import/export/report/calendar density.
10. Release gates:
   - Required checks and manual sign-offs before any commercial pilot.

## 13. Suggested Release Gates

Local demo gate:

- `composer test` passes.
- `composer check` passes.
- XAMPP-served copy passes `php tests/run.php`.
- Browser smoke covers primary navigation, clients/jobs/schedule/quotes/invoices/team drill-in and create/edit flows.
- Platform admin smoke covers dashboard, tenant detail, billing override, support login-as, return, pricing, accounting sandbox link, demo reset, health, security, mobile/offline.
- No dead visible buttons/links in primary UI.

Commercial beta gate:

- Demo auto-login disabled or environment-gated.
- Auth/session production hardening complete.
- DB-backed tenant isolation tests pass.
- RBAC and CSRF tests cover every protected route.
- Import upload commit and rollback tests pass.
- Accounting/calendar/LLM/billing integrations are mocked and contract-tested before live credentials.
- Audit logs for sensitive actions are durable.
- Accessibility smoke passes.
- Security review complete for personal data, financial data, imports, AI, secrets, external integrations, and support impersonation.

