# Business rules — ohlief-app-client

Evidence date: 2026-10-08. Source: `master` `98891e4`.

Rules below are what the client code does. None of them is a signed-off business policy. Each rule is marked **Implemented** or **Inferred**. Where two screens disagree, both sides are stated. Server-side rules are **Unknown** unless the client itself enforces them.

## Identity

### BR-001 — Two access states, no roles

**Implemented.** The client stores a boolean from `Auth.currentSession()`. It does not read groups, custom attributes, or a role claim.

Evidence: `src/App.js`.

### BR-002 — Guests see the lander only

**Implemented.** `/` does not require a session. List calls are skipped when `isAuthenticated` is false. `/login` is refused to a signed-in user (redirect to `/`). Every other named route uses `AuthenticatedRoute`.

Evidence: `src/Routes.js`, `src/containers/Home.js`.

### BR-003 — Login fields

**Implemented.** Email and password must both be non-empty before Login enables. The control is `type="email"`. The client does not encode a Cognito password policy. A rejected sign-in surfaces `e.message`.

Evidence: `src/containers/Login.js`.

### BR-004 — Signup is not a guest path

**Implemented.** `/signup` uses `AuthenticatedRoute`. Passwords must be non-empty and equal to the confirmation field. The confirmation-code form is never mounted, so a code cannot be submitted from this screen.

Evidence: `src/Routes.js`, `src/containers/Signup.js`.

### BR-005 — Login does not resume the requested path

**Implemented.** `AuthenticatedRoute` appends `redirect` to `/login`. `Login.js` always `history.push("/")` after success.

Evidence: `src/components/AuthenticatedRoute.js`, `src/containers/Login.js`.

## Orders and invoices

### BR-006 — A list line exists only with an order number

**Implemented.** Orders and invoices are pushed into the tables only when `orderNumber` is truthy. Other records from `GET /ohlief` or `GET /listinvoices` are dropped.

Evidence: `src/containers/Home.js` `renderNotesList`.

### BR-007 — One order number can cover many lines, and INVOICE names the number

**Implemented.** The confirm text says the action processes all orders with that order number. The call is `GET /invoiceupdate/{orderNumber}`, using `row.orderNo`, which is `orderNumber`, not `transactionId`. Every line with that number shows its own INVOICE button.

**Unknown / Requires Confirmation:** whether the API updates every line, one line, or something else.

Evidence: `src/containers/Home.js` `genInvoice`, `orderActionButtonFormatter`.

### BR-008 — Order total and invoice total

**Implemented** on the Orders tab:

- Order total = `Math.round(orderQty * singleUnitPrice * quantityPerPack).toFixed(2)`
- Invoice total = `Math.round(invoiceQty * singleUnitPrice * quantityPerPack).toFixed(2)`

Both are prefixed with `€ `.

`Math.round` runs before `toFixed`, so the displayed total is a whole number with `.00`.

**Inferred:** a missing factor produces `NaN` and then throws on `toFixed`. Not executed for this document.

Evidence: `src/containers/Home.js`.

### BR-009 — Invoice-tab total uses invoice quantity

**Implemented.** The column labelled Invoice Total is `Math.round(invoiceQty * singleUnitPrice * quantityPerPack).toFixed(2)` with a `€ ` prefix. Order quantity is shown and is not the multiplier on this tab.

Evidence: `src/containers/Home.js` invoice branch.

### BR-010 — Invoice quantity on an order may be edited

**Implemented.** On `/ohlief/orders/:id` the invoice quantity input is the only editable business field. `min` is 0 and `max` is the loaded order quantity. Those attributes are not rechecked in `handleSubmit`. The total uses the same round-then-two-decimals expression as BR-008, without a currency prefix.

Evidence: `src/containers/OrderDetail.js`.

### BR-011 — Saving an order requires note content

**Implemented.** `validateForm` returns `content.length > 0`. The body saved is `{ content, attachment, invoiceQty }`. Invoice quantity alone does not enable Save.

**Unknown / Requires Confirmation:** whether EDI lines include `content`. If they do not, the user cannot save an invoice quantity from this screen.

Evidence: `src/containers/OrderDetail.js`.

### BR-012 — Invoice rows are not editable

**Implemented.** On `/ohlief/invoices/:id` every field, including invoice quantity, is `readOnly`. There is no submit button. The `onChange` on that read-only field would have set total to `invoiceQty * singleUnitPrice` without `quantityPerPack`. It does not run while the control is read-only. The total shown after load still uses quantity per pack (BR-008).

Evidence: `src/containers/InvoiceDetail.js`.

### BR-013 — Invoice heading

**Implemented.** The heading is the literal `Invoice: I` plus `orderNumber` plus ` - Row Details`.

Evidence: `src/containers/InvoiceDetail.js`.

### BR-014 — Cancel does not delete

**Implemented.** Order Cancel, invoice Cancel, and product Cancel call handlers that `history.push("/")` and return. `API.del` in those files is not reached.

Evidence: `handleDelete` in `OrderDetail.js`, `InvoiceDetail.js`, `ProductDetail.js`.

### BR-015 — Back on the leftover note screen does delete

**Implemented.** On `/ohlief/:id` (`Notes.js`) the button labelled Back navigates home, then confirms "Are you sure you want to delete this note?". Confirm calls `DELETE /ohlief/{id}`.

This disagrees with BR-014. **Unknown / Requires Confirmation** which behaviour is intended. The home EDIT action does not open this screen; it opens `/ohlief/orders/:id`.

Evidence: `src/containers/Notes.js` `handleDelete`.

## Products

### BR-016 — A product row requires barcode

**Implemented.** The list includes a product only when `product.barcode` is truthy. Create and edit enable Save only when the barcode field's length is greater than 0. No other field is required by the client.

Evidence: `src/containers/Home.js`, `NewProduct.js`, `ProductDetail.js`.

### BR-017 — The Barcode column is the package barcode

**Implemented.** The list sets `barcode` on the row to `product.packageBarcode`. The gate in BR-016 uses the API field `barcode`. A product can be included because `barcode` is set and still display a different `packageBarcode`, including a blank one.

Evidence: `src/containers/Home.js` product mapping.

### BR-018 — Product money

**Implemented.** Unit price on the product table is `€ ` plus thousand separators. RSP is the raw `rsp` value, with no symbol and no separators. The edit form does not format either field.

Evidence: `src/containers/Home.js`, `src/containers/ProductDetail.js`.

### BR-019 — Product identity fields the client sends

**Implemented.** Create and update send `productCode`, `barcode`, `packageBarcode`, `productDescription`, `unitPrice`, `quantityPerPack`, `sizeOfProduct`, `unit`, `rsp`. The unit placeholder text is `Unit (ml,g)`. The client does not restrict `unit` to those two values.

Evidence: `src/containers/NewProduct.js`, `src/containers/ProductDetail.js`.

## Display and validation

### BR-020 — Thousand separators

**Implemented.** `addThousandSeperator` inserts commas on the integer groups of `toString()`. It is applied to unit prices on the three tables, not to totals (totals are rounded first).

Evidence: `src/libs/commonLib.js`, `src/containers/Home.js`.

### BR-021 — Currency symbol is not one rule

**Implemented** symbols in the tree:

- Home tables: `€ `.
- Hardcoded note/order leftovers: `R48.00`.
- Order and invoice detail totals: no symbol.

**Unknown / Requires Confirmation** which currency Oh-lief sells in. A March 2020 commit subject refers to rand. The current tables use the euro sign.

### BR-022 — Attachment size

**Implemented** where a file input exists (`NewNote.js`, and the unused `NewOrder.js`). Files larger than `MAX_ATTACHMENT_SIZE` (5 000 000) are rejected before upload. The alert divides by 1 000 000 and says MB.

Evidence: `src/config.js`, `src/containers/NewNote.js`.

### BR-023 — S3 object name

**Implemented.** `s3Upload` stores `${Date.now()}-${file.name}` with `Storage.vault.put` and the file's content type. The returned key is what the note POST sends as `attachment`.

Evidence: `src/libs/awsLib.js`.

### BR-024 — No client-side tenancy

**Inferred.** Nothing in the client filters orders by store, supplier, or user id. `storeSiteDescription` is loaded on the order and invoice screens (the invoice screen shows it; the order screen loads it and displays barcode in that row instead). If the API returns every customer's lines to every user, the UI will show them. The API's filter is **Unknown**.

Evidence: `src/containers/Home.js`, `OrderDetail.js`, `InvoiceDetail.js`.

## Permissions

There is no permission matrix in this repository. BR-001 and BR-002 are the whole access model on the client.
