# Business rules — pwa (1 for the Road)

Evidence date: 2026-09-28. Source: `master` `50d7009`.

Rules are taken from code or from copy on the page. **Inferred** means the behaviour is visible in code but no comment or sentence states it as a rule. This file does not add a rule the repository does not support.

There is no written business-rules document in the source repository.

## Who may book

**BR-001 — Two paths from one mobile number.** The login card offers Member and Non Member. **Documented** on `index.html`. **Implemented** as `loginVal` and `loginValCop`.

**BR-002 — A blank mobile number stops both paths.** Empty, null, or undefined does not call the API and does not open the voucher form. The field is marked with `customBorderBottom`. **Implemented** in `loginVal` and `loginValCop` (commit `50d7009`). No other format check exists. **Inferred**: any non-empty value is accepted by the script.

**BR-003 — Member means a cellphone match on `tblClient`.** `one_CheckLogin` strips a single leading `0`, removes spaces inside `strCell` for the comparison, and matches with `LIKE %number%`. **Implemented**. A contained substring can match. **Inferred** from the `LIKE` pattern; no comment says partial match is intended.

**BR-004 — No password or one-time code.** The member path sends only the cellphone. **Implemented**. **Inferred**: possession of a stored number is the only member check in this app.

**BR-005 — Unknown number stays on login.** When the API returns `result` equal to `"0 results"`, the page shows: "Please enter registerd number , or use Non Member with your active coupon code". **Implemented**. The member booking form is not opened on that result.

**BR-006 — Non Member does not look up the client first.** `loginValCop` only checks that the mobile number is non-empty, then shows the voucher form. **Implemented**.

## Voucher

**BR-007 — One voucher string is valid.** `one_BookRideWithVoucher` continues only when `voucher` equals `1FTR2021`. Otherwise the action returns message `invalid voucher`. **Implemented**. The comment on that `if` says the voucher section needs to be built in web-admin. A configurable voucher list is **not implemented** in this repository.

**BR-008 — The voucher field is not required in the browser.** `bookRideCoupon` checks name, pickup, and drop-off. It does not check that `#Voucher` is non-empty. An empty voucher is sent and fails BR-007 on the server. **Implemented**.

**BR-009 — The page still shows SUCCESS when the voucher is rejected.** The SUCCESS panel is shown when the POST is started, and the email is sent when the JSON parses. The script does not read `result.error`. **Implemented**. This disagrees with BR-007's server rejection. Both behaviours are in the repo. Which one a person should see is **Requires confirmation** if the product owner intended the server message to be shown.

## Client record

**BR-010 — A voucher booking reuses a client with the same cell.** The server selects `tblClient` with `LIKE %cellphone%` and, if any row exists, uses the last row's `ClientID` in the loop. The name typed on the form is not saved onto that row. **Implemented**. Leading zero is not stripped on this query, unlike BR-003. **Inferred**: the same handset can miss the member lookup and still miss the voucher-client lookup, or the reverse, because the two queries normalise the number differently.

**BR-011 — A new voucher client is inserted with fixed flags.** When no row matches, the insert sets `refMasterID` to `0`, email to an empty string, home address to the drop-off (`$data['to']`), and `blnMaster`, `blnMasterPaysSubs`, `blnMasterPaysUsage`, and `blnActive` to `1`. First and last user are `app`. Registered date is the server's `Y-m-d`. **Implemented**. The business meaning of the `bln*` flags is **Unknown**. No comment defines them.

**BR-012 — Name and surname are the same field.** The form has one "Name and Surname" input. The JSON sends that value as both `name` and `surname`. The server stores `name + " " + surname`, so the stored `strClient` repeats the typed value twice, separated by a space. **Implemented**. **Inferred** from the concatenation; the page does not say the name will be doubled.

## Booking row

**BR-013 — A booking stores pickup, drop-off, date, and time.** Both insert statements write `txtPickupAddress`, `txtDeliveryAddress`, `dateBookingDate`, and `strPickupTime` from the form. **Implemented**.

**BR-014 — Pickup and drop-off are required on both forms.** Empty values are highlighted and the booking POST is not sent. **Implemented** in `bookRide` and `bookRideCoupon`.

**BR-015 — Time and date are not validated in script.** They are prefilled. Member and voucher time become `hours + "h" + minutes` from the local clock, with no zero padding (a minute value under 10 has one digit). Date becomes the UTC calendar date `toISOString().slice(0, 10)`. **Implemented**. **Inferred**: the stored time is a display string such as `9h5`, not a checked clock time. The HTML date control also declares `min="2021-01-01"` and `max="2030-01-01"` and a default `value="2021-05-31"`, which the script replaces when the form opens.

**BR-016 — Cash received is zero.** Both client payloads set `dblCashReceived` to `0`. There is no cash field on the page. **Implemented**. **Inferred**: this app does not collect a fare.

**BR-017 — Trip description is empty.** Both payloads set `strTripDescription` to `''`. **Implemented**.

**BR-018 — The driver is unassigned.** Both inserts set `refDriverID` to `'-1'`. **Implemented**. **Inferred**: `-1` means no driver. No code comment says so.

**BR-019 — The editing user is `app`.** `strFirstUser` and `strLastUser` are the literal `app`. `dtFirstEdit` is `CURRENT_TIMESTAMP`. **Implemented**.

**BR-020 — Member booking does not prove the client id.** `one_BookRide` inserts `refClientID` from the request. It does not look up `tblClient` again. **Implemented**. If FR-003's screen switch throws before `clientid` is set, this action is not reached from a successful click. The global `clientid` starts as `'0'`.

## After the booking

**BR-021 — The bookings desk is emailed.** Subject "New Trip". To `bookings@1fortheroad.co.za`. From `info.1fortheroad@gmail.com`. Body starts with "The Trip information" and includes the form summary. Member emails are labelled "Member USER". Voucher emails are labelled "Coupon USER". **Implemented** in `emailNotify`. Commit `21037ab` is "change notification email back to 1fortheroad's email". Commit `c034812` is "change smtp details to gmail details for now". **Inferred** from "for now": Gmail was a temporary SMTP choice. No later commit replaces it.

**BR-022 — The page promises an SMS.** Success copy: "you will receive a confirmation SMS shortly". **Documented** in `index.html`. No SMS send exists in the repository. Whether a database trigger or another app sends it is **Unknown / Requires Confirmation**.

**BR-023 — CLOSE starts over.** `closeClear` reloads the page. **Implemented**.

## Access and security behaviour that acts as a rule

**BR-024 — A static token gates the API.** `api/api.php` runs the switch only when `t` equals the hardcoded token and `d` is set. The booking script sends that same token. Any other body returns `{'message':'no action'}`. **Implemented**. The token is not a per-user login.

**BR-025 — The API answers any origin.** `Access-Control-Allow-Origin: *` on `api/api.php`. **Implemented**.

**BR-026 — Login and booking responses include the SQL text.** Both paths put the query string on the JSON property `sql` (voucher also returns `sqlClient`, `sqlClientInsert`, and `sqlBooking`). **Implemented**.

These three are enforcement facts in the code. They are not described as accepted business policy anywhere else in the repo. Treating them as intended policy **Requires confirmation**.

## Payments

**BR-027 — SnapScan completion updates a payment row.** `api/callback.php` selects `payments` where `status = 1`. For each row it asks SnapScan about reference `MOMSTAXI` concatenated with `paymentsId`. If the returned status is `completed`, it sets `status = 2`. **Implemented** in that file. The booking page never calls it. The `MOMSTAXI` prefix does not appear in `index.html` or `scripts/1fortheroad.js`. **Inferred**: this rule belongs to a different product leftover, not to a 1 for the Road booking. **Requires confirmation** before anyone treats it as a 1 for the Road rule.

No price, zone, operating-hours, or cancellation rule was found.
