# Business rules — legitimate

Evidence date: 2026-10-10. Source: `git@bitbucket.org:overdrive/legitimate.git`, `main` at `0a9b1c8`.

Rules below are taken from code and SQL. Each rule is marked **Inferred** when the code behaves that way and no separate requirements document states it. Do not treat an inference as an approved regulatory rule.

Fee amounts, gazette notice length, and workflow-step text are stored in SQL Server and are not fully present in this repository. They are called out as **Unknown / Requires Confirmation**.

## Identity and access

### BR-001 — Staff pages require a user

**Inferred.** `Web.config` denies anonymous users. The login page is `~/Auth/Login`. The session slides for 2880 minutes (48 hours).

### BR-002 — Password reset uses a question

**Inferred.** Membership is configured with `requiresQuestionAndAnswer` true, `enablePasswordReset` true, `enablePasswordRetrieval` false, and `requiresUniqueEmail` true. `passwordFormat` is `Clear`.

### BR-003 — A securable grants access when read or edit is set

**Inferred.** `SecurityHelper.UserHasAccessToSecurable` returns true when the profile contains that securable id and either `CanRead` or `CanEdit` is true. It does not require both.

### BR-004 — System user id 1

**Inferred.** `Defaults.SystemUserID = 1`. A `SystemUsername` app setting also exists. Whether both refer to the same person is **Unknown / Requires Confirmation**.

### BR-005 — ASMX calls need the interface key

**Inferred.** `IsAlive` and the search methods return no data unless `IsValid(interfaceKey)` is true. A bad key on `GetApplications` returns an XML error, not an empty success document.

## Applications

### BR-006 — Named application types

**Inferred** from `ApplicationRequestType`. Ids:

| Id | Name |
|---|---|
| 1 | New operating licence |
| 2 | Add authorisations / routes |
| 3 | Replacements (COV) |
| 4 | Increase carrying capacity |
| 5 | Conversion LTPS to Legitimate |
| 6 | Normal transfer |
| 7 | Deceased transfer |
| 8 | Renewal |
| 9 | Late renewal (expired) |
| 10 | Duplicate licence |
| 11 | Temporary special licence |
| 12 | Data purification |
| 13 | Conversion OLAS to Legitimate |
| 14–18, 23, 26, 28 | Renewal combined with COV, transfer, and/or capacity |
| 19–22 | Duplicate or capacity combined with COV |
| 24 | Renewal, normal transfer, and COV |
| 25 | Deceased transfer, new OL |
| 27 | Temporary replacements |

SQL also mentions type 31, "Renewal (Pre 02-08-2024 cost adjustment)", and the e-hailing document script inserts type 30. Neither is in the C# enum. **Unknown / Requires Confirmation.**

### BR-007 — Transfer, COV, and renewal groupings

**Inferred.** `ApplicationRequestHelper.IsTransferType`, `IsCovtype`, and `IsRenewalType` list the combination ids. A comment says the matching script is `ApplicationRequest/Scripts/UI.cshtml`. If those two lists differ, the helper and the UI will disagree. They were not diffed line by line.

### BR-008 — Pre-check field rules

**Inferred** from `Applications/Scripts/Default-Events.cshtml`:

- Type other than new operating licence: OL/Permit number is required.
- ID number is required.
- Service type is required for a new operating licence, and for any application whose source is LTPS.
- Association is required when the service type is unscheduled and the mode is midibus or minibus.
- Number of buses is required when the mode is bus and the type is a new operating licence.
- The registration-number requirement for unscheduled midibus and minibus is commented out, so this pre-check does not require a registration number.

### BR-009 — Applicant rules

**Inferred** from `ApplicantDetails.cshtml`:

- A SARS income-tax number or a reason for not having one is required. The reason max length checked in script is 1000.
- If any selected service type is unscheduled, a verified association is required.
- If no verified association is selected, a region is required.
- On a transfer, the applicant's ID or business number must differ from the existing licence holder's.

### BR-010 — Receipt cost

**Inferred** from `App.Fn_GetReceiptCost`:

- Start from `App.ApplicationRequestTypes.Cost` for the application's type.
- Add the renewal type (id 8) cost when the call says it is a renewal, unless the type is already a renewal type, or the type is temporary special (11), temporary replacement (27), or 31.
- Add the LTPS-conversion type (id 5) cost when the call says it is a conversion, unless the type is 5, 11, 27, or 31.
- Multiply by vehicle count.
- If the type is a COV type, and the vehicle registration now ends in `ZN` while the previous registration does not, and the chassis number is unchanged, subtract the type-3 COV cost once.

The SQL comment attributes the ZN waiver to SUP0007250.

The rand values in `ApplicationRequestTypes` are **Unknown** from this repository.

### BR-011 — COV type test in SQL

**Inferred.** `App.Fn_IsCovType` is altered by scripts under `_database schema changes/` and `DbScripts/COV application fees/`. The C# list is `IsCovtype` in `ApplicationRequestHelper`. Whether the SQL function and the C# helper list the same ids was not proved by reading both side by side. **Requires Confirmation** if fees look wrong for a combination type.

## Modes and service types

### BR-012 — Modes

**Inferred.** Bus 1, Midibus 2, Minibus taxi 3, Metered taxi 4, Other 5, E-Hailing 6. The e-hailing insert sets `IsCancelled` 0, `OlasServiceTypeID` NULL, `LtpsID` 6, and `ExceptionHandlerUserID` 1036. That user id is a script constant, not a lookup in C#.

### BR-013 — Service types allowed for a mode

**Inferred** from `App.Fn_AllowedServiceTypesForMode` in `DbScripts/06`:

| Mode | Allowed service type ids |
|---|---|
| Bus (1) | 1 scheduled, 3 charter, 4 tourist, 5 staff, 6 scholar, 7 courtesy |
| Midibus (2) | 2 unscheduled, 3 charter, 4 tourist, 5 staff, 6 scholar |
| Minibus (3) | 2 unscheduled, 3 charter, 4 tourist, 5 staff, 6 scholar |
| Metered taxi (4) | 2 unscheduled, 3 charter, 4 tourist, 5 staff |
| E-Hailing (6) | 9 e-hailing only |
| Anything else, including Other (5) | 8 other |

`ServiceType.Learners` in C# is id 6. The SQL comment on id 6 says Scholar. Same id, two words.

### BR-014 — Client script does not match the SQL function

**Requires Confirmation.** `ModeDetails.cshtml` hard-codes:

- Midibus: unscheduled, scholar, charter. SQL also allows tourist and staff.
- Minibus: unscheduled, charter, tourist, staff, scholar. SQL matches those ids. The script lists charter twice.
- Bus, metered taxi, and other match the SQL function.
- There is no JavaScript list for mode 6.

The service-type checkboxes are also bound to lookup `NewOLServiceTypes`. If that lookup is the database function, the hard-coded arrays may still filter differently. The two mechanisms can disagree.

### BR-015 — Uber question on metered taxi

**Inferred.** When mode id is 4, the form shows "Is Uber application?" and `MDValidate` requires an answer. Commit `9e15893` says the client advised that e-hailing is not a charter or a metered-taxi service type. The Uber question is still in the view. Whether Uber-on-metered-taxi is still allowed is **Requires Confirmation**.

### BR-016 — E-hailing documents

**Inferred** from `DbScripts/07`. These document types are inserted if missing:

| Id | Description |
|---|---|
| 6000 | Signed agreement between applicant and platform provider(s) |
| 6001 | Signed statement that the operator will comply with labour laws and sectoral determinations (section 62(c) NLTA) |
| 6002 | Criminal record report of the driver from SAPS |
| 6003 | Signed declaration by the driver that there are no pending criminal investigations |
| 6004 | Report of road traffic offences for the driver from NTCMS or other relevant systems |
| 6005 | Tax Compliance Status Pin from SARS in the name of the applicant |

Each gets a yes/no question (`CaptureFieldID` 25). Type 6006 (vehicle licence disc) is commented out.

The script then links 6000–6005 to application types 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, and 30, workflow id 1, with `IsMandatory` 0. It does not filter on mode. So the link is not "e-hailing applications only" inside this script. **Requires Confirmation** that this mapping was intended.

## Adjudication

### BR-017 — Two panel members

**Inferred.** For both a hearing and a meeting, the panel page waits until two members have joined, then a chairman can be chosen and the hearing or meeting can start. Remove panel member is shown only in status 4 (awaiting quorum). Replace panel member is shown in status 2 when there is a member added after start and the row is not the chairman.

Commit `b1c576e` states the previous hearing rule was three panel members.

### BR-018 — Agenda edits stop after finalisation

**Inferred.** Date, time, and location inputs are enabled only in status 1. Add application is status 1 only. The reference prompt rejects a value whose length is not 10.

### BR-019 — Remove application

**Inferred.** The Remove Application link is visible when adjudication status is 1 or 4. It posts `RemoveApplicationFromAdjudication` after a confirm dialog that requires comments. After finalisation (status 3) or once decisions are in progress (status 2), the link is hidden. Commit `7c6f17e` describes this as disabling the button when an agenda is finalised. The view hides it for statuses other than 1 and 4, which is wider than "finalised" alone: status 2 is also hidden.

### BR-020 — Letters follow status

**Inferred.** Pre-adjudication objection letters and pre-adjudication letters are enabled in status 4. Post-adjudication letters are enabled in status 3.

## Licences, routes, and parties

### BR-021 — Special licence duration

**Inferred.** For application type 11, `LicenceIssueRequestController` throws if expiry minus issue is more than 14 days, or if expiry is before issue. The comment says the application save must use the same rule.

### BR-022 — Licence status names

**Inferred.** Awaiting issuing, issued, expiring within 60 days, expired, temporarily suspended, archived. The 60-day boundary is the status name. The job that moves a licence into that status was not found in the C# that was searched. **Unknown** where the 60-day transition is calculated.

### BR-023 — Route and party lifecycle

**Inferred.**

- Routes: Active, InActive, NotVerified, Cancelled.
- Verified associations and members: Active or Inactive.
- New association, member, route, and vehicle requests are either New (1) or Verification required (2).

### BR-024 — Identity document kinds

**Inferred.** RSA identity document, passport, founding statement, temporary identity document, foreign identity document, certificate of incorporation.

### BR-025 — Provinces

**Inferred.** The `Province` enum lists nine names. KwaZulu-Natal is 1. "NothernCape" is spelled that way in code. Whether captures are limited to KwaZulu-Natal is **Unknown**.

## Gazette

### BR-026 — Gazette statuses

**Inferred.** Awaiting finalisation, awaiting notice-period expiry, finalised, awaiting print date. A securable allows bypass of gazette expiry. The number of days in the notice period is **Unknown / Requires Confirmation**.

## Verification queue

### BR-027 — One trigger type

**Inferred.** `SystemTriggerTypeEnum` has a single value, `SendNewVerificationTaskInQueue`. Statuses are Pending, InProgress, Completed, Failed, Cancelled. The handler marks the trigger completed after the SignalR invoke attempt, including when the invoke throws. The exception is written to the trace. It does not set status Failed in the catch that was read.

## Conflicts to resolve

| Topic | Code | Other evidence |
|---|---|---|
| E-hailing vs Uber | Mode 6 and service type 9 added because e-hailing is not metered taxi | Mode 4 still requires "Is Uber application?" |
| Allowed service types | SQL function | Narrower midibus list in `ModeDetails.cshtml`; no mode-6 list in that script |
| Application type ids | C# enum ends at 28 | SQL uses 30 and 31 |
| Securable 1023 | `OverwriteOriginatingLicenceNumbers` and `UpdateVehiclesLinkedToOperatingLicencesIncorrectly` | Same integer |
| Securable 2012 | `LicenceIssueRequest_PrintLicence_Actual` and `RoutesModule_SendToPurification` | Same integer |
| Database name | Web app config `LegitimateKZN_QA` | Web service config `LegitimateKZN`; scripts `LegitimateKZN_QA_SO` |
| Remove Application commit text | Commit says disabled when finalised | View also hides it during status 2 |

These are recorded so they are not smoothed over. They need a person who owns the KZN rules.
