# Business rules — datafy_web (Datafy Web)

Evidence date: 2026-10-17. Source: `main` at `df8299e`.

No business-rules document exists in the application repository or in Silicon Ark. Rules below are taken from the client. Server-side rules in Supabase (RLS, triggers, RPC bodies) were not in the repository and are **Unknown**.

Each rule is marked **Implemented** (the client enforces it) or **Inferred** (the client stores or branches on the data, and the rule is a reading of that behaviour).

## Identity and access

### BR-001 — Sign-in is email and password

**Implemented.** The login button calls `authManager.signInWithEmail` with the email and password fields. A null user aborts the rest of the handler (`login_widget.dart`).

### BR-002 — Role and super-admin come from the `user` row

**Implemented.** After sign-in the client reads `user` by auth id and copies `role` and `super_admin` into app state. The router does not decode the JWT for role.

### BR-003 — Missing profile is not a handled business case

**Implemented** omission. The login handler uses `firstOrNull!` and `superAdmin!`. There is no branch that tells the person their auth user has no `user` row.

### BR-004 — Sidebar admin areas follow the super-admin flag

**Implemented.** Users and Customers are built only when `FFAppState().superAdmin == true` (`navbar_widget.dart`). This is a visibility rule in the sidebar, not a router guard.

### BR-005 — Route authentication is off

**Implemented.** Every `FFRoute` leaves `requireAuth` at the default `false`. The redirect to `/login` inside `toRoute` runs only when `requireAuth` is true. `/` and the error page choose login versus dashboard from `loggedIn`. Other paths do not.

**Inferred.** A person who knows a path can open the widget without the router challenging them. Whether Supabase then returns empty data depends on RLS, which is **Unknown**.

### BR-006 — Admins see the admin store list; other roles see linked stores

**Implemented.** `getStores` calls `storesAdminCopyCopy` when `userRole == 'Admin'` and `userLinkedStoresCopyCopyCopy` otherwise (`lib/actions/actions.dart`). Product, survey, promo, and KaChing store dropdowns repeat that split.

### BR-007 — Role values offered in forms

**Implemented.**

| Form | Choices |
|---|---|
| Add user | Admin, Rep |
| Edit user | Rep, Admin |
| Add admin/manager | Admin, Manager |
| Edit admin | Manager, Admin |

No other role strings were found as chip options. Call-sheet and SJ-report widgets also compare against `Manager`. Task site add/edit compare against `Rep`.

**Unknown.** Whether the database constrains `user.role` to these three strings.

### BR-008 — Every sign-in selects customer 1

**Implemented.** `FFAppState().selectedCustomer = 1` is unconditional after a successful login.

**Inferred.** The client behaves as if customer id 1 is the working tenant. **Requires Confirmation** before treating that as an approved rule.

### BR-009 — User active flag

**Inferred.** `user.active` is an integer column. The client binding does not document which integers mean active. **Unknown / Requires Confirmation.**

### BR-010 — Password confirmation on the custom sign-up action

**Implemented** in `sign_up_with_email.dart`: if `confirmPassword != password`, the action returns `Passwords do not match. Please try again.` and does not call Supabase.

The action comment says automatic email confirmation is enabled in Supabase settings. That statement is in source. It was not verified against the Supabase project. **Requires Confirmation.**

### BR-011 — Account deletion and email change exist on the auth manager

**Implemented** methods: `deleteUser`, `updateEmail`. Whether a screen exposes them was not confirmed. **Unknown** as user-facing rules.

## Stores and stock

### BR-012 — A store is identified to pickers by store code, and labelled by name

**Implemented.** Dropdown `options` are store codes and `optionLabels` are names, from `adminStores` or `userStores`.

### BR-013 — Stock and availability are numeric fields on the store

**Inferred** from column names on `store`: out of stock (`oos`), negative stock, stock on hand with no sales (`soh_no_sales`), no sales for 100 days, seven-day cover, visibility, availability, non-issues, fixed count, not-fixed count, optimal (`optimul` in the binding). The client does not show the formula. **Unknown** where the numbers are calculated.

### BR-014 — Store processing is queued

**Inferred.** Tables `store_processing_queue`, `product_store_processing_queue`, and `queue_processing_log` exist, and `AddStoresToQueCall` exists in `api_calls.dart`. The queue's states and retry rules are **Unknown** (not in this repository).

### BR-015 — November 2024 backups are still bound

**Implemented** as table classes `store_backup_20241118` and `store_product_backup_20241128`. **Inferred** they are snapshots, not the live store. **Requires Confirmation** before anyone writes to them from the app.

## Tasks

### BR-016 — Tasks can point at a person, a customer, a call sheet, and an SJ report

**Implemented** columns on `task`: `user_id`, `customer_id`, `callsheet_id`, `sjreport_id`, plus `status`, `type`, `day`, `week`, `barcode`, `price`, `count`.

Allowed `status` and `type` values were not found as an enum. **Unknown.**

### BR-017 — Task export for a store uses the latest week

**Implemented** client call to RPC `get_tasks_for_store_latest_week` (`export_tasks_to_excel.dart`). What "latest week" means is defined in the RPC, which is **Unknown** here.

### BR-018 — Image analysis is a fire-and-forget POST

**Implemented.** `analyzeImageApi` posts `image_id` and prints the result. It does not update a row in the action body. Any write-back would be inside the AWS function or a later screen refresh. **Unknown.**

## Surveys

### BR-019 — Question answer type selects the input

**Implemented.** View survey renders `choice`, `multiChoice`, `string`, `number`, `boolean`, and `image`. Other types are not listed in that branch.

### BR-020 — A survey can be marked submitted

**Implemented** column `survey.submitted` (bool). The transition rules (who may submit, whether edits lock) were not read out of the generated widget. **Unknown / Requires Confirmation.**

### BR-021 — Draft survey answers for named grocery ranges are stored on the device

**Implemented.** `FFAppState` persists string lists such as `juiceSurvey`, `cannedFruitSurvey`, `jamsSpreadsSurvey`, `babyFoodSurvey`, `bakedBeansSurvey`, and Tuffy bag ranges (`budgetBagsTuffy`, `heavyDutyTuffy`, `kitchenTuffy`, `swingPedalTuffy`, `lightHeavyTuffy`, `tuffy`).

**Inferred.** These names are product categories the field team surveys. **Requires Confirmation** that they are current and not obsolete FlutterFlow state.

## Promos and KaChing

### BR-022 — Promos and KaChings are time-bounded rows with a status

**Implemented** columns `start_date`, `end_date`, and `status` on both `promos` and `kaching`.

**Unknown** which status strings are valid, and whether the client or the database hides rows outside the date range. The export RPC is named `get_active_kachings_with_activity`, which **Inferred** means "active" is a real status or date rule on the server.

### BR-023 — A promo carries an issue for feedback

**Implemented** column `promos.issue_for_feedback`. How that issue is raised or closed is **Unknown**.

### BR-024 — KaChing has a challenge prompt and a reward

**Implemented** columns `kaching_challenge_prompt`, `challenge_type`, and `reward`.

**Inferred.** A KaChing is a field challenge attached to a product, with a reward. Payout, eligibility, and proof rules are **Unknown**.

### BR-025 — Linking is explicit

**Implemented** tables `promos_linked_users`, `kaching_linked_users`, `survey_store_link`, `store_user_link`, `store_customer_link`. The users screens include single and bulk link flows.

**Unknown** whether a link is unique, and what happens on a duplicate submit.

## Customers

### BR-026 — Customer is the tenant-like parent

**Inferred.** Stores have `client_id`. Tasks, surveys, and products have `customer_id`. Dashboard overview takes `customerId`. Link tables attach users, stores, clients, and brands to a customer.

The login rule BR-008 fixes that parent to id 1 in the client. **Requires Confirmation.**

## Reporting

### BR-027 — SJ report rows are store-and-product observations

**Inferred** from columns: date, region, cluster, geography, channel, store name, category, brand, product, variant. A Spar-specific table `sjreport_spar` exists.

The name "SJ" is **Unknown**. Whether Spar is one channel among others is **Inferred** from the table name.

### BR-028 — Call sheets name people against region, channel, and division

**Implemented** columns on `callsheet` include `region`, `channel`, `division`, `names`, `calling_name`, `surname`. Screens mention `Manager`. The scheduling rule (how a call cycle is completed) was not extracted from the generated widgets. **Unknown / Requires Confirmation.**

### BR-029 — User activity is updated through an RPC

**Implemented** call `update_user_activity`. When the client invokes it, and what it stores, is **Unknown** beyond the action existing.

## Files

### BR-030 — Uploaded bytes go to the `uploads` bucket and are then addressed by public URL

**Implemented.** `uploadSupabaseStorageFile` uploads binary and returns `getPublicUrl`. Delete parses a public URL back to bucket and path.

**Inferred.** Objects in `uploads` are world-readable if the bucket is public. **Requires Confirmation** against the Supabase bucket policy. The client asks for the public URL, which is consistent with a public bucket.

## What this client does not decide

- Row Level Security, JWT claims, and which roles may write each table.
- How stock counters are computed.
- Whether email confirmation, banned users, or inactive users are rejected.
- Reward fulfilment for KaChing.
- The production meaning of customer id 1.

Those items need a human who can read the Supabase project or confirm the operating rules.
