# Business rules — neoafrica_qa

Evidence date: 2026-10-09.

Source: `git@bitbucket.org:overdrive/neoafrica_qa.git`, `main` at `3f8d683` (2026-06-19).

Rules are included only where a script, config, or in-repo document states them. **Inferred** means the behaviour is visible in code but no sentence calls it a business rule. Fees in the OLAS seed are recorded as seeded data. They are not treated as the current tariff unless a later document repeats them.

Credential values that appear in `Environment_Readiness_Checklist.md` and `checklist.csv` are omitted.

## Divisions and source systems

| Rule | Statement | Provenance |
|---|---|---|
| BR-001 | PRE is `DivisionTypeID = 1`. NPTR is `DivisionTypeID = 2` | **Documented** in the header of `121_Scholar_Contracted_Gazetting_Rule.sql` |
| BR-002 | Application types carry flags `IsAllowedForOLAS`, `IsAllowedForLTPS`, and `IsAllowedForLG` | **Implemented** as columns on `Md.ApplicationRequestTypes` in the OLAS seed |
| BR-003 | Type 63 (metered taxi to e-hailing) is PRE only, for all provinces except National (`ProvinceID` 10 in the script comment) | **Documented** in `Md.ApplicationRequestTypes_63_Conversion_EHailing.sql` |
| BR-004 | The national database name in SQL scripts is `LegitimateNational`. The checklist calls it `LegitimateNATIONAL` and also requires `LegitimateKZN_LTPS` | **Documented** in both places. Whether the two national spellings are one database **Requires confirmation** |

## Application types and seeded fees

**Implemented** seed in `DBUp/Scripts/Migration/Step 1 - OLAS Data Seed.sql`. Amounts are the `Cost` column at seed time. **Unknown / Requires Confirmation** whether operations still charge these amounts.

| ID | Title in the seed | Seeded cost |
|---|---|---|
| 1 | New Operating Licence | 300 |
| 2 | Add Existing Authorisations/Routes | 300 |
| 3 | Replacements (COV) | 300 |
| 4 | Increase carrying capacity | 300 |
| 5 | Conversion (LTPS to legiti-mate) | 300 |
| 6 | Normal Transfer | 300 |
| 7 | Deceased Transfer | 300 |
| 8 | Renewal | 300 |
| 9 | New Operating Licence (pre-populate with expired licence data) | 300 |
| 10 | Duplicate Licence | 300 |
| 11 | Temporary (Special) licence | 50 |
| 12 | Data Purification | 0 |
| 13 | Migration/Conversion (OLAS to Legiti-mate) | 300 |
| 14 | Renewal and COV | 600 |
| 15 | Renewal and COV and Increase in Carrying Capacity | 900 |
| 16 | Renewal and Deceased Transfer | 600 |
| 17 | Renewal and deceased transfer and increase in carrying capacity | 900 |
| 18 | Renewal and deceased transfer and increase in carrying capacity and COV | 1200 |
| 19 | Duplicate and COV | 600 |
| 20 | Duplicate and Increase in Carrying Capacity | 600 |
| 21 | Duplicate and COV and Increase in carrying capacity | 900 |
| 22 | Increase in Carrying Capacity and COV | 600 |
| 23 | Renewal and Normal Transfer | 600 |
| 24 | Renewal and Normal Transfer and COV | 900 |
| 25 | New Operating Licence (pre-populate with expired deceased transfer licence data) | 600 |
| 26 | Renewal and deceased transfer and COV | 900 |
| 27 | Temporary Replacement | 50 |
| 28 | Renewal and Increase in Carrying Capacity | 600 |
| 29 | Personal Details Update | 300 |
| 30 | Change Radius Permit Route | 300 |
| 31 | New accreditation | 1000 |
| 32 | Renewal of accreditation | 1000 |
| 33 | Add Operating Licence to accreditation | 300 |
| 34 | Upliftment Extension | 0 |
| 35 | New TAT Request | 300 |
| 36 | Add New Authorisations/Routes | 300 |
| 37 | Amend Authorisations/Routes | 300 |
| 38 | New accreditation without Operating Licence | 1000 |

Later types:

| ID | Title | Fee | Provenance |
|---|---|---|---|
| 60 | `E-hailing App Provider Registration` after prefix removal. Earlier title `NPTR: E-hailing App Provider Registration` | R5 000 in `docs/EhailingProviders.md` and QA case TC-007 | **Documented**. The SQL cost column was not re-read in `0923` |
| 61 | `Renewal of E-hailing App Provider Registration` | QA case TC-042 expects R5 000 | **Documented** |
| 62 | `Amend Conditions – E-hailing App Provider Registration` | Same module doc; a separate fee line was not extracted | **Documented** as a type. Fee **Requires confirmation** if it differs from R5 000 |
| 63 | `Conversion of Metered Taxi to E-hailing` | Seeded `0.00`. Comment: cost is governed by tariff configuration | **Documented** in the type 63 script |

IDs 39–59 were not found as `ApplicationRequestTypes` rows in this pass.

### Linked and source-licence rules (sample, not a full catalogue)

These are **Documented** as comments inside `App.Pr_ApplicationPrecheckCreate_updated.sql`:

- If an LG licence is expired, the application type must be "New Operating Licence (pre-populate with expired licence data)" or the expired deceased-transfer variant (comment C6).
- A message tells the user to start "New Operating Licence (From Source)" and to add the licence to the exception list first, when the licence is expired or not active.
- Type 9 ("New Operating Licence (From Source)" in that comment; seed title is the longer pre-populate name) can auto-add the licence to a bypass list.

**Requires confirmation:** the comment's short name "New Operating Licence (From Source)" and the seed title for id 9 are the same row. The procedure treats id 9 as that case.

## Gazetting

| Rule | Statement | Provenance |
|---|---|---|
| BR-010 | PRE new operating licence (type 1), all provinces: gazette when forced, or bus-and-contract, or service type description is `SCHOLAR SERVICE`, and gazetting is not bypassed. The script replaces the previous rule on those rows | **Documented** requirement in `121_Scholar_Contracted_Gazetting_Rule.sql` |
| BR-011 | Scholar contracted = Yes sets `ApplicationDetails.IsGazettingBypassed = 1` (in `Pr_ApplicationCreate`, according to the script comment) and therefore does not gazette. Non-contracted scholar does gazette | **Documented** in that header. The create-procedure assignment was not re-opened |
| BR-012 | NPTR gazetting rules are not changed by the scholar script | **Documented** in that header |
| BR-013 | Mpumalanga (`ProvinceID` 8) previously excluded scholar service. The new rule removes that exclusion so PRE behaviour is uniform | **Documented** in that header |
| BR-014 | NPTR gazettes that contain e-hailing types 60, 61, or 62 expire at print date plus 14 days (`GazettePeriodDaysEhailing`). PRE uses 21 days | **Documented** in `docs/EhailingProviders.md` and the settings script |
| BR-015 | Gazette Console "Bypass expiry date" for e-hailing is shown only when `AllowBypassGazetteExpiryForEhailing` is TRUE. The script says this is for dev/test and should be FALSE or omitted in production. The insert writes TRUE if the row is missing | **Documented**. The inserted default conflicts with the production instruction until someone updates the row. **Requires confirmation** of the value in each environment |
| BR-016 | Gazette print with region 0 returns all rows after `74_BugFix_Pr_PrintGazetteReport_ReturnAllWhenRegionZero` | **Implemented** (script name and path). The procedure body was not quoted here |

## E-hailing providers

| Rule | Statement | Provenance |
|---|---|---|
| BR-020 | Setting `ehailing.providers.enabled` is TRUE or FALSE. FALSE hides types 60–62 on start application and hides the registry nav. Endpoints remain RBAC-guarded | **Documented** in `docs/EhailingProviders.md` |
| BR-021 | Business registration number is unique on `Verified.PlatformProviders` | **Implemented** unique index in `0923_NPTR_EhailingProviderRegistration.sql` |
| BR-022 | Certificate number is unique. Expiry is described as issued date plus 7 years. Number format `NPTR-EHP-YYYY-####` | **Documented**. Script `1106_App_Pr_IssueProviderCertificate_Alter_Duration.sql` may change duration. **Requires confirmation** |
| BR-023 | Reissue creates a new certificate with `ReissuedFromID` and keeps history. A reason is passed to `ReissueProviderCertificate` | **Documented** |
| BR-024 | Registry status is Active, Expired, or Cancelled, from the latest certificate per provider on `Verified.Vw_ProviderRegistry` | **Documented** |
| BR-025 | Registry search is allowed for NPTR or PRE when the flag is TRUE | **Documented** |
| BR-026 | Issue and reissue require the adjudication approval securable | **Documented** |
| BR-027 | Notifications: submission, decision approved, decision rejected, certificate issued, certificate reissued. Recipients: applicant email and optionally proxy email. Send is best effort and non-blocking | **Documented** |
| BR-028 | Returned-for-correction email is not wired | **Documented** limitation |
| BR-029 | No QR on the certificate | **Documented** (client request to skip) |
| BR-030 | No API for real-time suspension or expulsion. Updates are manual | **Documented** limitation |
| BR-031 | Type 63 requires gazette, an adjudication meeting, and inspection | **Documented** in the type 63 script header |

## Workflow and payment

| Rule | Statement | Provenance |
|---|---|---|
| BR-040 | Completing a task with status 2 or 4, when a task with status 5 (Requested Correction) exists at a later step, moves the instance back to that requesting step instead of the template next step. The script comment's example is payment | **Documented** in `10_ApplicationChangeRequestSkipsPayment/updated_Pr_WorkflowInstanceTaskAction.sql` |
| BR-041 | If no status-5 task exists, the procedure keeps the previous next-step behaviour | **Documented** in that header |
| BR-042 | If the return-to step is missing from the template, the procedure falls back to `Fn_GetWorkflowTemplateNextStep` | **Documented** in that header |
| BR-043 | Invalid user on that procedure sets an error beginning `Security error: Invalid user - ` plus the username | **Implemented** in that procedure (`SET @ErrorMessage`) |

## Vehicles and licences

| Rule | Statement | Provenance |
|---|---|---|
| BR-050 | Professional Drivers Permit document checks are commented out in `110_Fn_ApplicationStep3Checks.sql` and `111_Fn_LicenceIssueStep2Checks.sql`. A commented condition in the licence-issue function also excluded NPTR (`DivisionTypeID != 2`) | **Implemented** as inactive checks in those scripts. PR #4 states the permit was made optional |
| BR-051 | A document type still exists for an original certified copy of a Professional Driver's Permit (`DocumentTypeID` 1020 in the OLAS seed) | **Implemented** seed. The document can still be stored. It is not the same statement as "the document is mandatory" |
| BR-052 | Operating-licence location restrictions exist as script folder `2025/1212_#29_Operating licence location restrictions` | **Implemented** as a script set. The rule text was not extracted line by line |
| BR-053 | Unused vehicle data has a cleanse procedure `122_Pr_Svc_CleanseUnusedVehicleData.sql` | **Implemented**. The retention rule inside the procedure was not extracted |

## Identity and access

| Rule | Statement | Provenance |
|---|---|---|
| BR-060 | Every web request must be authenticated. Login URL is `~/Auth/Login` | **Implemented** in `Web.config` |
| BR-061 | Forms ticket timeout is 30 minutes and expiration slides | **Implemented** in `Web.config` |
| BR-062 | Membership requires a unique email and a password question and answer. Password retrieval is off. Password reset is on. `passwordFormat` is `Clear` | **Implemented** in `Web.config`. Storing passwords without hashing is what that attribute means. **Requires confirmation** that this is accepted |
| BR-063 | Roles are cached in cookie `.NeoLegitimate_ASPROLES` | **Implemented** |
| BR-064 | E-hailing start-application permission name is `ApplicationsModule_Home_StartNewApplication`. Adjudication capture permission name is `ApplicationsModule_ApplicationRequest_Adjudication_CaptureDecision` | **Documented** |
| BR-065 | API methods that rely on `IsValidClaim` require a `UserID` claim. Missing claim is not valid | **Implemented** in `LegitimateLicencesApiController` |
| BR-066 | API CORS allows any origin, header, and method | **Implemented**. Whether that is an approved rule **Requires confirmation** |

## Operations rules stated as setup, not as product law

The environment checklist and `checklist.csv` tell an operator to:

- Enable SQL CLR and mark the database TRUSTWORTHY.
- Grant the database user SELECT, INSERT, UPDATE, DELETE, EXECUTE, and EXTERNAL ACCESS ASSEMBLY.
- Deploy web, API, and AFX services on separate IIS app pools.
- Install the Windows service for automatic start.
- Register the `legitimatedesktop` protocol and ActiveX print controls.
- Configure eNatis certificate path and Clickatell.
- Keep dev, test, and production connection strings and certificates apart.

These are **Documented** setup steps. They are not proof of how production is actually configured.

SQL jobs for backup, index maintenance, statistics, cleanup, and health are checklist items. They are **not** in this repository as SQL Agent scripts, except for application procedures the Windows service might call. **Unknown** which jobs exist on the server.

## Inferred rules (not stated as requirements)

- Staff act for a division (NPTR or PRE) and an office. Office is a required-looking field on the user screen (`Views/UserConsole/UserDetails.cshtml` asks for an office location). The segregation rules implied by branch names such as `feature/int-neoafrica-user-province-segregation` were not read as a finished rule.
- Applicants do not use the web app anonymously, because anonymous users are denied. They may still be data subjects who receive email and SMS.
- SignalR verification events are broadcast to every connected client (`Clients.All`). No filter by office or division is visible in `MessageHub.cs`.

## Conflicts

| Topic | A | B |
|---|---|---|
| E-hailing type names | Doc still uses codes `NPTR_EhailingProviderRegistration` and similar | SQL `1103` stores display titles without `NPTR:` |
| Gazette bypass default | Comment says production should be FALSE | Insert sets TRUE when the setting is absent |
| Database name | Checklist `LegitimateNATIONAL` | Scripts `LegitimateNational` |
| Service install path | Checklist `C:\Installs\LegitimateLicense\` | `checklist.csv` `C:\Applications\Legitimate Licences Windows Service` |
| Certificate duration | Feature doc says 7 years | A later script name says the duration was altered |

Do not treat either side as superseded until someone confirms the deployed database and config.
