# PRD — intersect_cloud_prod (Simpleflow Hub)

Evidence date: 2026-10-12.

Source: `git@bitbucket.org:overdrive/intersect_cloud_prod.git`, `main` at `f4affe1` (2025-01-06, "latest update").

Package `simpleflow_hub`. Product name on the running app: **Simpleflow Hub**.

Provenance labels used below: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown / Requires Confirmation**.

There is no separate requirements document in the repository. Requirements below are recovered from the implementation. They describe what the code does. They are not an approved product decision.

## 1. Product purpose

**Inferred.** Simpleflow Hub is a multi-tenant operations app. A user signs in, lands in one tenant, and works through that tenant's processes: tasks, job cards, inspections, bookings, documents, assets, messages, support tickets, and tenant-specific reports (pest control and quality).

The README does not state a purpose. It says "Simpleflow Hub" and "A new Flutter project." Provenance: **Documented**, incomplete.

## 2. Users and roles

Two role strings are stored. The screens label them differently from the stored values.

| UI label | Stored value | Where it is written | What the shell does with it |
|---|---|---|---|
| System Admin | `user.user_role = 'Admin'` | Add-user screens | System admin management route; tenant switcher when the nav is open |
| System User | `user.user_role = 'User'` | Add-user screens | No system-admin management |
| Tenant Admin | `tenant_user_list.role = 'Admin'` | Add-user / tenant-link screens | Tenant-admin management route |
| Tenant User | `tenant_user_list.role = 'User'` | Same | No management item |

Provenance: **Implemented**. Mapping example: `lib/pages/user_management/users/add_user/add_user_widget.dart` writes `'Admin'` or `'User'` into `user_role` and into the tenant link `role`. The side nav reads `FFAppState().systemRole` and `FFAppState().tenantRole` (`lib/pages/include/side_nav/side_nav_widget.dart`).

Login copies those fields into app state (`lib/pages/auth/login/login_widget.dart`):

- `systemRole` from `user.user_role` of the signed-in user.
- `tenantRole` from the first `tenant_user_list` row for that user.
- `currentTenantId` and `selectedTenantName` from that same first row.

**Inferred rule, and a failure case.** Sign-in uses `firstOrNull!` on the tenant-user query. A user with no `tenant_user_list` row cannot finish login. The code does not show an error for that case; the `!` throws.

A `role`, `permission`, `menu`, and `role_menu_link` table set exists in the generated client. The shell does not check those tables before showing a menu item. Whether the database enforces them is **Unknown / Requires Confirmation** (RLS is not in this repo).

`list_user_override_role` exists as a table class. `userRoleOveride` (spelling as in code) is read on the tenant detail screen. How an override is granted is **Unknown / Requires Confirmation**.

## 3. Sessions and entry

### FR-001 — Open the app

- **Who:** any person with the client.
- **Trigger:** app start.
- **Behaviour:** load `assets/environment_values/environment.json`, initialise Supabase, restore persisted app state, listen to the Supabase user stream, show a splash for 1 second, then route.
- **Output:** `/` shows `NavBarPage` (Dashboard tab) when a session exists, otherwise `LoginWidget`.
- **Evidence:** `lib/main.dart`, `lib/environment_values.dart`, `lib/flutter_flow/nav/nav.dart`.
- **Implementation:** Implemented.
- **Confidence:** High.

### FR-002 — Sign in with email and password

- **Who:** a user who already has a Supabase auth user and a `user` row.
- **Trigger:** Sign In on `pages/auth/login`.
- **Inputs:** email, password.
- **Behaviour:** `authManager.signInWithEmail`. On success, load tenant link, user row, and tenant colours, store roles, go to Dashboard.
- **Validation:** password field uses the page validator. Empty auth result returns without navigation.
- **Error:** a missing tenant link is not handled (see section 2).
- **Evidence:** `lib/pages/auth/login/login_widget.dart`.
- **Implementation:** Implemented.
- **Confidence:** High.

### FR-003 — Forgot password and reset

- **Who:** a user who knows the account email.
- **Trigger:** Forgot password link on the login page, then the reset page.
- **Behaviour:** `resetPasswordForEmail` is implemented on the auth manager. Routes `ForgotPassword` and `ResetPassword` exist. `CreateUserPassword` is also routed.
- **Unknown:** the exact email template and the redirect URL used in production. They are not hard-coded in the manager snippet that was read (`redirectTo` is a parameter).
- **Evidence:** `lib/auth/supabase_auth/supabase_auth_manager.dart`, `lib/index.dart`.
- **Implementation:** Implemented (client). Email delivery is outside this repo.
- **Confidence:** Medium.

### FR-004 — Route guard

- **Behaviour:** `FFRoute.requireAuth` defaults to false. The only route that sets `requireAuth: true` is `SupportTicketList`. When that flag is set and the user is logged out, the router redirects to `/login` and remembers the URL.
- **Inferred:** other named routes (`dashboard`, `tasks`, `management`, and the rest) do not use that guard. Data calls still need a Supabase session. Whether PostgREST rejects them depends on RLS, which is not in this repo.
- **Evidence:** `lib/flutter_flow/nav/nav.dart` (default at the `FFRoute` constructor; flag set only on `SupportTicketList`).
- **Implementation:** Implemented.
- **Confidence:** High for the client guard. Database enforcement is Unknown.

## 4. Tenant workspace

### FR-010 — One current tenant

- **Who:** signed-in user.
- **Behaviour:** `currentTenantId` is persisted in Flutter secure storage. Queries for processes, tasks, equipment, documents, and tickets pass that id.
- **System admin:** when the side nav is expanded and `systemRole == 'Admin'`, a tenant list is shown and choosing a row replaces `selectedTenantName`, `currentTenantId`, and `tenantRole`.
- **Evidence:** `lib/app_state.dart`, `lib/pages/include/side_nav/side_nav_widget.dart`.
- **Implementation:** Implemented.
- **Confidence:** High.

### FR-011 — Tenant branding

- **Behaviour:** after login, primary, accent, background, header, tile, and menu colours are copied from the tenant row. Defaults if missing: `#ff9900`, `#495564`, `#ffffff`, and the same orange/slate again for header, tile, and menu.
- **Evidence:** login widget colour block; `TenantRow` fields `primary_color`, `accent_color`, `background_color`, `header_color`, `tile_color`, `menu_color`, `logo_path`.
- **Implementation:** Implemented.
- **Confidence:** High.

### FR-012 — Tenant record

A tenant has name, business address, logo, website, phone, business email, the colours above, `active`, and timestamps.

**Unknown / Requires Confirmation:** whether `active = false` hides the tenant from login or from the switcher. The login query does not filter on `active` in the block that was read.

## 5. Shell navigation

Phone bottom bar (`lib/main.dart`), hidden from tablet landscape and desktop:

| Tab | Route / widget |
|---|---|
| Home | Dashboard |
| Process Hub | ProcessHub |
| Notifications | Notification |
| Tasks | Tasks |
| More | MoreNavBar |

Desktop side nav (`lib/pages/include/side_nav/side_nav_widget.dart`) links:

Dashboard, Process Hub, Calendar, Assets and Parts, Documents, Tasks, Notifications, Messages, Support Tickets, Reports, My Profile, and Management (only if system role or tenant role is `Admin`).

Support Tickets is the only one of these with `requireAuth: true` (FR-004).

Sign-out clears `selectedTenantName`, `systemRole`, and `tenantRole`.

## 6. Routed pages

These are the pages `lib/index.dart` exports and `nav.dart` registers. Paths are relative to `/`.

| Route name | Path | Page |
|---|---|---|
| Login | `login` | `pages/auth/login` |
| ForgotPassword | `forgotPassword` | `pages/auth/forgot_password` |
| ResetPassword | `resetPassword` | `pages/auth/reset_password` |
| CreateUserPassword | `createUserPassword` | `pages/auth/create_user_password` |
| Dashboard | `dashboard` | `pages/dashboard/dashboard` |
| Profile | `profile` | `pages/profile/profile` |
| Reports | `reports` | `pages/reporting/reports` |
| Notification | `notification` | `pages/notifications/notification` |
| Message | `message` | `pages/messages/message` |
| ViewMessage | `viewMessage` | `pages/messages/view_message` |
| Tasks | `tasks` | `pages/general_forms/tasks/tasks` |
| ProcessHub | `processHub` | `pages/process_hub/process_hub` |
| Calendar | `calendar` | `pages/general_forms/bookings/calendar` |
| AssetList | `assetList` | `pages/general_forms/asset_register/asset_list` |
| DocumentList | `documentList` | `pages/general_forms/documents/document_list` |
| SupportTicketList | `supportTicketList` | `pages/general_forms/support_ticket/support_ticket_list` |
| MoreNavBar | `moreNavBar` | `pages/include/more_nav_bar` |
| Management | `management` | `pages/management/management` |
| ManagementUser | `managementUser` | `pages/management/management_user` |
| UserList | `userList` | `pages/management/users/user_list` |
| UserListTenantAdmin | `userListTenantAdmin` | `pages/management/users/user_list_tenant_admin` |
| TenantsList | `tenantsList` | `pages/management/tenants/tenants_list` |
| TenantsListUser | `tenantsListUser` | `pages/management/tenants/tenants_list_user` |
| clientsList | `clientsList` | `pages/management/clients/clients_list` |
| clientsListUser | `clientsListUser` | `pages/management/clients/clients_list_user` |

Most forms (job card editor, pest site form, Plateau reports) are opened from Process Hub as widgets, not as their own routes.

## 7. Management

### FR-020 — Who sees management

- System admin (`systemRole == 'Admin'`) goes to route `Management`.
- Else tenant admin (`tenantRole == 'Admin'`) goes to `ManagementUser`.
- Anyone else does not get the nav item.
- **Evidence:** side nav, about line 1322.
- **Implementation:** Implemented. Several management pages also re-check the role in `initState` and leave if it does not match. Example: `pages/management/users/edit_user` requires `systemRole == 'Admin'`; tenant-admin user edit requires `tenantRole == 'Admin'`.

### FR-021 — Create a user

- **Who:** system admin on the add-user flow (a parallel tenant-admin flow exists).
- **Inputs:** first name, last name, phone, email, system role (System Admin / System User), active switch, optional photo, tenant, tenant role (Tenant Admin / Tenant User).
- **Behaviour:** creates the auth user (the page reads `_model.authCheck`), inserts `user`, then inserts `tenant_user_list` if that user is not already linked to the tenant.
- **Validation:** if a tenant link already exists, dialog title "Already linked!".
- **Default avatar** when no file is uploaded: a public object on `lwbyelfbryqvcvebcsin.supabase.co`, bucket `uploads`, path `profile/PngItem_1503945.png`. That host is not the app's configured API URL. Provenance: **Implemented**. Whether that object still exists is **Unknown**.
- **Evidence:** `lib/pages/user_management/users/add_user/add_user_widget.dart`. The routed management screens under `pages/management/users/` repeat the same role labels.

### FR-022 — Tenants and clients

System-admin screens list, add, and edit tenants and clients. Tenant-admin screens list tenants and clients in the narrower `*User` pages. Client links are `tenant_client_link` / `client_user_link`.

**Unknown / Requires Confirmation:** the business difference between a client and a tenant. Both are first-class records. A tenant has many clients in the schema. The product reason is not written down.

## 8. Process Hub

### FR-030 — Load the tenant's processes

- **Trigger:** Process Hub open.
- **Behaviour:** read `tenant_process` for `currentTenantId`, then `process` rows whose ids are in that list, ordered by `process.order`. Chip labels are `process.filter_name`.
- **Evidence:** `process_hub_widget.dart` `initState`.
- **Implementation:** Implemented.
- **Confidence:** High.

### FR-031 — Open a process panel

The selected `filter_name` must match one of these strings or the page has no panel for it:

`Job Cards`, `Scheduled Inspection`, `Building Inspection`, `Tasks`, `Equipment Inspection`, `Items`, `Bookings`, `Action Reports`, `Non-Conformance Reports`, `Corrective Action Reports`, `Tickets`, `Recommendation Reports`, `Service Reports`, `Site Forms`, `Documents`.

Provenance: **Implemented** (`webFilterChoiceChipsValue` branches in `process_hub_widget.dart`).

A second, smaller chip set is hard-coded for another layout: `Tasks`, `Scheduled Inspection`, `Building Inspection`, `Job Cards`, `Equipment Inspection`. Provenance: **Implemented**. Which breakpoint uses which set is visible in that file's layout tree; both sets exist.

Which `filter_name` values are actually stored for live tenants is **Unknown / Requires Confirmation**. The names are data, not seed data in git.

### FR-032 — Filter change deletes draft job cards

On the dynamic filter chips, after the selection is stored, the app deletes `job_card` rows whose `status` is null.

Provenance: **Implemented** (`JobCardTable().delete` with `isFilter('status', null)` in the chip `onChanged`).

**Requires Confirmation.** This reads as cleanup of incomplete cards. It is not labelled as such in the UI. If RLS is missing, the filter is not scoped to the current user in that call.

## 9. Tasks

### FR-040 — Create a task

- **Inputs include:** name and description (task table), due date, tags chosen from `Urgent`, `Project`, `Activity`, `Assistance`, assignee, tenant, optional process and form links, files.
- **Initial status:** `'Open'`.
- **Evidence:** `lib/pages/tasks/create_task/create_task_widget.dart`. The routed list is `pages/general_forms/tasks/tasks`. Both trees contain task screens.
- **Implementation:** Implemented.
- **Confidence:** High for the status and tags that were read.

### FR-041 — Complete a task

Marking work done writes `status: 'Done'`. The details screen treats `Done` as a distinct state (sections hidden or shown on that equality).

No other status string was found in the task pages that were searched (`Open` and `Done` only).

**Unknown / Requires Confirmation:** whether "archived" (`task.archived`) is a third lifecycle state or a separate flag. The column exists.

### FR-042 — Task linkage

A task may point at `process_id`, `process_step_id`, `form`, and `form_id`. Comments live in `task_comments` and `create_task_form_comments`.

## 10. Job cards

### FR-050 — Job card record

Table `job_card` stores date, time spent, employee, customer name/address/telephone/email, order number, parts, materials, follow-up, VAT, total with and without VAT, status, signature, photos, PDF path, tenant, and `jobcard_identifier`.

Line items are `job_card_item`. Totals can be asked from RPCs `calculate_total_job_card_new` and `calculate_sum_by_jobcard`.

A basic job-card screen sets `status` to `'Submitted'` on save (`pages/job_card/job_card_basic`).

`dummy_data_job_card` is queried by `JobCardDummyDataCall` (ilike search). **Unknown** whether that table is still used in production or is leftover test data.

### FR-051 — Job card PDF

Custom actions `create_job_card_pdf` and `create_job_card_basic_pdf` build a PDF on the device. Process Hub can open `DownloadPdJobCardWidget` for a stored PDF.

VAT calculation rules (rate, inclusive vs exclusive) are **Unknown** beyond the columns `vat`, `total_no_vat`, and `total_with_vat`. The RPC bodies are not in this repo.

## 11. Inspections, assets, documents

### FR-060 — Building and equipment inspections

Tables: `building_inspection_form`, `equipment_inspection_form`, `equipment_inspection_assign_form`, and `equiment_inspection_assign_form` (the second name is spelled without the first `p` in the table class).

Process Hub opens them when the filter name is `Building Inspection` or `Equipment Inspection`. PDFs: `create_building_inspection_pdf`, `create_equipment_inspection_pdf`.

### FR-061 — Assets / equipment register

Side nav label "Assets and Parts". Table `equipment_register` plus locations, assignment, and change log. Search API `list_equipment` filters by tenant and matches name, description, model number, SKU, category, location, and client name.

### FR-062 — Documents

Table `document` (name, type, file name, tenant). Access rows in `document_access`. RPC `insert_document_access` and `get_user_documents_details`. Upload bucket used by the add-document screen: `files`.

**Inferred:** a document can be limited to specific users. The policy that decides who may read a file in storage is **Unknown** (storage policies are not in git).

## 12. Pest Elite pack

**Inferred** from names and forms: a pest-control workflow compiled into the same app, shown when the tenant's process `filter_name` is one of `Bookings`, `Action Reports`, `Tickets`, `Recommendation Reports`, `Site Forms`, or related checklist screens under `pages/pest_elite/`.

Implemented records:

- Site form, site map, service report.
- Checklists for rodents, cockroaches, flies, and SPI, each with a trap child table. Trap rows are inserted through RPCs `insert_pest_elite_rodent_traps`, `insert_pest_elite_cockroach_traps`, `insert_pest_elite_flies_traps`, `insert_pest_elite_spi_traps`.
- Recommendation severity chips include `Low`, `Moderate`, `High`. Cockroach items also include `None`.
- Action report with revisit dates and Yes/No chips.
- Bookings: company, address, contact, email, service type, date, preferred time, appointment notes, assigned users (`booking`, `booking_assigned_to`).
- Support tickets created with status `New`. Edit screen options: `New`, `Pending`, `Complete`.

PDFs: site form, recommendation report (two actions, one named `..._pdf_copy`), action report.

**Unknown / Requires Confirmation:** whether "Pest Elite" is a customer, a product module sold to many tenants, or both.

## 13. Plateau pack

**Inferred** the same way. Process filters `Non-Conformance Reports`, `Corrective Action Reports`, and `Service Reports` open Plateau widgets.

- Non-conformance: description, problem closed out (Yes/No), responsible target completion date, file.
- Corrective action: incident report, target completion date, file.
- Service report: areas treated (`plateau_service_report_areas_treated`).

PDFs: `create_plateau_non_conformance_pdf`, `create_plateau_corrective_action_pdf`, `create_plateau_service_report_pdf`.

**Unknown / Requires Confirmation:** whether "Plateau" is a customer name.

## 14. Messages, notifications, calendar, reports

### FR-070 — Messages

Routes `Message` and `ViewMessage`. Tables `messages`, `chat`, `chat_messages`. Uploads use buckets `messages` and `chats`. Custom action `subscribe` listens for Postgres inserts on a caller-supplied table, filtered to the current chat id.

### FR-071 — Notifications

Route `Notification`. Tables `notification` and `user_notification`. iOS has an `ImageNotification` extension target. No Firebase (or other push SDK) is declared in `pubspec.yaml`. Whether push is live is **Unknown / Requires Confirmation**.

### FR-072 — Calendar

Route `Calendar` under `general_forms/bookings`. Uses the `table_calendar` dependency. What it plots (bookings vs tasks) was not fully traced. **Requires Confirmation** if a tester needs the exact event source.

### FR-073 — Reports

Route `Reports`. The dropdown offers `Job Card Report`, `Quotes Report`, `Inspection Report`.

**Unknown:** a quotes table was not in the generated table list. Where "Quotes Report" reads from is **Unknown / Requires Confirmation**.

## 15. Email

Table `send_que` (recipient, subject, body, attachment, status, result). Table `email_template`. Table `mail`. RPC `send_custom_email`. Process rows can store `email_template_id`.

The worker that drains `send_que` is not in this repository. **Unknown / Requires Confirmation** which service sends the mail (a database trigger, `intersect_lambda`, or something else).

Custom actions replace placeholders in HTML for tasks, support tickets, job cards, inspections, and bookings (`replace_*_html_placeholders.dart`).

## 16. Files

Uploads go to Supabase storage. Bucket names used in screens:

| Bucket | Used for |
|---|---|
| `uploads` | Photos on users, tenants, clients, profile photo change, several forms |
| `files` | Task files, support-ticket files, documents |
| `profilePictures` | My Profile photo |
| `messages` | Message attachments |
| `chats` | Group and contact images |

Public URL is returned after upload (`lib/backend/supabase/storage/storage.dart`). There is a delete-by-public-URL helper.

## 17. Dynamic forms

Tables `form_definition`, `form_field`, `form_submission`. Custom widgets `dynamic_form_builder` and `dynamic_draggable_form_builder`. Actions update those fields.

**Inferred:** some forms are defined as data rather than only as Flutter widgets. How far that builder is used versus the hard-coded Pest Elite and Plateau screens is **Requires Confirmation**. Both mechanisms are in the tree.

## 18. Integrations

| System | Direction | What is exchanged | Auth | Failure behaviour |
|---|---|---|---|---|
| Supabase-compatible API at `https://prod-alb.simpleflowhub.app` | App → API | Auth, rows, storage, realtime, RPCs | Anon key from `environment.json`, then the user JWT | Environment load failure prints and leaves the URL and key empty. Later calls then fail. Exact user-facing text is the Supabase/Flutter error, not a product message |
| AWS S3 + CloudFront | Pipeline → AWS | Contents of `build/web` | OIDC role `$AWS_PROD_ARN` | Not described in the pipeline file |
| Email RPC `send_custom_email` | App → database function | Recipient and body, as the RPC defines | User JWT / anon key, same as other API calls | **Unknown** (function body not in repo) |
| Sign in with Apple | Declared on `AuthManager` and in `pubspec.yaml` (`sign_in_with_apple`) | Apple identity | Apple | **Unknown** whether any screen calls `signInWithApple`. The login page that was read uses email only |

No Stripe, no SMS provider, and no analytics SDK showed up in `pubspec.yaml`.

## 19. Constraints

- Locale list is English only (`Locale('en', '')`).
- Auth flow type is implicit (`AuthFlowType.implicit` in `lib/backend/supabase/supabase.dart`).
- Version shown to stores, if the mobile apps are shipped, is `1.0.0+1`.
- Web is the path the pipeline deploys. Mobile and Windows builds are possible from the projects in git. Whether they are released is **Unknown**.

## 20. Non-functional notes recovered from the repo

- **Secrets in git:** `assets/environment_values/environment.json` contains the production API URL and a Supabase anon JWT. The JWT payload's role is `anon`. The raw token is intentionally not copied into this PRD. **Requires Confirmation** that this key is still the live anon key and that it is acceptable for it to be in the repository.
- **Tests:** `test/widget_test.dart` is the Flutter counter template. It pumps `MyApp` and does not assert product behaviour.
- **CI:** on `main`, deploy the already-built web folder. The build step is commented out.

## 21. What is not a requirement

- `intersect_cloud_v2` being a newer Simpleflow Hub. That repository has no app.
- SonarQube as a gate. The scan files named in OVER-161 are not in this commit, and the pipeline does not run a scan.
- The unused page copies (`process_hub_v1`, `process_hub_base`, `process_hub_plateau`, `process_hub_pest_elite`, `pages/authentication`, `pages/user_management`) as separate products. They are extra trees in the same commit. The router uses `lib/index.dart`.
