# Business rules — magalies-pharma-heisenberg-frontend

Evidence date: 2026-10-09. Source: `master` at `f153f4f`.

Rules below are what this Angular app enforces or displays. Server-side rules in `magalies-pharma-heisenberg-serverless` can be stricter. Where this file says **Inferred**, the code behaves that way and no comment or README states it as policy.

Order status names, batch status names, and delivery types are loaded from the API (`POST /form-data` and order extras). This repository does not list the allowed values. Those catalogues are **Unknown** here.

## Access

**BR-001 — A person signs in with email. The UI does not offer sign-up.**

**Implemented.** `amplify-authenticator` `[hideSignUp]="true"` and `[loginMechanisms]="['email']"`.

**BR-002 — Roles are Cognito groups, and any one matching group is enough.**

**Implemented.** Groups read from `accessToken.payload['cognito:groups']`. `isAuthorised` returns true on the first overlap. The five names offered in the user UI are `Customer`, `Fulfilment`, `Packaging`, `Prescription`, `SuperAdmin`.

**Inferred** that these strings are the same groups as the Heisenberg API authorizers. **Requires Confirmation** that no other group is in use in Cognito.

**BR-003 — SuperAdmin is the only role offered the user list, the log list, and the dashboard charts.**

**Implemented** in the side nav `access` arrays and in `*authGroup="['SuperAdmin']"` on the user-list card and the dashboard chart card.

**BR-004 — The users table is not shown to other roles even if they open the users URL.**

**Implemented** as a template guard on the card (`user-list.component.html`), from commits `e137bc9` and `6f2f520`. The route itself is not behind the role guard (the child hook is commented out). The card is hidden. Whether the resolver still calls `GET /user` before the template hides the card is **Implemented** as resolver-on-route behaviour. The API authorizer is outside this repo.

**BR-005 — Customer may open New Prescription. Customer is not on the prescription list, batch, medicine, individual, or company links.**

**Implemented** in `side-nav.component.ts`.

**BR-006 — Prescription may submit, list prescriptions, list batches, and maintain individuals, companies, and medicine. Prescription may not create a batch.**

**Implemented.** "New batch" access is `SuperAdmin`, `Fulfilment`, `Packaging`. "Batches" list also includes `Prescription`.

**BR-007 — A Fulfilment user does not match the "New medicine" or "New order medicine" links.**

**Implemented.** Those two `access` arrays contain `Fulfilmennt`. The other medicine links use `Fulfilment`. **Inferred** typo. **Requires Confirmation** whether Fulfilment staff are supposed to create medicine.

**BR-008 — Menu hiding is not the same as a route block.**

**Implemented.** See PRD FR-003. A signed-in user who knows the URL is not stopped by `RoleAuthorisationGuard`.

**BR-009 — Signing in is presented as agreement to the terms.**

**Documented** in the sign-in header: "By signing in you have agreed to our terms and conditions".

**Implemented** destination `/tandcs` shows `Watch this space - Terms & Conditions coming soon`. There is no checkbox and no stored acceptance flag in this app. **Requires Confirmation** whether that sentence is the legal text.

## Users

**BR-010 — Create user collects email, name, optional phone, one or more roles, and a company. It does not collect a password.**

**Implemented.** `user-create.component.ts`. Email is required and must pass `Validators.email`. Phone, if present, matches `^\+[0-9]{11,12}$`. Commit message `ec24304` states the password field was removed and reset was added.

**BR-011 — Email and phone on an existing user are not editable in the form. Save writes the stored email and phone back.**

**Implemented.** Controls are `disabled: true` and the inputs are `readonly`. `save()` passes `getAttribute('email')` and `getAttribute('phone_number')`. Commit `ac21d49`.

**BR-012 — A user can have more than one role.**

**Implemented.** Roles are a chip list. `editUser` sends `roles: this.roles`. Commit `e8dd19a`.

**BR-013 — Enabling a user and resetting a password are separate API calls, each without a body.**

**Implemented.** `GET /user/{Username}/enable` and `GET /user/{name}/reset-password`.

**Inferred** that GET is used for a state change. This file does not call that a defect. Whether the API expects GET is defined in the serverless repo, not here.

## Prescriptions and orders

**BR-014 — A prescription file must be a PDF.**

**Implemented.** `acceptFiles = ".pdf"` on `ClientFormComponent`. The upload control's other checks were not fully enumerated. The accept attribute is the rule visible on that field.

**BR-015 — The submitter chooses Individual or Company before the rest of the stepper.**

**Implemented.** `EntityType` enum `Individual` | `Company`. The older `ForWho` enum (`Me`, `Someone else`, `Company`) still exists in `src/enums/for-who.ts`. Cypress public submission still speaks of "for me" and "for someone else". The routed client form uses `EntityType`. **Requires Confirmation** which pair is the business distinction staff use today.

**BR-016 — Order statuses are chosen from the list the API returns, not from a list in the frontend.**

**Implemented.** `orderStatuses = data.order.extras.order_status` and `getFormData(['order_status'])`. Changing status sends `order_status_id`.

**BR-017 — An order can be split into child orders by selecting parent line ids and a matrix of child quantities.**

**Implemented.** `OrderSplitPayload` `{ parentOrderItemIDs, childOrdersMatrix }` posted to `POST /order/{id}/split`. The numeric meaning of each matrix cell is whatever the API expects. This UI does not document it further.

**BR-018 — Fulfilment is scanning batch barcodes onto order lines until the user confirms they have finished.**

**Implemented.** Scan endpoints in `batch.service.ts`. The leave dialog text is "Check if you have finished scanning all the items. Are you sure you want to leave this page?" It is shown even when the dirty flag is false (`FulfilmentGuard`).

**BR-019 — A waybill that will not share a shipment with another order opens the shipping form. A shared shipment opens the packing-status dialog.**

**Implemented.** `waybillOrder` asks "Will this be shipped with another order?". No → `openShippingFormDialog`. Yes → `openStatusPackingDialog`.

**BR-020 — A completed waybill is described as final.**

**Implemented** as confirmation copy in `completeWaybill`: it cannot be changed and no new parcels can be captured. The UI does not itself disable a second complete call after success beyond returning the boolean. Server enforcement is outside this repo.

**BR-021 — Marking shipped requires an explicit confirm.**

**Implemented.** "Please check if this order has been shipped." Then `GET /order/{id}/shipped`.

**BR-022 — The order PDF adds 15 percent VAT.**

**Implemented** in `prescription-review.component.ts`: `vatRate = 15`, comment "standard in South Africa" and "order.vat is not available". **Inferred** as a display rule for that PDF only. It is not read from the order. **Requires Confirmation** if the rate must change or if some orders are zero-rated.

**BR-023 — When an OTP for the order is verified, the PDF states who verified it and the practice number.**

**Implemented.** Footer only if `otpDetails.status === 'verified'` and `verified_at` is set. Name comes from Cognito attribute `name` via `POST /otp/by-order` then `GET /user/{id}`. Missing name prints `Unknown User`. Missing practice number prints `-`.

## Catalogue and stock

**BR-024 — A batch number is 3 to 10 uppercase letters followed by 3 to 10 digits.**

**Implemented.** Pattern `^([A-Z]{3,10}[0-9]{3,10})$` on batch create and on stocktake.

**BR-025 — A stocktake barcode is that batch number, a hyphen, and 3 to 10 digits.**

**Implemented.** Pattern `/^([A-Z]{3,10}[0-9]{3,10}-[0-9]{3,10})$/`.

**Inferred** link to the print-server QR `{batch_number}-{suffix}`. The print server pads a four-digit suffix. This pattern allows 3 to 10 digits after the hyphen, so it accepts values the print template's four-digit pad would not produce, and rejects nothing that is four digits. **Requires Confirmation** that scanners and labels agree.

**BR-026 — Batch create quantity and stocktake quantity must be from 1 to 500 inclusive.**

**Implemented.** `Validators.min(1)` and `Validators.max(500)` on both forms.

**BR-027 — Medicine, dosage, and packaging are separate records the UI edits through nested API paths.**

**Implemented** in `medicine.service.ts`: dosage units, dosage types, package types, medicine dosage types, and packages under a dosage type. Deleting a dosage package type was fixed in `620c6bc` ("Fixed not found error when deleting"). The current delete URL is the nested `package/{id}` path in that service.

**BR-028 — Order medicine is a second list, not the catalogue medicine screen.**

**Implemented.** Own routes and `OrderMedicineService` (`/ordermedicine`). It was added in `f153f4f`. What business event "order medicine" records, as opposed to a prescription line, is not written in a comment. **Unknown / Requires Confirmation.**

## Addresses, labels, and courier

**BR-029 — Sender details for a waybill are the Magalies block in the environment file.**

**Implemented.** Account `PRYPP430`, company "Magalies Pharmaceuticals", street "Port 298", city "Kosmos Skeerpoort", postal code `0261`, service type `ONX`, contact "MBP Compounding", site `TEST01`. The same block is in the dev file and the prod file, including longitude/latitude placeholders and what3words `test.test.test`.

**Inferred** that `TEST01` and `test.test.test` are not production courier data. **Requires Confirmation.**

**BR-030 — The Dawn Wing URL compiled into both environments is the UAT path.**

**Implemented.** `http://swatws.dawnwing.co.za/dwwebservices/v2/uat/api`. The browser does not call that host itself for capture; it posts to the Heisenberg `/waybill/*` routes and sends the configured credentials to `/waybill/auth`. Whether the API then calls UAT is **Unknown** from this repo.

**BR-031 — Label text includes a fixed dispenser name `J Lubbe`.**

**Implemented.** `environment.printing.dispensed_by` in both environment files. The print-server templates documented in Silicon Ark no longer contain a `dispensed_by` placeholder (removed in that repo at `a44f442`). This frontend still has the field. **Requires Confirmation** that labels still show a dispenser.

**BR-032 — City is part of an address.**

**Implemented** as a field added across address forms (commit `6f5d008`, "I added city field to all adddress forms"; `92db2c0` "Add city to address"). Building number is shown on delivery address (`e0edf06`).

## Dashboard and money display

**BR-033 — Dashboard charts and report downloads are a SuperAdmin activity. The welcome line is not.**

**Implemented.** See BR-003 and the heading placement in `dashboard.component.html`.

**BR-034 — Report date ranges require a start and an end.**

**Implemented.** `Validators.required` on `start` and `end` in the dashboard forms that were read.

## Session and dirty data

**BR-035 — Sign-out is global.**

**Implemented.** `Auth.signOut({ global: true })`, which Amplify documents as invalidating tokens on all devices. This file reports the call, not a product decision to revoke every device.

**BR-036 — A dirty form asks before navigation. Fulfilment asks even when not dirty.**

**Implemented.** `DirtinessGuard` versus `FulfilmentGuard`.

**BR-037 — Create, delete, batch request, waybill capture, parcel capture, waybill complete, and mark-shipped ask for confirmation before the HTTP call.**

**Implemented** in the service methods read (`user.service.createUser`, `batch.service.createBatch` and `requestBatch`, `shipping.service` capture/complete/shipped). A cancel returns without calling the API.

## Rules this app does not encode

- Price, discount, and credit rules. Prices are displayed from `medicine_dosage_package_type.price` and multiplied by quantity on the PDF. No minimum order or currency other than `R` was found on that PDF.
- Scheduling, repeats, or expiry of a prescription as a business calendar. Batch and stocktake forms have an expiry date control. What expiry means for dispensing is **Unknown** here.
- Who may change an order status to a particular code. The UI sends the id the user picked. Allowed transitions are **Unknown** in this repository.
