# Business rules — pestelite_html (Pest Elite)

Evidence date: 2026-10-06.

Source: `main` at `c5476a0`, plus Form.io form definitions fetched that day. Submissions were not read.

No business-rules document exists in the repository. Rules below are copied from the screens or from the published forms. Anything derived rather than written as a rule is marked **Inferred**.

## Access

### BR-001 — Login is required for lists

**Implemented.** Sites, clients, pesticides, and users all GET their submission collection with header `x-jwt-token` taken from `localStorage`. If that response is not OK, the page navigates to `./index.html`.

The login request itself sends an empty `x-jwt-token`.

There is no rule in the UI for session length, refresh, or logout. The token stays in `localStorage` until something else removes it.

### BR-002 — Login fields

**Implemented** in `www/index.html`. The email input is `type="email"` and `required`. The password input is `type="password"` and `required`. The browser blocks submit when either is empty or the email is not a valid email shape.

**Implemented** on the Form.io form `pestelitelogin`, and not used by the active login path: email and password are not required. The active path does not render that form.

A failed login shows the alert text `Could not log you in`. **Inferred:** the code then still evaluates the success path with an empty body and throws. There is no separate message for a network error.

### BR-003 — Who counts as an admin

**Implemented** on `www/sites.html` only. After load, the app loads one user submission and treats the user as an admin when `data.role` is exactly the string `admin`.

**Inferred** intent: hide the navigation unless the user is an admin, because the toggler starts with class `hidden` and that class is removed only for admins. No stylesheet in the repo defines `.hidden`, and Bootstrap 4 does not either. The other three pages show the same links with no role check. So the admin rule is not actually enforced by the pages as they are written.

**Requires Confirmation:** whether admin-only navigation was the business rule.

### BR-004 — Roles that exist on the user record

**Implemented** on Form.io resource `pesteliteusers`. The role values are `none`, `admin`, `client`, and `technician`. Email on that resource is required and unique. Tech No is hidden unless the role is `technician`.

The HTML does not mention `none`, `client`, or `technician` except to print `data.role` on the user card. No further permission rule for those values is in the repository. **Unknown / Requires Confirmation.**

### BR-005 — Form.io access lists

**Implemented** in the published form JSON. Access entries include the Form.io anonymous role and three other role ids. The names of those three ids are not in git.

This document does not treat the anonymous grants as an approved business rule. They are what the form JSON contained on the evidence date. **Requires Confirmation** whether anonymous read or create is intentional. Submissions were not requested, so the live effect of those grants was not observed.

## Site service reports

### BR-006 — What a site card shows

**Implemented** in `www/sites.html`.

- Heading: `data.client`
- Line: `Technician: ` plus `data.technician`
- Line: `Next visit: ` plus the characters of `data.nextVisit` before the first `T`, or `TBD` when `nextVisit` is missing
- Line: `data.address`

**Inferred:** `client` and `technician` are easy to confuse with `clientName` and `techName`. On the live form, `client` and `technician` are calculated fields on the non-conformance section. `address` is a calculated text field on the service report, set from the selected client's Nominatim `display_name` when that path exists. If the calculation did not run, the card still prints the value, including the JavaScript text `undefined`.

### BR-007 — Required service-report inputs

**Implemented** on Form.io form `digimonkv1`, not in the HTML. Select Client and Date of Visit are required. The client dropdown is required with error text `Client Not Selected`.

The HTML Add and Details actions do not add their own validation.

### BR-008 — Client details copied onto the service report

**Implemented** as Form.io calculate logic on `digimonkv1`.

- Address is set from `selectClient.data.address.address.display_name` when that object exists.
- Contract number is set from `selectClient.data.contractNumber`.
- Client name is set from `selectClient.data.clientName`.
- Telephone is set from `selectClient.data.telephone`.

The same pattern fills client and technician name fields on the non-conformance and recommendation sections. The exact calculate scripts for every copy field were not all transcribed. The ones above were read from the form.

### BR-009 — Infestation levels

**Implemented** on `digimonkv1`. Rodents, rats, mice, bedbugs, fishmoths, cockroaches (including American, German, and Other), flying insects, and fleas use the values `none`, `low`, `medium`, and `high` (labels None, Low, Medium, High).

Previous recommendation outstanding is Yes `yes` or NO `no`.

Risk on the recommendation report is Select `select`, Low `low`, Medium `medium`, High `high`.

Action by client is No `no` or Yes `yes`.

Checklist rows use No `no` / Yes `yes` for inspected, activity, and rebaited flags where those options were present. Some selects on the same form had empty option lists in the fetch (`bConditionsFoundB1`, `bConditionsFoundB2`, and others). Their allowed values are **Unknown**.

### BR-010 — Pesticides used on a visit

**Implemented** on `digimonkv1`. A repeating grid selects a pesticide from `pesticidesregister` submissions, showing `data.pesticide`. Type, registration number, and dilution are calculated from the selected pesticide. Quantity is a text field on the row.

### BR-011 — Next visit display versus storage

**Implemented.** The form field `nextVisit` is a datetime. The card shows only the date portion before `T`, or `TBD`. The card does not show time and does not convert timezone. **Inferred:** a value without a `T` would be shown in full, because `split('T')[0]` returns the whole string.

## Clients

### BR-012 — Client card contents and order

**Implemented** in `www/clients.html`.

- Heading: `data.clientName`
- Line: `Contact Person: ` plus `data.siteContact`
- Then `data.telephone` and `data.email`

The list is sorted with `<` and `>` on `clientName` before render. That comparison is case-sensitive and does not use a locale. Missing names sort by JavaScript's comparison of those values. Other lists are left in API order.

### BR-013 — Client register fields

**Implemented** on `clientregister`. No field was marked required in the fetched form. Contract number is a number. Telephone is a phone-number component. Email is an email component. Addresses use Nominatim with manual entry available.

Trap and threshold grids are part of the client record (flying insects, cockroaches, rodents, stored-product pests), including amounts, locations, and trap types. The list screen does not apply a threshold. No rule in git says what a threshold means when a count is exceeded. **Unknown / Requires Confirmation** beyond "the field exists on the client form".

Service frequency, contract expiry, next service due, and tube replacement month are stored on the client. Nothing in the HTML schedules a visit from them.

## Pesticides

### BR-014 — Pesticide card and types

**Implemented** in `www/pesticides.html`. The card shows `data.pesticide`, `data.type`, and `data.dosageDilution`.

**Implemented** on `pesticidesregister`. Type is one of: `none`, `wg`, `granualBait`, `sC`, `aerosol`, `liquidBait`, `iGR`, `fumigant`, `baitBlock`, `gB`, `foggingSolution`, `dP`, `foggingCompound`, `wG`. The default on the form is `none`. Labels include the spelling "Granual Bait". The card prints the stored value, not the label.

Registration number and dosage are free text. None of the pesticide fields are marked required.

## Users

### BR-015 — User card

**Implemented** in `www/users.html`. The card shows `data.name`, a space, `data.surname`, then `Role: ` plus `data.role`, then `data.email`. The password is not written into the card. The details modal loads the whole form, including the password component.

### BR-016 — User email

**Implemented** on `pesteliteusers`. Email is required and unique. Name, surname, password, and role are not marked required. Password is a protected Form.io password component.

The login screen does not create users. Creating a user is a separate form on `users.html`. Whether that user can then log in through `pestelitelogin` is **Unknown / Requires Confirmation**.

## Editing and deleting

### BR-017 — Save from the details modal

**Implemented.** Details builds a Form.io form on the submission URL. On that form's `submit` event, the list is fetched again and the modal is toggled. The app does not send its own PUT. Saving is whatever Form.io does inside that rendered form.

### BR-018 — Add does not close itself

**Implemented.** The Add form's submit handler refreshes the list and does not toggle the modal.

### BR-019 — No delete control

**Implemented** as absence. No button deletes a submission. The unused update modal is not a delete path.

## Data handling notes that are visible in code

These are implementation facts, not approved policy.

- The signed-in user object (`json.data`) is stored as JSON in `localStorage` under `pest-elite-user`. The user id is stored with `JSON.stringify`, quotes included.
- List and detail values are inserted into HTML with template strings. The pages do not escape them.
- The Content-Security-Policy meta tag is commented out, and the comment in the page says inline scripts are disabled unless `unsafe-inline` is added. The pages then use inline scripts anyway, which is possible because the policy tag is not active.
- `config.xml` allows http and https intents to any host.
