# PRD — datafy_web (Datafy Web)

Evidence date: 2026-10-17. Source: `git@bitbucket.org:overdrive/datafy_web.git`, `main` at `df8299e` (2026-01-19).

There is no prior PRD in Silicon Ark and no product specification in the application repository. Requirements below are taken from the implementation. Each item is labelled Documented, Implemented, Inferred, Planned, or Unknown.

`README.md` says only that this is a Flutter project built to run on the Flutter stable release. That is **Documented** and is not a product requirement.

## Product purpose

Datafy Web lets a signed-in person inspect and update retail store execution data for a customer: stores and their stock issues, products, field tasks, promotions, KaChing challenges, surveys, and notifications. Super admins also manage users and customers.

Provenance: **Inferred** from the routed screens, navbar, and `get_dashboard_overview` comment in `lib/custom_code/actions/get_dashboard_overview.dart`. The comment is in source and describes the RPC's return shape. It is the closest thing to a product statement in the repository.

## Users and roles

Roles are a string on `user.role` plus a separate boolean `user.super_admin`. After email sign-in the login screen copies both into `FFAppState` (`lib/pages/auth/login/login_widget.dart`).

| Role | What the client does with it | Evidence | Provenance |
|---|---|---|---|
| Admin | Store pickers use `adminStores` from the admin stores API. Several screens branch on `userRole == 'Admin'` | `lib/actions/actions.dart` `getStores`, tasks, surveys, products, promos, KaChing, stores | Implemented |
| Manager | Offered when adding or editing an admin. Shown in store and call-sheet screens | `add_admin_manager_widget.dart`, `edit_admin_widget.dart`, `stores_widget.dart`, call-sheet list widgets | Implemented |
| Rep | Offered when adding or editing a user. Default selected tab on the users screen is `Rep` | `add_user_widget.dart`, `edit_user_widget.dart`, `users_widget.dart` | Implemented |
| Super admin | Boolean, not a role string. Navbar shows Users and Customers only when `FFAppState().superAdmin == true`. Dashboard has a super-admin branch | `navbar_widget.dart`, `dashboard_widget.dart`, `user.super_admin` | Implemented |
| App user | Separate `app_user` table (mobile number, name, sites). A screen `app_users_list` exists under `lib/pages/user/` and is not a GoRouter route | `app_user.dart`, `app_users_list_widget.dart` | Implemented. How an app user differs from `user` is **Unknown** |

Login sets `selectedCustomer` to `1` for every successful sign-in. Provenance: **Implemented**. Whether every deployment has a single customer with id 1 is **Unknown / Requires Confirmation**.

People who are not signed in see the login screen on `/`. Other paths do not set `requireAuth: true`, so the router does not send them to `/login`. Provenance: **Implemented** (`lib/flutter_flow/nav/nav.dart`).

## Main workflows

### W1 — Sign in

Trigger: a person opens `/` or `/login` and submits email and password.

Implemented behaviour:

1. `authManager.signInWithEmail` signs in with Supabase Auth.
2. If that returns null, the handler returns and stays on the page.
3. The client queries `user` where `id` equals the auth user id.
4. It stores display name, email, profile image, `role`, and `super_admin` in secure storage.
5. It sets `selectedCustomer` to `1`, page title `Home`, page detail `Dashboard`.
6. It navigates to `/dashboard`.

If the `user` query returns no row, the handler force-unwraps `firstOrNull` and `superAdmin`. Provenance: **Implemented**. The resulting error behaviour is **Unknown** (no dedicated empty-state branch was found).

A profile image fallback URL is `https://alb.datafyapp.co.za/storage/v1/object/public/uploads/R%20(1).png`. Provenance: **Implemented**.

`sign_up_with_email.dart` rejects mismatched passwords, calls `supabase.auth.signUp`, and comments that automatic email confirmation is enabled in Supabase settings. The comment is **Documented** inside the action. Whether confirmation is actually enabled on the server is **Unknown / Requires Confirmation**. The create-account route is `/createAccount`. Whether that page calls this action end to end was not traced through every generated callback.

`SupabaseAuthManager.resetPassword` exists. No screen in the searched widgets calls it. Provenance: **Implemented** method, **Unknown** whether a person can start a reset from the UI.

`signInWithApple` is declared on the auth manager. No call site was found outside the manager. Provenance: **Implemented** capability, unused from the screens searched.

### W2 — Open the platform

Trigger: a signed-in person uses the sidebar.

Always shown: Dashboard, Tasks, Stores, Products, Promos, KaChing, Surveys, Notifications (the label text is `Notfications`), Profile.

Shown only when `superAdmin` is true: Users, Customers.

Routes that exist and are not items in that sidebar: `/sjreport`, `/callsheet`, `/nonIssues`, `/viewSurvey`, `/viewSurveyResponse`, `/createSurvey`, `/createAccount`, `/baseTemplatePage`, `/baseTemplatePageCopy`.

Provenance: **Implemented**.

### W3 — Load stores for the session

Trigger: `getStores` in `lib/actions/actions.dart`.

- If `userRole` is `Admin`, call `storesAdminCopyCopy` and store the result in `adminStores`.
- Otherwise call `userLinkedStoresCopyCopyCopy` with the user id and store the result in `userStores`.

Later screens (products, surveys, promos, KaChing) offer store codes from `adminStores` when the role is Admin and from `userStores` otherwise.

Provenance: **Implemented**. The view or function behind those REST calls is not in this repository.

### W4 — Dashboard

Trigger: open `/dashboard`.

`getDashboardOverview` calls RPC `get_dashboard_overview` with the current auth user and a customer id. The action comment says the payload includes stores linked to the user for that customer, reps and managers on those stores, linked KaChings, promos, and surveys, tasks fixed in the last 7 days, and top 5 stores and reps.

Other dashboard RPCs present as custom actions: `get_store_scores`, `get_active_users`, `get_last_user_activity`, `get_combined_week_report_by_store`, `get_combined_week_report_by_region_channel`, `get_user_stores_for_report`.

Provenance: **Implemented** client calls. The SQL of each RPC is **Unknown** (not in this repository).

### W5 — Tasks

Trigger: open `/tasks`.

The tasks area lists tasks, filters stores by role, and can call `analyzeImageApi` with an image id. That action POSTs JSON `{"image_id": imageId}` to `https://i78eyrnc87.execute-api.eu-west-1.amazonaws.com/Stage/analyze-image` with no auth header in the action. Success and failure are printed. They are not shown as a structured error in the action itself.

Export uses RPC `export_tasks_to_file` and `get_tasks_for_store_latest_week`.

A task row can carry status, type, day, week, barcode, price, count, note, user, customer, call sheet id, and SJ report id (`task.dart`).

Provenance: **Implemented**. Allowed status values are **Unknown / Requires Confirmation** (no enum was found in the client).

### W6 — Stores and stock issues

Trigger: open `/stores` or `/nonIssues`.

A store has name, address, GPS, region, channel, banner, store code, suburb, town, and numeric issue counters: `oos`, `negative_stock`, `soh_no_sales`, `no_sales_100_days`, `seven_day_cover`, `visibility`, `availability`, `non_issues`, `fixed_count`, `not_fixed_count`. Category payloads on the store include grocery groups such as baking aids, beans, beverages, condiments, desserts, fruit, gravy, and infant foods.

Admins and managers see different controls on the stores screen (`userRole == 'Admin'` and `userRole == 'Manager'`).

Provenance: **Implemented**. The business meaning of each counter is **Inferred** from the column names. Confirmation of the calculation (client versus database) is **Unknown**.

### W7 — Products

Trigger: open `/products` with optional `tabIndex` and `productHistory`.

Products have customer, category, brand, product, variant, size, barcode ending, article, and image paths (`product.dart`). Store-product links, a processing queue, and a machine-learning table (`product_ml`, `model_versions`) are bound in the client.

Provenance: **Implemented**. What the model scores mean is **Unknown / Requires Confirmation**.

### W8 — Promos and KaChing

Trigger: open `/promos` or `/kachingNew`.

A promo has channel, store type, region, brand, product, variant, size, category, `issue_for_feedback`, start and end dates, status, and an image. Users can be linked (`promos_linked_users`).

A KaChing row has the same retail dimensions plus `challenge_type`, `kaching_challenge_prompt`, `reward`, and status. Users can be linked. Export of active KaChings calls RPC `get_active_kachings_with_activity`. The user row has an integer `kaching` field. The navbar label is `KaChing`.

Store filters follow the Admin versus linked-store split.

Provenance: **Implemented**. The meaning of KaChing (a field challenge with a reward) is **Inferred** from those columns. Legal or payout rules are **Unknown**.

### W9 — Surveys

Trigger: open `/surveys`, `/createSurvey`, `/viewSurvey`, or `/viewSurveyResponse`.

A survey has brand, brand logo, category, title, submitted flag, and customer. Questions support answer types `string`, `number`, `boolean`, `choice`, `multiChoice`, and `image` (`view_survey_widget.dart`). Responses and store links have their own tables.

`FFAppState` persists draft answer lists whose names match grocery ranges (juice, canned fruit, jams and spreads, jelly, baby food, canned tomato, canned vegetables, baked beans, pickles, gravies and coatings, canned meats, cross merchandising, and several Tuffy bag ranges).

Provenance: **Implemented** storage of those lists. Whether they are still the live survey catalogue or leftover FlutterFlow state is **Unknown / Requires Confirmation**.

### W10 — Users and customers (super admin)

Trigger: `superAdmin` is true and the person opens `/users` or `/customers`.

User create/edit offers role chips:

- Add user: Admin or Rep.
- Edit user: Rep or Admin.
- Add admin/manager: Admin or Manager.
- Edit admin: Manager or Admin.

Profile images upload to the Supabase bucket `uploads`.

A customer has name, logo, email, telephone, cell, contact person, and address. Link tables: `customer_user`, `customer_store`, `customer_client`, `customer_brand`.

Bulk store linking and rep/manager linking screens exist under `lib/pages/users/` (`link_store_to_user_bulk`, `link_rep`, `link_manager_single`, `link_user_customer`, `link_admin_customer`). They are not top-level routes; they are opened from the users flow.

Provenance: **Implemented**.

### W11 — SJ reports and call sheets

Trigger: open `/sjreport` or `/callsheet`, or an embedded upload widget.

SJ report rows describe a store visit result: date, region, cluster, geography, channel, store, category, brand, product, variant. Related tables: `sjreport_spar`, `sjreport_summary`, `sjreport_archive`, `sjreport_uploads`. PDF and CSV export actions exist (`export_to_pdf_sjreport`, `export_to_csv_sjreport`).

A call sheet has region, channel, division, names, calling name, and surname. Screens include call cycle list, completed list, and history, and they reference the string `Manager`.

Neither destination is in the sidebar section that was read. Provenance: **Implemented** routes. How a person reaches them in the current UI is **Unknown / Requires Confirmation**. The expansion of "SJ" is **Unknown**.

### W12 — Notifications and activity

`/notifications` is in the sidebar. Tables `notification`, `logs`, and `log` exist. `update_user_activity` calls RPC `update_user_activity`.

Provenance: **Implemented** client. When activity is recorded, and what a notification row contains, was not fully traced. **Unknown** beyond the presence of the screen and RPC.

## Functional requirements

### FR-001 — Email sign-in

The system signs a person in with email and password through Supabase Auth, loads their `user` row, and stores role and super-admin flag.

- Actor: any person with an auth user and a `user` row.
- Trigger: Sign In on `/login`.
- Inputs: email, password.
- Output: navigation to `/dashboard` and persisted app state.
- Validation: delegated to Supabase Auth. The client returns early when sign-in returns null.
- Error: missing `user` row is not handled explicitly.
- Evidence: `login_widget.dart`, `supabase_auth_manager.dart`.
- Implementation: Implemented.
- Confidence: High.

### FR-002 — Role-based store list

Admins receive the admin store list. Every other role receives stores linked to the user id.

- Actor: signed-in user.
- Trigger: `getStores`.
- Evidence: `lib/actions/actions.dart`.
- Implementation: Implemented.
- Confidence: High.
- Confirmation: the server-side filter (RLS versus the view) is not in this repository.

### FR-003 — Super-admin navigation

Users and Customers appear in the sidebar only when `superAdmin` is true.

- Evidence: `navbar_widget.dart`.
- Implementation: Implemented.
- Confidence: High.
- The routes `/users` and `/customers` themselves do not set `requireAuth` and do not check `superAdmin` in the router. Whether the page body also hides data is **Requires Confirmation** if a non-super-admin opens the URL directly.

### FR-004 — Dashboard overview

The dashboard requests overview statistics for a customer id via `get_dashboard_overview`.

- Evidence: `get_dashboard_overview.dart`.
- Implementation: Implemented client.
- Confidence: Medium (RPC contract is described in a comment; SQL was not inspected).

### FR-005 — Task image analysis

From the tasks screen the client can POST an image id to the Stage analyze-image API.

- Evidence: `tasks_widget.dart`, `analyze_image_api.dart`.
- Implementation: Implemented.
- Confidence: High for the call. The API's response contract and who may invoke it are **Unknown**.

### FR-006 — Survey answer types

Survey questions render `string`, `number`, `boolean`, `choice`, `multiChoice`, and `image` answers.

- Evidence: `view_survey_widget.dart`.
- Implementation: Implemented.
- Confidence: High.

### FR-007 — File export

The client can export tasks, survey responses, SJ reports, promos, KaChings, GPS, historical data, activity, store visibility, and SDC reports to PDF, CSV, or Excel through custom actions and RPCs.

- Evidence: `lib/custom_code/actions/export_*.dart`.
- Implementation: Implemented.
- Confidence: Medium (each exporter was not executed).

### FR-008 — Public web shell

The web app title is Datafy Web, robots meta is `noindex`, and the description meta is "Built with FlutterFlow."

- Evidence: `web/index.html`.
- Implementation: Implemented.
- Confidence: High.

## Integrations

| System | Purpose | Direction | Auth in this repo | Failure behaviour |
|---|---|---|---|---|
| Supabase `https://datafy.appmonitor.co.za` | Auth, PostgREST, RPC, storage | Client to server | Anon key hardcoded in `lib/backend/supabase/supabase.dart`. User JWT after sign-in | Sign-in failure stays on the page. Most API helpers check `succeeded` before writing app state. RPC errors in dashboard actions return an empty list |
| AWS API Gateway Stage `analyze-image` | Image analysis | Client POST | No header in `analyze_image_api.dart` | Prints status code. No user-facing error in the action |
| `https://alb.datafyapp.co.za` | Default profile image | Browser GET of a public storage URL | None in the URL | Flutter image failure behaviour was not traced |
| SonarQube `https://scan.appmonitor.co.za` | Pipeline analysis | Pipeline to Sonar | `$SONAR_TOKEN` | Not an app runtime |
| Supabase `https://supabase.appmonitor.co.za` | Pipeline writes `deploy_results` | Pipeline POST | `$SUPABASE_API_KEY` | Pull-request pipeline only, as currently uncommented |
| AWS S3 and CloudFront | Web deploy of `./build/web` | Pipeline | `$AWS_ARN` OIDC | Step is commented out on `main` and on the default pipeline |

The anon key is a client credential committed in source. This PRD does not copy it. Whether it is the intended public anon key is **Requires Confirmation**. No service-role key was found in the files read.

## Constraints

- Locale list is English only (`Locale('en', '')` in `main.dart`).
- Environment name is the constant `Production`. The JSON file only supplies a version string.
- Auth flow type is implicit.
- Web router uses path URLs (`usePathUrlStrategy`).
- Package is `publish_to: none`.
- Android application id is `coza.datafy.web`. Whether Android or iOS builds are shipped is **Unknown**.

## Planned behaviour

No roadmap, ticket list, or TODO product spec was found. Commented pipeline steps (deploy, Bandit, email, Slack) are operational switches, not product features. Provenance: **Unknown** whether deploy is planned to be turned back on.

## Requirements that need confirmation

1. Is `main` at `df8299e` the build users open?
2. What is the production web URL?
3. Is customer id `1` correct for every sign-in?
4. Which role may open `/users` and `/customers` by URL?
5. Are unauthenticated deep links accepted on purpose?
6. What do SJ report and SDC report stand for, and are those screens still in use?
7. Are the Tuffy and grocery survey drafts in `FFAppState` current catalogues?
8. Is the Stage analyze-image API still the production model endpoint?
9. Is FlutterFlow still the source editor, or is `lib/` now hand-maintained?
10. What Row Level Security rules does the Supabase project enforce? They are not in this repository, so the client cannot be treated as the full access-control story.
