# PRD — ohlief-app-client (Oh-lief EDI)

Evidence date: 2026-10-08. Source: `git@bitbucket.org:overdrive/ohlief-app-client.git`, `master` `98891e4` (2020-07-05).

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/ohlief-app-client/` before this file. Requirements below are recovered from the React screens, the route table, and the Amplify calls. They are not a new product decision.

The meta description in `public/index.html` still says "A simple note taking app". The title and the landing copy do not. That meta line is leftover starter text, not the product purpose.

## Product purpose

A person opens a web app titled **Oh-lief EDI**. The manifest calls it **oh-lief EDI for xProcure**. A guest sees the heading **Oh-lief** and the line **FTP EDI Orders Invoices**. After a Cognito login, the same page shows tabs for orders, invoices, and products. Those tabs read and write a remote API. This repository is only the browser client.

**Documented** title: `public/index.html`. **Documented** manifest name: `public/manifest.json`. **Implemented** landing copy: `src/containers/Home.js`. **Unknown / Requires Confirmation:** what "xProcure" and "FTP EDI" mean operationally. No FTP code is in this tree.

## Users and roles

The client has two states: signed out and signed in (`isAuthenticated` in `src/App.js`).

There is no role, tenant, store, or supplier field on the user. Any Cognito user who can sign in sees every order, invoice, and product the API returns.

| Actor | How the client treats them | Provenance |
|---|---|---|
| Guest | Home lander. `/login` is open. Other `/ohlief/...` routes redirect to `/login?redirect=<path>` | **Implemented** |
| Signed-in user | Home tabs, order edit, invoice view, product create and edit, logout | **Implemented** |

Who is allowed to exist in the user pool is **Unknown**. The client does not administer users. The signup screen is present and does not finish; see FR-016.

## Major workflows

### 1. Open the app

Trigger: browser loads the CRA bundle (`src/index.js`).

Behaviour:

- `serviceWorker.unregister()` runs.
- `Amplify.configure` runs after `ReactDOM.render`, and before the session `useEffect` (that effect runs after the first commit).
- `App` calls `Auth.currentSession()`. Success sets `isAuthenticated`. The string `No current user` is ignored. Any other error is shown with `alert`.
- While that check is in flight, the navbar is not rendered (`!isAuthenticating`).
- `/` is public. A guest sees the lander. A signed-in user sees the three tabs.

Output: lander or the Orders tab. **Implemented.**

### 2. Sign in

Trigger: Login on `/login`.

Inputs: email, password. The Login button stays disabled until both are non-empty. The email field is `type="email"` (browser format check). No minimum password length is set in this form.

Behaviour when the form submits:

- `Auth.signIn(email, password)`.
- Success sets `isAuthenticated` and `history.push("/")`.
- Failure alerts `e.message` and leaves the user on the form.

`/login` redirects to `/` when a session already exists.

The login page does not read the `redirect` query that `AuthenticatedRoute` adds. After a forced login the user always lands on `/`. **Implemented.**

### 3. Work the order list

Trigger: signed-in home, Orders tab (default).

The client loads three collections up front:

- `GET /ohlief` (variable name `notes`)
- `GET /listinvoices`
- `GET /listProducts`

Each failure alerts on its own. The page then clears the loading flag.

A row is shown on Orders only when `orderNumber` is truthy. Columns: Order #, Barcode, Product, Unit Price, Order Qty, Invoice Qty, QTY Per Pack, Order Total, Invoice Total, and actions **EDIT** and **INVOICE**.

Search box and pagination are the react-bootstrap-table toolkit. Sort is enabled on Order #, Barcode, and Product only.

**EDIT** navigates to `/ohlief/orders/{transactionId}` with a full page link (`href`), not a router `Link`.

**INVOICE** asks: "Do you want to process ALL orders with the same Order number: {orderNumber}". Cancel does nothing. Confirm calls `GET /invoiceupdate/{orderNumber}` and then `window.location.reload()`. Failure alerts. **Implemented** on the client. What the API changes is **Unknown**.

### 4. Edit an order line

Trigger: EDIT on an order row. Route `/ohlief/orders/:id`.

The screen loads `GET /ohlief/{id}` and shows Order #, Bar Code, Product, Unit Price, Order Qty (all read-only), Invoice Qty (editable number), and Total.

Invoice Qty has `min={0}` and `max={orderQty}` on the input. Save does not check those bounds. Changing the quantity recomputes Total as `Math.round(invoiceQty * singleUnitPrice * quantityPerPack).toFixed(2)`.

Save sends `PUT /ohlief/{id}` with `{ content, attachment, invoiceQty }` and returns home. The Save button is disabled unless `content.length > 0`. `content` is the API field of that name, not the invoice quantity. If the API omits it or sends an empty string, Save stays disabled. **Implemented.**

Cancel returns home immediately. The delete call underneath that handler is not reached.

### 5. Review invoices

Trigger: Invoices tab.

Rows come from `GET /listinvoices`, kept only when `orderNumber` is truthy. The first column is labelled **Invoice #** and shows `orderNumber`. The total column is labelled **Invoice Total** and is `Math.round(invoiceQty * singleUnitPrice * quantityPerPack).toFixed(2)`, prefixed with `€ `.

**VIEW** opens `/ohlief/invoices/{transactionId}`.

The invoice screen title is `Invoice: I{orderNumber} - Row Details`. Fields are read-only, including Invoice Qty. The only button is **Cancel**, which returns home. There is no save control on this screen. **Implemented.**

### 6. Maintain products

Trigger: Products tab.

Rows come from `GET /listProducts`, kept only when `barcode` is truthy. The Barcode column displays `packageBarcode`, not `barcode`. **Create Product** opens `/ohlief/newproduct`. **VIEW** opens `/ohlief/products/{transactionId}` and the page heading is **Edit Product Details**.

Create and edit send the same fields: product code, barcode, package barcode, description, quantity per pack, size, unit, unit price, RSP. Save is disabled until barcode is non-empty. Create is `POST /createProduct`. Edit is `PUT /updateProduct/{id}` after `GET /getproduct/{id}`. Success returns home. Cancel returns home and does not call delete.

## Integrations

All calls use Amplify API name `ohlief` and the base URL in `src/config.js`. Auth is the Cognito session Amplify attaches. Failure behaviour on the screens that were read is `alert` of the thrown error. There is no retry.

| Call | Used by | Client intent |
|---|---|---|
| `GET /ohlief` | Home orders, and the unlinked processed list | List order lines |
| `GET /listinvoices` | Home invoices | List invoice lines |
| `GET /listProducts` | Home products | List products |
| `GET /invoiceupdate/{orderNumber}` | INVOICE button | Ask the API to process that order number |
| `GET /ohlief/{id}` | Order edit, invoice view | Load one line |
| `PUT /ohlief/{id}` | Order Save | Store `content`, `attachment`, `invoiceQty` |
| `GET /getproduct/{id}` | Product edit | Load one product |
| `PUT /updateProduct/{id}` | Product Save | Store the product fields |
| `POST /createProduct` | New product | Create a product |
| `POST /ohlief` | New note screen | Create a note body plus optional attachment |
| `DELETE /ohlief/{id}` | Present in handlers; the buttons that look like delete return home first | **Unknown** whether any reachable button still completes this call. See FR-014 |
| S3 `Storage.vault.put` | New note screen, and order save if a file were selected | Upload to bucket `ohlief-app-uploads` |

S3 and the note `POST` are reachable only by URL. The order and product screens do not show a file input. See features.

## Constraints

- Region on every AWS block in `src/config.js` is `eu-west-1`.
- Attachment cap, where a file can be chosen, is 5 000 000 bytes. The alert says that size divided by 1 000 000, so "5 MB".
- Money on the three home tables uses a euro sign and `addThousandSeperator` on unit price. Totals are rounded to an integer before two decimal places are appended. Order and invoice detail totals are the same rounding without the euro sign. Product RSP is unformatted.
- Tables paginate through `paginationFactory()` with no options object in this repo.
- No role checks, no captcha, no rate-limit handling, no offline mode (`serviceWorker.unregister()`).

## Functional requirements

### FR-001 — Guest lander

The user opens `/` with no Cognito session.

The system shows **Oh-lief** and **FTP EDI Orders Invoices**, a Login link, and the Oh-lief logo. It does not call the list APIs.

Evidence: `src/containers/Home.js` `renderLander`, `src/App.js`.

Implementation: Implemented.

Confidence: High.

### FR-002 — Restore a session

The user opens the app with a Cognito session Amplify can read.

The system sets the signed-in flag and shows the Orders tab. A missing user is not an alert. Any other session error is an alert.

Evidence: `src/App.js` `onLoad`.

Implementation: Implemented.

Confidence: High.

### FR-003 — Sign in

The user submits email and password on `/login`.

The system calls `Auth.signIn`. Success opens `/`. Failure shows `e.message`. Empty email or password keeps Login disabled.

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

Implementation: Implemented.

Confidence: High.

### FR-004 — Sign out

The user clicks Logout.

The system calls `Auth.signOut`, clears the signed-in flag, and opens `/login`.

Evidence: `src/App.js` `handleLogout`.

Implementation: Implemented.

Confidence: High.

### FR-005 — Home tabs

A signed-in user is on `/`.

The system shows tabs Orders (default), Invoices, and Products. It loads `GET /ohlief`, `GET /listinvoices`, and `GET /listProducts`. A failed call alerts and the other loads still run.

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

Implementation: Implemented.

Confidence: High.

### FR-006 — Order table

The Orders tab renders lines whose `orderNumber` is truthy.

Each line shows order number, barcode (`barCode`), product (`sellerProductDescription`), unit price, order quantity, invoice quantity, quantity per pack, order total, and invoice total. Order total is `orderQty * singleUnitPrice * quantityPerPack`, rounded. Invoice total uses `invoiceQty` in that same product. Unit price and both totals are prefixed with `€ `.

The user can search and can sort Order #, Barcode, and Product.

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

Implementation: Implemented. The rounding rule is what the expression does. Whether that matches a commercial rounding rule is **Unknown / Requires Confirmation**.

Confidence: High for the expression. Medium for business intent.

### FR-007 — Request an invoice for an order number

The user clicks INVOICE on a row.

The system asks for confirmation and names the order number, saying it will process all orders with that number. Confirm calls `GET /invoiceupdate/{orderNumber}` and reloads the page. Dismissing the confirm leaves the list unchanged.

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

Implementation: Implemented on the client. Server effect: Unknown.

Confidence: High for the client. Low for the outcome.

### FR-008 — Edit invoice quantity

The user opens `/ohlief/orders/{transactionId}`.

The system loads `GET /ohlief/{id}` and lets the user change invoice quantity. Total updates from quantity, unit price, and quantity per pack. Save sends `PUT /ohlief/{id}` and returns home. Save is enabled only when `content` is a non-empty string.

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

Implementation: Implemented.

Confidence: High.

### FR-009 — Invoice table and read-only row

The user opens the Invoices tab and VIEW on a row.

The table lists invoice lines from `GET /listinvoices`. VIEW opens `/ohlief/invoices/{transactionId}`, which loads `GET /ohlief/{id}` and shows the line with invoice quantity read-only. Cancel returns home. The heading prefixes the order number with `I`.

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

Implementation: Implemented.

Confidence: High.

### FR-010 — Product list

The user opens the Products tab.

The system lists products from `GET /listProducts` where `barcode` is truthy. Columns: Barcode (value `packageBarcode`), Product Code, Product Description, Qty / pack, Size, Unit, Unit Price (`€ ` plus thousand separators), RSP (raw), and VIEW.

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

Implementation: Implemented.

Confidence: High.

### FR-011 — Create a product

The user clicks Create Product and submits the form.

The system requires a non-empty barcode, then `POST /createProduct` with product code, barcode, package barcode, description, quantity per pack, size, unit, unit price, and RSP. Success opens `/`. Cancel opens `/` without a call.

Evidence: `src/containers/NewProduct.js`. The exported function is named `NewOrder`. The route imports the file as `NewProduct`.

Implementation: Implemented.

Confidence: High.

### FR-012 — Edit a product

The user opens `/ohlief/products/{transactionId}` and saves.

The system loads `GET /getproduct/{id}`, requires a non-empty barcode, and `PUT /updateProduct/{id}` with the same fields as create. Success opens `/`. Cancel opens `/` and does not call the delete function in that file.

Evidence: `src/containers/ProductDetail.js`. The exported function is named `InvoiceDetail`.

Implementation: Implemented.

Confidence: High.

### FR-013 — Hide data routes from guests

The user opens an `/ohlief/...` or `/signup` path with no session.

The system redirects to `/login?redirect={pathname}{search}`.

Evidence: `src/components/AuthenticatedRoute.js`, `src/Routes.js`.

Implementation: Implemented. The redirect query is not consumed after login (FR-003).

Confidence: High.

### FR-014 — Note screens still in the route table

A signed-in user opens `/ohlief/new` or `/ohlief/{one segment}`.

`/ohlief/new` shows a textarea and a file input. Create requires non-empty content, rejects files over 5 000 000 bytes, uploads via `s3Upload` when a file is chosen, and `POST /ohlief` with `{ content, attachment }`.

`/ohlief/:id` is registered before `/ohlief/old`, `/ohlief/orders/:id` is a longer path so it still matches `OrderDetail`. A single segment other than `new` or `newproduct` renders `Notes`. That screen's API load is commented out. The form shows fixed strings (order `24751`, store `Shoprite Liquor Sale`, product `Jameson 500ml`, unit price `R48.00`, quantity `6`, total `500.00`). The button labelled **Back** runs the delete handler: it navigates home, then asks "Are you sure you want to delete this note?", and on confirm calls `DELETE /ohlief/{id}`.

No navbar or home button links to `/ohlief/new`. **Inferred:** these screens are leftover from the note starter, not the EDI workflow. Not confirmed by a product owner.

Evidence: `src/Routes.js`, `src/containers/NewNote.js`, `src/containers/Notes.js`.

Implementation: Implemented.

Confidence: High for behaviour. Medium that they are obsolete.

### FR-015 — Unknown routes

The user opens a path the switch does not claim.

The system shows **Sorry, page not found!**

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

Implementation: Implemented.

Confidence: High.

### FR-016 — Signup does not complete in the UI

A user opens `/signup`.

The route is an `AuthenticatedRoute`, so a guest is sent to login first. The navbar has no Signup link (`b8b9d6e` subject: "Home update signup hidden"). The form can call `Auth.signUp` with the email as username. Passwords must be non-empty and equal. There is no password-policy check in the form. `renderConfirmationForm` exists and is never used: after sign-up the same form stays on screen, and `newUser` is ignored. Confirming a code is not possible from this UI.

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

Implementation: Implemented as incomplete. Whether signup should exist is **Unknown / Requires Confirmation**.

Confidence: High.

## Error and edge behaviour

| Situation | What the client does | Provenance |
|---|---|---|
| List call fails | `alert(e)` for that call; other lists still attempted | **Implemented** |
| Order save throws | `alert(e)`, stay on the form, clear the loading flag | **Implemented** |
| User dismisses the invoice confirm | No API call | **Implemented** |
| Order line has no `orderNumber` | Omitted from the Orders table | **Implemented** |
| Product has no `barcode` | Omitted, even if `packageBarcode` is set | **Implemented** |
| `content` missing on an order | Save stays disabled; if `content` is not a string, `content.length` throws on render | **Implemented** gate. The throw is **Inferred** from the expression, not executed here |
| Invoice quantity above order quantity | The input declares `max`. Save does not reject it | **Implemented** |
| Total factors missing | `Math.round` of a non-number yields `NaN`, and `toFixed` throws | **Inferred**. Not executed |
| File larger than 5 000 000 bytes on the note form | Alert, no upload | **Implemented** |
| Session error other than no user | Alert | **Implemented** |

## Planned

No roadmap, ticket, or TODO that states a future release was found. Comments in `Home.js` say "Will extrapolate into components later" and "TODO - rename notes to Orders". Those are developer notes, not a scheduled plan. **Unknown** whether anyone still intends that work.
