# Business rules — unityFrontend

Evidence date: 2026-09-30. Source: `master` `9742198`.

Rules below are what the code does. None of them is a signed-off business policy. Each rule is marked **Implemented** or **Inferred**. Where the code disagrees with itself, both sides are stated.

Seeded passwords and the map API key are omitted.

## Identity

### BR-001 — Guest is an empty role

**Implemented.** Initial user state is `role: []`, display name Guest, email `guest@unity.com`.

Evidence: `src/app/auth/store/reducers/user.reducer.js`.

### BR-002 — Login matches email, then password

**Implemented.** `GET /api/auth` looks up the mock user by email. Missing user: "Check your username/email". Wrong password: "Check your password". Both can be returned together when the user is missing (password error is also set because the user is absent). Success returns the user without the password and a JWT.

Evidence: `src/@fake-db/db/auth-db.js` `mock.onGet('/api/auth')`.

### BR-003 — Login form length

**Implemented.** Email and password are required and must be at least 4 characters before the Login button enables. The login form does not use an email-format validator. Registration does (`isEmail`).

Evidence: `JWTLoginTab.js`, `JWTRegisterTab.js`.

### BR-004 — Session length

**Implemented** in the mock signer: `expiresIn: "2 days"`. The client treats the token as valid while `decoded.exp` is greater than now. It does not check the signature. An expired token clears the session and emits `access_token expired`.

Evidence: `auth-db.js` `jwtConfig`, `jwtService.js` `isAuthTokenValid`.

### BR-005 — After login, go to the user's redirect URL

**Implemented.** `setUserData` calls `history.push(user.redirectUrl)`.

| Role string | Redirect |
|---|---|
| `admin` | `/apps/unity/landing` |
| `team manager` | `/apps/ticketing/tickets` |
| `manager` | `/apps/ticketing/tickets` |
| `technician` | `/apps/unity/technicianHome` |
| `unassigned` (new registration) | `/apps/unity/landing` |

Evidence: `auth-db.js`, `user.actions.js`.

### BR-006 — New accounts are unassigned

**Implemented.** Register stores `role: "unassigned"`, `active: true`, and a generated uuid. Duplicate email: "The email is already in use". Empty display name: "Enter display name". Password is not strength-checked. Password confirmation is a client-side Formsy match only.

Evidence: `auth-db.js` `mock.onPost('/api/auth/register')`, `JWTRegisterTab.js`.

### BR-007 — Register is guest-only; login is not

**Implemented.** `/register` sets `auth: authRoles.onlyGuest` (empty array), which `hasPermission` allows only when the user role is missing or empty. `/login` sets no `auth`, so any role may open it.

Evidence: `RegisterConfig.js`, `LoginPageConfig.js`, `FuseUtils.hasPermission`.

## Access

### BR-008 — Routes with no auth array are open

**Implemented.** `generateRoutesFromConfigs(routeConfigs, null)` leaves `auth` null unless the config sets it. `hasPermission` returns true for null or undefined. User management, user groups, Unity homes, e-commerce, and most Fuse demos do not set `auth`.

Evidence: `routesConfig.js`, `FuseUtils.js`, `UnityAppConfig.js`, `UserManagementAppConfig.js`, `ECommerceAppConfig.js`.

### BR-009 — Tickets require one of four role strings

**Implemented.** Ticketing config `auth` is `['admin', 'teamManager', 'manager', 'technician']`.

The seeded team-manager user has role `team manager` (space, not camel case). **Inferred:** that user fails this check. The other three seed roles match the list. `unassigned` and guest do not.

Navigation does not apply this list. The `auth` properties on Tickets, Admin, and Technician nav items are commented out.

Evidence: `TicketingAppConfig.js`, `auth-db.js`, `navigationConfig.js`.

### BR-010 — Module capabilities are not an access rule

**Implemented** storage and UI. **Not implemented** as enforcement.

The Admin group seeds:

| Module | canView | canAdd | canEdit | canDelete |
|---|---|---|---|---|
| Ticketing | true | true | true | true |
| User-Management | true | true | true | true |
| Knowledge-base | true | true | false | false |

Other groups have no modules. Checkboxes post the flags. The mock returns the body and does not write it back (the write sits after `return`). No route or nav reads the flags.

Evidence: `auth-db.js` groups, `ModuleAccessTable.js`.

This is not evidence that Knowledge-base was meant to be read-only for admins in production. It is seed data only.

## Tickets

### BR-011 — A ticket name is required to save

**Implemented.** Save is disabled unless `form.name.length > 0` and the form differs from the loaded ticket.

Evidence: `Ticket.js` `canBeSubmitted`.

### BR-012 — Inactive tickets are not editable

**Implemented.** Name, description, and the selects set `disabled={!form.active}`. New tickets start `active: true`. Close sets `active` to false before posting. Close does not set status to Closed and does not set `resolved`.

Evidence: `Ticket.js`, `ticket.actions.js` `closeTicket`.

### BR-013 — Status vocabulary is two lists

**Implemented**, and they differ.

Form options: New, In Progress, Pending Client Response, Senior Assigned, Document Update Required, Scheduled, Pending Repairs.

Mock tickets and the metrics counter use Open, Closed, and In Progress. The metrics `switch` ignores every other status. The doughnut labels are Open, Closed, In Progress and does not read the API.

**Inferred:** "Closed" as a status and "active: false" are not the same flag. No comment in the code defines which one means the ticket is finished.

Evidence: `Ticket.js` status `MenuItem`s, `ticketing-db.js` seed and `dashboard/metrics`.

### BR-014 — Type, priority, department, company, and assignee are fixed lists

**Implemented** as selects, not as data from an API.

- Type: Operational, Service Request, Maintenance.
- Priority: all, Low, Medium, High. The value `all` is a menu item, not a filter.
- Department: blank labelled All, Technical, Infrastructure, Software, AWS, Human Resources.
- Company: GLASSTEP, FURNAFIX, PARLEYNET, ISOLOGIX, NEBULEAN, ACLIMA, KLUGGER, SOFTMICRO, ROCKYARD.
- Assignee: Alex Kedian, Kagiso Malepe, Jaco Brits.

Mock tickets also use company MONDICIL and CALCULA, which are not in the select. **Inferred:** those seed rows cannot be re-selected if the user changes company. Not executed for this document.

Evidence: `Ticket.js`.

### BR-015 — Ticket search is by name

**Implemented.** The list search keeps a ticket when `name` contains the typed text, case-insensitive. It does not search number, company, or assignee.

Evidence: `TicketsTable.js`.

### BR-016 — Age and resolved are display fields

**Implemented** on the list. `age` is a number on the mock ticket. `resolved` is the string `"True"` or `"False"`, not a boolean. The detail save path's `newTicket()` does not set `resolved` or `age` or `number`. **Inferred:** a ticket created in the UI has no number until something else sets it. The header still renders `Ticket #` plus `form.number`.

Evidence: `TicketsTable.js`, `ticket.actions.js` `newTicket`, `Ticket.js` card header.

### BR-017 — Dashboard counts are not wired

**Implemented** endpoint counts Open, Closed, and In Progress. **Implemented** chart ignores that endpoint and shows 12, 20, and 32. **Inferred:** the chart is not a business total. The month-metrics payload is campaign click rows, not tickets opened this month.

Evidence: `ticketing-db.js`, `DoughnutChartComponent.js`, `unity.actions.js`.

## Users

### BR-018 — Two user stores

**Implemented.** The screen loads users from `/api/auth/users`. Save posts to `/api/user-management/user/save`, which updates `userManagementDB` (ids `"111"` …, roles Admin / Technician / Team Manager / Manager). Those records are not the login users.

**Inferred:** saving a user on the Users screen does not change who can log in. Not executed for this document.

Evidence: `users.actions.js`, `user.actions.js`, `user-management-db.js`, `auth-db.js`.

### BR-019 — New user-management record defaults

**Implemented.** `newUser()` sets `active: true` and `role: 'unassigned'`.

Evidence: `src/app/main/apps/user-management/store/actions/user.actions.js`.

## What is not a rule

- Commented role arrays in `authRoles.js` and `navigationConfig.js` are not enforced.
- "Email Connector" on a mock ticket is a creator string, not an inbound-mail rule.
- Fuse e-commerce prices, mail, and academy content are template data. No Unity pricing or catalogue rule was found.
- No retention, SLA, audit, or multi-tenant rule was found in code or docs.
