# Business rules — DoctorX_Josh

Evidence date: 2026-10-16.

Source: `git@bitbucket.org:overdrive/doctorx_josh.git`, `master` at `01973b1` (2021-05-04).

No business-rules document exists in the source repository. Rules below are **Implemented** (enforced by this client) or **Inferred** (a reasonable reading of labels, comments, or payloads). They are not an approved policy. Server-side rules in Cognito, API Gateway, or MySQL are **Unknown** unless this client shows them.

## Access

### BR-001 — Local role flag is the route session

**Implemented.** `isLoggedIn()` in `src/utils/index.js` returns true when `localStorage` contains `roles`. The comment in that function says the app assumes this means the user is authenticated, and that the check is temporary.

A public route renders only when that item is missing (`src/routes/Auth.js`). `/app` redirects to `/` when it is missing (`src/routes/PrivateRoutes.js`).

This flag is not the Amplify session. Both are written at a successful verified login and both are cleared on logout.

### BR-002 — Missing permission means any signed-in user

**Implemented.** `getAllowedRoutes` keeps a route when `permission` is absent or is not a non-empty array. Home, Diagnosis, and My Profile have no `permission`. Users sets `permission` to `[Roles.SUPER_ADMIN]` (`src/config/PrivateRoutesConfig.js`).

The check is `intersection(permission, JSON.parse(localStorage.roles))`. `roles` must parse as JSON. Login stores `body.strRole` with `setItem` and does not stringify it (`src/components/Login.js`).

### BR-003 — Role catalogue

**Implemented** names in `src/config/Roles.js`:

| Key | String stored in the constant |
|---|---|
| `SUPER_ADMIN` | `SUPER_ADMIN` |
| `DOCTOR` | `DOCTOR` |

The file comment says these are placeholders to replace.

**Implemented** exception: self-registration create sends `strRole: "['GUEST']"` (`Register.js`). `GUEST` is not in `Roles.js`. **Inferred:** a user whose stored role does not parse as JSON, or does not contain `SUPER_ADMIN`, will not see Users. A parse failure throws inside `getAllowedRoutes` and the private shell does not render.

### BR-004 — Profile cannot change its own role

**Implemented.** The profile role dropdown is `disabled`. The update body is `DoctorID`, `strName`, `strSurname`, `strEmail`, `strCity` only (`Profile.js`).

### BR-005 — Users screen assigns roles

**Implemented.** The add/edit dialog requires at least one selected role. Options are the keys of `Roles`. The selected array is sent as `strRole` on create and update (`Users.js`).

**Inferred.** Nothing in this client stops a super admin from assigning `SUPER_ADMIN`.

## Registration and login

### BR-006 — Registration fields

**Implemented** in `validateRegistrationForm` (`Register.js`). All are client-side:

| Field | Rule |
|---|---|
| Email | Non-blank. Message: "Cannot be blank!" |
| Name | Non-blank |
| Surname | Non-blank |
| Password | Non-blank |
| Confirm password | Non-blank, and must equal password. Message: "Passwords must match!" |
| Conditions | Must be accepted. Message: "Must be accepted to register!" |

No email-format check and no password-length check exist in this file. Cognito may still reject the password. That policy is **Unknown**.

### BR-007 — Terms must be accepted

**Implemented.** The checkbox must be true before `Auth.signUp`. The terms and privacy strings are the modal bodies in `Register.js`. They are not loaded from a URL or a versioned document.

**Inferred.** Accepting them is a UI gate only. The create payload does not send `blnAcceptedConditions`.

### BR-008 — Email confirmation is mandatory in this build

**Implemented.** `isMultifactor` is `true`, so a code step always follows sign-up. The comment ties the flag to Cognito multifactor auth. The call is `Auth.confirmSignUp`, then `Auth.signIn`.

If `email_verified` is not `true` after that sign-in, the code throws the string "User mail remains unconfirmed".

### BR-009 — Doctor row after confirmation

**Implemented.**

- Lookup: `POST /josh/getuuid/` with `{ strEmail }`.
- If the parsed body is `undefined` or `[]`: `POST /josh/create/` with Cognito username as `DoctorID` and `refUserID`, the form name and surname, email, `strRole` `['GUEST']`, and `strUserCreatedName`.
- Else: `POST /josh/update` with `DoctorID` = Cognito username, `strEmail`, and `refUserID` = `confirmSignUp`'s `UserID`.

**Unknown / Requires Confirmation.** What `Auth.confirmSignUp` returns in Amplify 3.3.21, and whether `UserID` is set. The comparison `body === []` is reference equality against a new array literal, so a parsed empty array does not take the create branch.

### BR-010 — Login fields and verified email

**Implemented** (`Login.js`):

- Email and password must be non-blank.
- `NotAuthorizedException` → alert "Incorrect email or password".
- Other errors → alert `error.message`.
- `email_verified === true` is required before doctor lookup. Otherwise a modal offers the confirmation page.
- On success, last login is posted and the app waits 100 ms before `/app/home`.

`UsernameExistsException` on registration is shown on the email field (`error.message` from Cognito).

### BR-011 — Password reset

**Implemented** (`ForgotPassword.js`):

- The two email fields must be non-blank and equal ("Emails must match!!").
- Code and both password fields must be non-blank.
- The new passwords must be equal ("Passwords must match!!").
- Submit uses `Auth.forgotPassword` then `Auth.forgotPasswordSubmit`.

The confirmation form does not bind `errors` to the inputs. The blank and match checks still set state and skip the API call.

### BR-012 — Last login timestamp shape

**Implemented.** `dtLastLoggedIn` is `new Date().toISOString()` with `T` replaced by a space and the fractional seconds plus `Z` removed. Example shape: `2021-05-04 11:06:14`. Time zone conversion is not applied. The source is UTC because `toISOString` is UTC.

## Diagnosis

### BR-013 — Diagnosis form required fields

**Implemented** (`Diagnosis.js`):

| Field | Rule | Message |
|---|---|---|
| type | Non-blank | "Must Select an option!" |
| model | Non-blank | "Must Select an option!" |
| xray | Non-blank | "Must Select a file!" |

The only option values are `"Type"` and `"Model"`, which pass the non-blank rule.

**Not implemented in this tree:** an allow-list of exam types, model ids, file type, or file size. `MAX_ATTACHMENT_SIZE` is unused.

### BR-014 — Diagnosis payload

**Implemented.** `POST /josh/diagnose` with the form object `{ type, model, xray, diagnosis }`. `xray` is the file input's string value. The client expects `JSON.parse(body)[0].result` and writes it to state key `"Diagnosis"`.

**Inferred.** The visible textarea will stay empty because it reads `"diagnosis"`.

Failure alert text is "An API error occoured".

## Doctor directory and profile

### BR-015 — Profile required fields

**Implemented** (`Profile.js`): name, surname, email, and city must be non-blank. City message: "Cannot be blank!". Role is not validated and is not saved.

Load uses `POST /josh/get/` with `{ id: <doctorID from login> }`.

### BR-016 — Directory required fields

**Implemented** (`Users.js`): name, surname, email, and city must be non-blank. Roles length must be at least 1 ("Must select at least 1 role!").

### BR-017 — New doctor creates a Cognito user, then a row

**Implemented** order in `handleSendUserDetail` when `isNewUser` is true:

1. `Auth.adminCreateUser` with the current user's pool id, username = email, attributes email and `email_verified: true`.
2. `DoctorID` is deleted from the payload.
3. `POST /josh/create/`.

**Implemented** and ineffective as written: the activation checkbox does not change the `adminCreateUser` arguments. The code looks up `sendmail` while state stores `sendMail`, and it sets `MessageAction` on an object that is not passed. The spelled value in source is `SUPRESS`.

**Unknown / Requires Confirmation.** Whether the browser user is allowed to call `adminCreateUser`.

### BR-018 — Edit does not rewrite the creator name

**Implemented.** On update, `strUserCreatedName` is deleted before `POST /josh/update/`. On create it is the signed-in user's `name` + space + `surname` from `localStorage`.

### BR-019 — City field names differ on edit

**Implemented.** EDIT sets `city` from `row.original.strLocation`. The table reads `strCity`. Save sends `strCity`. Profile load and save both use `strCity`.

**Inferred.** Opening EDIT can show an empty city when the row has `strCity` and no `strLocation`, and the subsequent save can write that empty-looking value back. Empty is rejected by BR-016, so a missing `strLocation` blocks save until the admin types a city.

### BR-020 — Resend invitation

**Implemented.** The dialog button runs the same validation as save, then `Auth.resendSignUp(email)`. It does not post to the doctor API. It is rendered for new and existing rows.

## Navigation and empty states

### BR-021 — Logged-in users leave the public site

**Implemented.** Any public path, including `/login`, redirects to `/app/home` when `roles` is set. `Auth.js` has a TODO that this also blocks other public pages. Those pages are not in the route table.

### BR-022 — Unknown `/app` path

**Implemented.** After the allowed routes, `MapAllowedRoutes` renders `NotFound` ("404 not found", link to `/`).

### BR-023 — Home assumes a surname

**Implemented.** The greeting reads `getUserDetails().surname` with no null check. If `userDetails` is missing or is not JSON, `JSON.parse` throws.

## Data the client sends

**Inferred** column names, because they appear as JSON keys and not as a schema file:

`DoctorID`, `strName`, `strSurname`, `strEmail`, `strRole`, `strCity`, `strLocation` (read only, on EDIT), `refUserID`, `strUserCreatedName`, `dtLastLoggedIn`, `userPoolID`, `id` (profile get).

Diagnosis keys: `type`, `model`, `xray`, `diagnosis`.

No delete call, no deactivate flag, and no duplicate-email check exist in this client. Cognito's `UsernameExistsException` is the only duplicate rule visible here.

## What is not a rule of this repository

- MySQL constraints, IAM authorizer behaviour, and the diagnose response. Those belong to the API. `DoctorX_lambda_api_Josh` in Silicon Ark documents a matching path list. This file does not copy that API's rules into Doctor X UI rules.
- Retention, consent versioning, and clinical safety. The privacy modal is one sentence. No other policy file is in git.
