# PRD — pestelite_html (Pest Elite)

Evidence date: 2026-10-06.

Source: `main` at `c5476a0` (2024-02-19). Live Form.io form definitions were fetched the same day from `https://rzkiqpzlxxtfqjl.form.io`. Submissions were not read.

There is no requirements document in the repository. Requirements below are taken from the screens and from the published form definitions. Each item is labelled.

Provenance labels: **Documented** (written in the repo), **Implemented** (present in code or in the fetched form), **Inferred**, **Planned**, **Unknown / Requires Confirmation**.

## Product purpose

**Inferred.** Pest Elite is a data-entry front end for a pest-control operation. A person signs in, then works with site service reports, clients, pesticides, and users. Records are stored in Form.io, not in this repository.

**Documented** names only: widget `<name>PestElite</name>`, npm `displayName` `PestElite`, widget id `com.elite.pest`, version `1.0.0`. The Cordova description string is still "Sample Apache Cordova App".

## Users and roles

The user resource `pesteliteusers` has a Role field with these stored values:

| Label | Value |
|---|---|
| None | `none` |
| Admin | `admin` |
| Client | `client` |
| Technician | `technician` |

Provenance: **Implemented** on the Form.io resource. The HTML only branches on `json.data.role === "admin"`, and only on `www/sites.html`. No screen checks for `client` or `technician`. What those two roles are allowed to do is **Unknown / Requires Confirmation**.

The app does not show a logged-out public area beyond the login page. There is no self-registration screen. Users are created from `www/users.html`.

## Major workflows

1. Open `index.html`, enter email and password, and land on `sites.html` when Form.io accepts the post.
2. From a list screen, read cards, open a record in a modal, or open an Add modal and submit a Form.io form.
3. On `sites.html` only, after load, fetch the current user and, when the role is `admin`, toggle a CSS class on the navigation.

There is no logout step in the UI.

## Integrations

| System | Direction | Trigger | Data |
|---|---|---|---|
| Form.io `rzkiqpzlxxtfqjl.form.io` | App to Form.io | Login, list, create, open a record | JSON submissions and the `x-jwt-token` header |
| jsDelivr and cdn.form.io | CDN to browser | Page load | Bootstrap, jQuery, Popper, Form.io client |
| Nominatim | Form.io address component to Nominatim | Address lookup on the client form | Address search. The HTML does not call Nominatim itself |
| `pestelite.co.za` | Form.io HTML component loads an image | Site form render | Logo URL only. The site itself is not integrated in git |

Authentication to Form.io, failure behaviour, and field rules are specified in the requirements below.

## Constraints

- The repository has no server of its own. Behaviour of validation beyond the HTML `required` attributes is whatever the live Form.io forms enforce.
- Form definitions in Form.io were modified after `c5476a0`. A form change does not require a git change, and a git checkout does not pin the form schema.
- Content-Security-Policy meta tags are commented out on every page.
- `config.xml` allows navigation intents to any `http://` and `https://` URL.

## Functional requirements

### FR-001 — Application identity

**Requirement:** The packaged app is named PestElite, id `com.elite.pest`, version `1.0.0`, and starts at `index.html`.

**Who:** the Cordova container, when one is built.

**Trigger:** app launch.

**Inputs:** `config.xml`.

**Expected behaviour:** Cordova loads `index.html`. An icon is declared as `./www/img/logo.png` at 57 by 57.

**Outputs:** the login screen.

**Evidence:** `config.xml`.

**Implementation:** Implemented.

**Confidence:** High for the widget file. Whether a store build exists is Unknown.

### FR-002 — Login

**Requirement:** The start page shows the Pest Elite logo, an email field, a password field, and a Login button. Both fields are marked `required` in the HTML.

**Who:** any person who can open the page.

**Trigger:** submit on the login form.

**Inputs:** email string, password string.

**Expected behaviour:**

1. The browser submit is cancelled.
2. The app POSTs JSON `{ data: { email, password } }` to `https://rzkiqpzlxxtfqjl.form.io/pestelitelogin/submission` with `Content-Type: application/json` and `x-jwt-token` set to an empty string.
3. When the response is not OK, the app shows `alert("Could not log you in")`.
4. When the response is OK, the app stores `response.headers.get("x-jwt-token")` in `localStorage` key `x-jwt-token`, stores `JSON.stringify(json.data)` in `pest-elite-user`, stores `JSON.stringify(json['_id'])` in `pest-elite-user-id`, and sets the location to `./sites.html`.

**Error conditions:**

- A failed HTTP response alerts and does not navigate. The following `.then` still runs with no JSON body, so it throws when it reads `json.data`. Provenance: **Implemented**. The user remains on the login page only if that throw happens before navigation. There is no `catch`.
- A network failure has no handler.
- The HTML requires email and password. The published Form.io form `Pest Elite Login` marks both fields not required. The live login path uses the HTML form, not `Formio.createForm`, so the HTML `required` flags are the ones the browser enforces.

**Evidence:** `www/index.html`. Form title and field keys from the fetched `pestelitelogin` form.

**Implementation:** Implemented.

**Confidence:** High for the request. Whether Form.io treats that POST as a real user login (versus storing a login submission) was not confirmed: `GET .../pestelitelogin/action` returned 401, so server-side form actions were not readable.

**Discrepancy:** a commented block in the same file used `Formio.createForm` on `.../pestelitelogin`, saved `submission.data` to `sessionStorage` key `pestEliteUser`, and redirected to `./pesticide.html`. That file does not exist. The active path redirects to `./sites.html`.

### FR-003 — Session on later screens

**Requirement:** List requests send `x-jwt-token` from `localStorage`. If the list response is not OK, the app navigates to `./index.html`.

**Who:** a person who opened sites, clients, pesticides, or users.

**Trigger:** page load, and again after a modal submit refreshes the list.

**Inputs:** `localStorage["x-jwt-token"]`, which may be null.

**Expected behaviour:** non-OK list response replaces the page with the login screen. An OK response renders cards.

**Error conditions:** no `catch` on the fetch. A token is not cleared when the redirect happens. There is no logout control that removes `x-jwt-token`, `pest-elite-user`, or `pest-elite-user-id`.

**Evidence:** `www/sites.html`, `www/clients.html`, `www/pesticides.html`, `www/users.html`.

**Implementation:** Implemented.

**Confidence:** High.

### FR-004 — Admin navigation on the sites screen

**Requirement:** On `www/sites.html`, after load, the app reads `pest-elite-user-id`, strips double quotes by taking `split('"')[1]`, and GETs `https://rzkiqpzlxxtfqjl.form.io/pesteliteusers/submission/{id}` with the JWT. If `json.data.role === "admin"`, it toggles the class `hidden` on `.navbar-toggler` and on `#navbarSupportedContent`.

**Who:** the signed-in user, on the sites screen only.

**Trigger:** `window.onload` on `sites.html`.

**Inputs:** user id written by FR-002. That id is `JSON.stringify`'d, so the stored value includes quote characters. The split assumes those quotes are present.

**Expected behaviour:** the author intended the nav to be hidden until the user is an admin. The toggler and the collapse div are given class `hidden` in the HTML. No stylesheet in the repository defines `.hidden`. The page loads Bootstrap 4, which does not provide a `.hidden` class (that class belonged to Bootstrap 3). The collapse menu is already hidden by Bootstrap's `collapse` class until the toggler is used.

**Outputs:** a class toggle when the role string is exactly `admin`. Other roles, a failed user fetch, or a missing id do not redirect.

**Evidence:** `www/sites.html` lines that query `.navbar-toggler` and compare `json.data.role`.

**Implementation:** Implemented as a class toggle. Whether it hides anything is **Inferred** as no, because no `.hidden` rule is shipped. **Requires Confirmation** if some host CSS was expected.

**Discrepancy:** `clients.html`, `pesticides.html`, and `users.html` render the same links with no role check. Anyone who can open those URLs sees the navigation, subject only to Form.io rejecting the list call.

### FR-005 — Site service reports

**Requirement:** `www/sites.html` is titled "Pest Elite - Site Forms". It lists submissions of `digimonkv1` and can create one.

**Who:** a person whose list request succeeds.

**Trigger:** page load; Add button; Details button.

**Inputs:** JWT. Create and detail forms are the Form.io form itself.

**Expected behaviour:**

- While loading, a spinner is shown in `#sites-table`.
- Each submission becomes a card. The card shows `data.client`, `Technician: ` plus `data.technician`, `Next visit: ` plus the date portion of `data.nextVisit` before `T`, or the text `TBD` when `nextVisit` is missing, then `data.address`, and a Details button.
- Details opens `#viewSiteModal` and builds a Form.io form from `.../digimonkv1/submission/{id}`. On that form's submit, the list reloads and the modal toggles.
- Add opens `#newSiteModal`, which contains a form built from `.../digimonkv1` (no submission id). On submit, the list reloads. The code does not close the Add modal.
- `openUpdateModal` builds the same submission form in `#updateSiteModal`. Nothing in the page calls `openUpdateModal`.

**Validation:** the app does not validate site fields. The published form requires Select Client (`selectClient`) and Date of Visit (`dateTimeVisit`). Other service-report fields observed on the form are not marked required. Address, contract number, client name, and telephone are calculated from the selected client.

**Error conditions:** non-OK list response goes to the login page (FR-003). Card text is interpolated directly into HTML.

**Evidence:** `www/sites.html`. Field list from the fetched `digimonkv1` form (title `Digimonkv1`, modified 2024-02-23).

**Implementation:** Implemented.

**Confidence:** High for the screen. The card keys `client`, `technician`, `nextVisit`, and `address` do exist on the form. `client` and `technician` are calculated fields on the non-conformance section, not the main client label `clientName`. A submission that never calculated those keys will render the text `undefined`. That outcome is **Inferred** from the template literals.

The form is a multi-section pest-control report. Sections present in the fetched definition:

- Service Report: client select (URL `.../clientregister/submission`, template `clientName`), address, contract number, visit date/time, client name, telephone, emergency, area size, recommendations, capacity, previous recommendation outstanding (Yes/No), equipment checkboxes, infestation levels (None / Low / Medium / High) for rodents, rats, mice, bedbugs, fishmoths, cockroaches (American, German, Other), flying insects and fleas.
- Pesticides grid: pesticide name from `.../pesticidesregister/submission`, type, registration number, and dilution calculated, plus a quantity.
- Signatures: client name, client signature, time in/out pairs, technician selected from the user resource, technician number, technician signature.
- Check List: bait stations, general area, insect monitors, insect light traps, red top, aerosol units.
- Non-Conformance Report: report numbers, dates, client, technician, conditions found, action taken, next visit, action by client (Yes/No), signature.
- Recommendation Report: client, technician, problems, recommended action, risk (Select / Low / Medium / High), dates, signatures, an image upload, a description, and a calculated `formCompleted` checkbox.
- Site map image upload.

Several selects on that form (`bConditionsFoundB1`, `bConditionsFoundB2`, `typeCockroaches`, and others) had empty `data.values` in the fetch. Their option lists are **Unknown**.

Why the form path is `digimonkv1` is **Unknown / Requires Confirmation**.

### FR-006 — Clients

**Requirement:** `www/clients.html` is titled "Pest Elite - Client Forms". It lists `clientregister` submissions, sorted by `data.clientName` with a case-sensitive string comparison, and can create one.

**Who:** a person whose list request succeeds.

**Trigger:** page load; Add; Details.

**Expected behaviour:** each card shows `clientName`, `Contact Person: ` plus `siteContact`, `telephone`, and `email`, plus Details. Details and Add use `.../clientregister/submission/{id}` and `.../clientregister` the same way as sites. The update modal function exists and is not called. Add does not close its modal on submit.

**Fields on the published form** (title `PE Client Register`, modified 2024-07-12), none marked required in the fetch:

- Client name, street address and postal address (Form.io address component, provider Nominatim, manual mode enabled), contract number (number), telephone, email, account number, site contact, contract expiry, next service due, tube replacement month, service frequency.
- Thresholds and grids for flying insects, cockroaches, rodents, and stored-product pest activity (amount, location, trap type).

Flying-insect trap types: None, Insect Light Trap, Red Top Fly Bag, Aerosol Dispensor. Cockroach locations include Canteen, Lockers, Kitchen, Dinning area, Toilets, Other. Cockroach types: None, Residual Spray, Insect Monitor. Rodent trap types: None, Carton Trap, Mechanical Trap, Non-Toxic Bait Station, Tamperproof Bait Station. Stored-product type: None, SPI Monitors.

**Evidence:** `www/clients.html` and the fetched form.

**Implementation:** Implemented.

**Confidence:** High.

**Note:** the list does not show the address, contract, or trap layout. Those appear only inside the Form.io modal.

### FR-007 — Pesticides

**Requirement:** `www/pesticides.html` is titled "Pest Elite - Pesticides". It lists `pesticidesregister` submissions in API order and can create one.

**Who:** a person whose list request succeeds.

**Expected behaviour:** each card shows `data.pesticide`, `Type: ` plus `data.type`, and `Dosage: ` plus `data.dosageDilution`, plus Details. Modal behaviour matches FR-006, against `pesticidesregister`.

**Fields on the published form** (title `PE PesticidesRegister`, modified 2024-04-15):

- Pesticide (text)
- Type: None, WG, Granual Bait, S.C., Aerosol, Liquid Bait, I.G.R, Fumigant, Bait Block, G.B, Fogging Solution, D.P., Fogging Compound, W.G.
- Reg No (text)
- Dosage/Dilution (text)

None of those fields are marked required in the fetched form. The card shows the stored type value (for example `granualBait`), not the label.

**Evidence:** `www/pesticides.html` and the fetched form.

**Implementation:** Implemented.

**Confidence:** High.

### FR-008 — Users

**Requirement:** `www/users.html` is titled "Pest Elite - Users". It lists `pesteliteusers` submissions and can create one.

**Who:** a person whose list request succeeds. The page does not check role.

**Expected behaviour:** each card shows `data.name`, `data.surname`, `Role: ` plus `data.role`, and `data.email`, plus Details. The list function is named `fetchSiteForms` and writes `#users-table`. Modal behaviour matches FR-006, against `pesteliteusers`. The password is not printed on the card. The details form is the full user form, which includes a password field.

**Fields on the published resource** (title `Pest Elite Users`, type `resource`, modified 2024-04-08):

- Name, surname (not required on the form)
- Email, type email, `unique: true`, `required: true`
- Password, protected
- Role: none, admin, client, technician
- Tech No, hidden by form logic unless role is technician

**Evidence:** `www/users.html` and the fetched resource.

**Implementation:** Implemented.

**Confidence:** High for the screen. How Form.io maps this resource onto the login in FR-002 is Unknown / Requires Confirmation.

### FR-009 — Shared navigation

**Requirement:** Sites, clients, pesticides, and users pages show a fixed top bar with the logo and links to `./sites.html`, `./clients.html`, `./pesticides.html`, and `./users.html`. The current page is marked active.

**Who:** anyone who can see the bar. On sites, see FR-004.

**Evidence:** the four list pages.

**Implementation:** Implemented.

**Confidence:** High.

### FR-010 — Unlinked pesticides page

**Requirement:** `www/sites/index.html` renders Form.io form `pesticidesregister` into `#formio` and writes the submission to the console. It does not list records, does not send a JWT, and is not linked from the other pages.

**Evidence:** `www/sites/index.html`.

**Implementation:** Implemented, and unused by the navigation.

**Discrepancy:** its script src is `js/index.js`, which resolves under `www/sites/js/` and does not exist. The Form.io script comes from the CDN, so the form can still load. The page title is "Hello World".

### FR-011 — Cordova device ready

**Requirement:** `www/js/index.js` listens for `deviceready` and then adds class `ready` to `#deviceready` and logs the Cordova platform id and version.

**Evidence:** `www/js/index.js`. Every current HTML page loads this file. None of them contain an element with id `deviceready`, and none include `cordova.js`.

**Implementation:** The listener is implemented. The sample UI it targets is not on the pages. If `deviceready` fires, `getElementById('deviceready')` is null and the classList call throws.

**Confidence:** High.

### FR-012 — No delete and no logout

**Requirement:** No screen offers delete, and no screen offers logout.

**Evidence:** the HTML files. `openUpdateModal` is the closest unused edit path; view-form submit is the path that can save changes if Form.io allows the update.

**Implementation:** Implemented as absence.

**Confidence:** High for the UI. Whether Form.io still allows delete via its own form chrome inside the modal depends on the form and was not exercised.

## Important behaviour discovered only in the live forms

These are **Implemented** on Form.io as fetched on 2026-10-06. They are not copied into git.

- Client select on a service report loads every `clientregister` submission and displays `clientName`.
- Pesticide rows load every `pesticidesregister` submission and display `pesticide`.
- Technician select uses Form.io resource id `65c0a2695e73db6988139702`, which is the `pesteliteusers` resource id, and displays `surname` then `name`.
- The site form's HTML component loads the logo from `pestelite.co.za`.

The published form JSON includes Form.io access lists. The anonymous role id `000000000000000000000000` appears on create, read, and update submission access for more than one of these forms. Three other role ids are also listed. Their names are not in the git repository. Whether anonymous callers can read submissions was **not tested**, because that would read customer records. **Requires Confirmation.**

## Non-requirements (not in the repo)

No documented requirement was found for invoicing, scheduling as a calendar, GPS, push notifications, offline storage, reporting exports, or a customer portal. The client and technician role values exist on the user form, but no separate portal is in this repository.
