# Business rules — unity-soft-ui

Evidence date: 2026-09-30. Source revision: `main` `afdeab6` (2022-01-24).

No business-rules document existed in the repository or in `silicon_ark/unity-soft-ui/` before this file. Rules below are taken from code. Each rule is marked **Documented** (a comment or README states it) or **Inferred** (the code enforces it and nobody wrote it down as a rule). Nothing here is a new policy.

Server-side rules inside Cognito or the `unity` API are **Unknown** unless the client clearly depends on a specific payload string.

## Identity and access

### BR-001 — Five role names

**Documented** as placeholders in `src/config/Roles.js`: `SUPER_ADMIN`, `ADMIN`, `MANAGER`, `CUSTOMER`, `GUEST`.

**Inferred** from `src/config/RoutingConfig.js`: the first four are interchangeable for every route. Only `GUEST` is special.

### BR-002 — Missing roles means guest

**Documented** in a comment on `isLoggedIn` in `src/utils/index.js`: if `localStorage` has `roles`, the user is treated as authenticated.

**Inferred** from `getAllowedRoutes`: when `roles` is null, the filter uses `[GUEST]`.

The Cognito session is not consulted on load. **Inferred**.

### BR-003 — Who may open which page

**Inferred** from the `permission` arrays:

- Login, Register, and Forgot Password: `GUEST` only.
- Profile: `SUPER_ADMIN`, `ADMIN`, `MANAGER`, `CUSTOMER`. Not `GUEST`.
- Every other route: all five roles.

There is no per-button permission. A guest who can open `/users` or `/calendar` is following the route table, not a deviation from that table. Whether a guest should see those pages is **Requires Confirmation**.

### BR-004 — Navigation versus access

**Inferred**:

- `canNavigate: true` shows the route in the top nav (child routes only inside the Config dropdown, and only when the parent is rendered with `children`).
- `canNavigate: false` hides it from the nav. Register and Forgot Password are still reachable by URL while the visitor is a guest, and by links on the login form.
- `showInFooter: true` lists the route in the footer. Terms is footer-only.
- `useUsername: true` on Profile replaces the title with `userDetails.data.strName`, cut at 22 characters.

Logout is an extra nav item, not a route, and only when `isLoggedIn()` is true.

### BR-005 — What is stored as the session

**Inferred** from `Login.js` when `/user/details` returns `result === true`:

- `roles` = `data.strRole` stored as the raw string (not passed through `JSON.stringify`).
- `userDetails` = the payload object.
- `IDNumbers` = JSON array of the primary `strIdNumber` and each additional `strIDNumber`, skipping empty strings.

Logout deletes `roles` and `userDetails` only. **Inferred**: `IDNumbers` survives logout.

If `strRole` is not a JSON array string, the next `JSON.parse` in `getAllowedRoutes` throws. The legal stored shape is **Requires Confirmation**.

## Registration and sign-in

### BR-006 — Email and password on login

**Inferred** from `Login.js` `validateForm`:

- Email is required and must pass the Email pattern.
- Password is required.
- No length, character-class, or breach check exists in the client.

Cognito may reject passwords the client accepted. That policy is **Unknown**.

### BR-007 — Email pattern

**Inferred** from `validateFieldType` field type `Email`:

```text
^([A-Z|a-z|0-9](\.|_){0,1})+[A-Z|a-z|0-9]@([A-Z|a-z|0-9])+((\.){0,1}[A-Z|a-z|0-9]){2}\.[a-z]{2,3}$
```

The character class includes a literal `|`. Dots in the local part are optional single separators. The domain requires two extra labels before a 2–3 letter TLD. Many ordinary addresses fail this pattern. **Requires Confirmation** if the pattern was meant to be a normal email check.

### BR-008 — Person name

**Inferred**. Field type `Name`: letters (Unicode `\p{L}`), spaces, `.`, `’`, `'`, `` ` ``, and `-`. Used for name and surname on register and on the login details modal.

### BR-009 — Phone

**Inferred**. Field type `Phone`: optional leading `+`, then 10 to 13 digits. No other characters. Used for `cellphoneNumber` / `strMobileNumber`.

### BR-010 — Identity number types

**Inferred** from the select options and `validateFieldType`:

| `strIdType` | Label shown | Rule |
|---|---|---|
| `02` | RSA ID Number | Must match `/^\d{2}[0-1][0-9][0-3]\d\d{4}[0-1]\d{2}$/` and the last digit must equal `mod10CheckDigit` of the preceding digits |
| `03` | Foreign ID Number | Any non-blank value is valid. The function returns `true` |
| `04` | Business Registration | Any non-blank value is valid. The function returns `true` |

Default selected type is `02`. Blank id number is rejected before the type check.

`mod10CheckDigit` throws if it is given a non-string. Callers pass a string slice. **Inferred**.

### BR-011 — Password confirmation

**Inferred**. Register and the reset form require the second password to equal the first. Messages: "Passwords must match!" (register) and "Passwords must match!!" (reset). Reset also requires the two emails to match ("Emails must match!!").

### BR-012 — Cognito username is the email

**Inferred**. `Auth.signUp` and `Auth.signIn` use the email field as the username. The details query uses `response.username`, which the client then sends as `strCognitoUuid`. Whether the pool's username is a generated uuid is **Unknown**; the client treats `response.username` as that uuid.

### BR-013 — Unverified email cannot finish login

**Inferred**. If `email_verified` is not true, login does not call `/user/details`. A modal offers a redirect to `/register?verify=<email>`. Choosing No stays on the login page.

### BR-014 — Cognito user with no Unity row

**Inferred**. If the details call returns `payload === "User does not exist"`, login opens a details form and POSTs `/register`. Any other `result !== true` falls through without a popup and without navigation (the success branch and this string are the only handled cases). **Requires Confirmation** for other payload values.

### BR-015 — Register always asks for an email code

**Documented** comment in `Register.js`: `isMultifactor` should be true when Cognito MFA is enabled.

**Inferred** from the constant `true` and the calls that follow: the user always sees the code step, and the step calls `Auth.confirmSignUp`, which confirms the sign-up code. It does not show a TOTP prompt. The comment and the behaviour disagree. Whether the user pool has MFA is **Unknown**.

### BR-016 — Create versus link after confirmation

**Inferred** from `handleCodeFormSubmit`:

- Look up `/user/details` by Cognito uuid and by email.
- If the email lookup payload is `"User does not exist"`, POST `/register` with name, surname, email, id type, id number, mobile, and Cognito uuid.
- Else if either lookup has `result === false`, POST `/updateregistration` with Cognito uuid and email.
- If both lookups have `result === true`, neither write runs, and the success panel still shows.

The success sentence is "Your account registration has been submitted." It does not say the account is approved or active. No approval queue exists in this repo.

### BR-017 — Duplicate Cognito email

**Inferred**. `UsernameExistsException` is shown on the email field using Cognito's `error.message`. Other sign-up errors use the popup.

### BR-018 — Reset code delivery

**Inferred**. The confirm step tells the person to check their email for the code. The client does not choose the channel. Cognito does.

## Profile, users, calendar

### BR-019 — Profile is not saved

**Inferred**. The Update Profile button does not call the API. Role multi-select state is not written to `localStorage` or to `/user/details`. Company is disabled and the value is the literal "Silicon Overdrive".

### BR-020 — User table is read-only

**Inferred**. `/users` has no create or delete. EDIT navigates to `/app/user/<UserID>`, which is not a configured route, so it does not edit. The path segment `1` on `/unity/getusers/1` is fixed. Its meaning is **Unknown**.

### BR-021 — Calendar event shape

**Inferred** from `transformToDB` / `transformToReact`:

| UI field | API field |
|---|---|
| `id` | `CalendarIDitem` |
| `title` | `txtDescription` |
| `description` | `txtNotes` |
| `start` | `dtStartDate` |
| `end` | `dtEndDate` |
| `strFirstUser` | `strFirstUser` |
| `refSysUserID` | `refSysUserID` |

New events set `strFirstUser` from `getUserDetails().userName` and `refSysUserID` from `getUserDetails().userID`. Title comes from `window.prompt`. An empty title does not create an event. Delete requires `window.confirm`. Weekends are visible unless the checkbox is cleared; that flag is not sent to the API.

Load and mutate failures are logged. Mutate failures revert the on-screen change. There is no conflict rule (two people editing one event) in the client.

### BR-022 — Dashboard numbers are examples

**Inferred** from the literal in `Dashboard.js` and commit `afdeab6` ("add more example data"). They are not a business total. "Total Onions" (42) and "Total Spaghett" (92) are in that literal. Do not treat them as operational counts.

### BR-023 — Tasks are examples

**Inferred**. The two tasks are constants. Save Task does not persist. One description mentions SAWIS. That is copy, not a rule that this system integrates with SAWIS.

## Copy that looks like policy and is not confirmed

### BR-024 — Paymyfines.com agreement line

**Inferred** as implemented copy, not as a confirmed contract: the register form says clicking Submit agrees to Paymyfines.com's terms and privacy policy. Both modals are the sentences "A whole bunch of terms" and "A whole bunch of policies". The terms route is an empty jumbotron. **Requires Confirmation** before anyone treats Paymyfines as this product's legal entity.

### BR-025 — Contact address

**Inferred**. Footer enquiries use `helpme@please.com`. That is not evidence of a support process.

### BR-026 — Company name on the profile

**Inferred**. The disabled company field displays "Silicon Overdrive". The nav brand is "Unity Soft". These are not reconciled in code. **Requires Confirmation** which organisation the screen is supposed to name.

## Data rules that are presentation only

### BR-027 — Header labels

**Inferred**. `getNiceName` drops a leading `dt`, `str`, or `int`, then splits on `_` or on camel-case capitals and title-cases the pieces. Used when charts label series. It is not a database constraint.

### BR-028 — Chart numbers

**Inferred**. For chart types other than the counter, values that `isNaN` considers numeric are replaced with `Number(...)`. Non-numeric series are left as-is. Empty data renders "No Data". A `GraphType` outside the known list renders "Unknown Graph Type".

### BR-029 — Table search and export (component, no screen)

**Inferred** from `ExportableTable.js`, which no route mounts:

- Search is case-insensitive and matches if any non-object field contains the term.
- When `columnFormat` defines a formatter for that column, the search uses the formatted string with spaces removed (commit `06d76ef`).
- `searchFilterOverride` replaces the internal search box value when set (commit `a1284f6`).
- Export downloads a CSV of the filtered rows.

These rules apply only if a future screen renders the component. They are not active product behaviour at `afdeab6`.

## Permissions the code does not enforce

- No row-level check that a calendar event belongs to the caller, beyond sending `userID` on load.
- No check that the id on `/unity/getdetails` is the signed-in person. The id is `location.data`, which the profile nav does not set.
- No rate limit, captcha, or lockout in this client. Cognito may supply lockout. **Unknown**.
- No audit log in this client.
