# Business rules — superbowl

Evidence date: 2026-10-02.

Source: `git@bitbucket.org:overdrive/superbowl.git`, `master` at `a6f4b8b`.

Rules are taken from `docs/tasks/TASK-001.md`, `UserPermissions.vb`, detail-page validation, and SQL scripts. **Inferred** means the code behaves this way and no sentence in `docs/` states it as policy. Do not treat an inference as an approved rule.

## Access

1. **Documented and implemented.** Three named levels: Viewer `intLevel = 2` (read-only), SC Admin `intLevel = 4` (Product menu and Export Sales records), Full Admin `intLevel = 5` (all edits). Group name for level 4 is `SC Admin`, system `Superbowl`. `UserPermissions.vb`, `Scripts/Add_SC_Admin_Superbowl_Group.sql`, `docs/tasks/TASK-001.md`.
2. **Documented.** Full Admin level 5 keeps every edit right it had before SC Admin. Viewer stays read-only.
3. **Documented.** Export Sales Process is full Admin only. SC Admin may add, edit, and delete export rows. **Implemented:** Process and Select All are disabled and `DemandFullAdmin` runs on process. Delete of a row with `blnProcessed = true` is disabled in the grid. **Implemented gap:** the delete event does not re-check `blnProcessed` (the "in use" counter is fixed at 0).
4. **Documented.** Accounts, Sales Reps, Setup, Month End processing, and Integrity data changes are full Admin only. **Implemented** by `DemandFullAdmin` and disabled buttons on those pages.
5. **Documented.** Product Archive counts as Product access, so SC Admin may archive. **Implemented.** `ProductArchiveDetails.aspx.vb`.
6. **Documented.** A missing or invalid session level fails closed. `CurrentLevel()` returns 0 when the session value does not parse. Demands then throw HTTP 403. **Implemented.**
7. **Documented.** Level 3 is not an edit level for Export Sales. **Inferred** for every other page as well: only exact levels 4 or 5 pass the edit functions. Any other integer, including 1 or 3, is read-only.
8. **Inferred.** A Windows identity with no `mstUser` row for system `Superbowl` is refused ("Insufficient Access"), even if Active Directory authenticated them. The page text says Active Directory controls login.
9. **Implemented, not a product rule to copy.** When `blnDebug` is true and the binary is DEBUG, the master page invents user `a` at level 5 if `UDOMAIN` is empty. Committed config has `blnDebug` true. Production value is **Unknown**.

## Identity and audit

10. **Implemented.** The name stored on saves is `Session("User")`, which login sets from `mstUser.strUserName` (comment v1.4.5, 2016-09-13).
11. **Implemented.** Login inserts `mstUsageLog` (`mstUserID`, `dtStamp` `yyyyMMddHHmmss`, `strSystem = 'Superbowl'`).
12. **Implemented.** Customer save captures the previous `mstCustomer` row in `AuditEntity` before update. The audit log page reads the audit store by date. The exact insert performed by `AuditEntity` was not line-audited for every entity. **Unknown** which entities besides customers write audit rows.
13. **Implemented.** Export processing failures call `sqlDB.WriteLog` with process name `Export & DC Sales: dwfSales`.

## Customers

14. **Implemented.** Customer number, name, branch, channel segment, sales rep, type 1, type 2, and country are required.
15. **Implemented.** If the country dropdown is empty and the country text matches `mstCountry.strCountry`, that id is selected. Otherwise the user must pick a country or add one under Accounts → Countries.
16. **Implemented.** Tags maximum 500 characters. Liquor licence maximum 25 characters. Column script: `Add_strTags_Columns.sql`, `Add_strLiquorLicense_Column.sql`.
17. **Implemented.** Typing a customer number that already exists opens that customer instead of creating a second row. The explicit "customer already exists" check on save is commented out.
18. **Documented** (`Migrate_NonRGBC_To_mstCustomer.sql`). Non-RGBC import inserts into `mstCustomer` only when `strCustomerNo` is absent. It does not delete `mstNot_RGBC_Customer` and does not fill `dwdCustomer`. **Implemented** in the web app: the Non-RGBC list always redirects to the main customer list.
19. **Inferred.** Promotional accounts are excluded from the home and month-end sales totals unless the sales row's free-stock 9L is 0. The SQL predicate is `(blnPromotionalAccounts = 0) OR (dbl9lFreeStock = 0)`.

## Products

20. **Implemented.** Product code length must be at least 6. The message example is `99999X`.
21. **Implemented.** Product type, both descriptions, brand, and display order are required. Tags maximum 500 characters. Duplicate-product SQL checks are commented out.
22. **Implemented.** Product data and dimensions require a product (labelled "SAP Code Required") and a brand.
23. **Documented and implemented.** A competitor set has a name, one primary product, at most 10 competitors, no duplicate competitor codes, and no self-reference. `Create_mstProductCompetitorSet_Table.sql`, `CompSetProductDetails.aspx.vb`.
24. **Implemented.** Archive codes are the max five-character prefix where `strProdCode >= '90001'` and `strProdCode LIKE '9%'`, plus one. The archived pair is `{prefix}F` and `{prefix}S`. Both `mstProduct` and `dwdProduct` rows are set `blnActive = 0` and `strProdCodeArchive` is the old code. Competitor-set primary and competitor codes are renamed with them. A `sysCronJob` row `ArchiveProduct` / `New` is inserted. Completed queue rows for the old codes are deleted first; other queue rows are renamed. **Inferred** from the `F`/`S` suffix split: each product is stored as two codes (the file does not define what F and S mean).

## Reference data

25. **Implemented.** Unique names (or codes, where noted) are rejected with "already exists" for brand, company, principal, country, customer group, key account, key account group, rep id, rep manager, segment, Snell area, and Snell super group code.
26. **Implemented.** Brand lead months must be an integer from 0 to 12. Variance must be a positive number. Principal, company, and brand manager are required on a brand. Company and principal require financial year start and end text. The allowed format of those year fields is **Unknown** (non-empty only).
27. **Implemented.** Key account requires a price group code (comment v1.4.3, 2013-11-06, field `strPriceGroupCode`).
28. **Implemented.** Rep requires id, name, market, branch, segment, and email.
29. **Implemented.** Branch requires name, sales office, region, code, SAP code, and region description 2. All six messages say "Branch name required." Uniqueness uses code and SAP code.
30. **Implemented.** Channel segment save does not check that primary or secondary channel or segment text is non-empty, and does not check duplicates.
31. **Implemented.** User name, domain login, and email are required. Domain login must be unique against other `mstUser` rows. The insert also writes group ids for other systems (stock control, forecast, promotion, scorecard, free stock, POR, SDS) and manager flags. What those other systems require is **Unknown** in this repo.

## Export sales

32. **Implemented.** A new or edited export requires customer number, product code, period, and country.
33. **Implemented.** Process ignores ids that are not numeric, rows that no longer exist, and rows already `blnProcessed`. A product with no brand is logged and skipped. Posted rows share one batch id (`Date.Now.Ticks`). Sales facts for that batch are replaced (`DELETE` then `INSERT` into `dwfSales`). Document number is `Y` plus a zero-padded 7-digit `ExportID`, document type `I`. 9L is `units * pack * size / 9000`, rounded to 4 decimal places. If the sales insert fails, the batch is undone and a log row says `UNDOING PROCESS`.
34. **Implemented.** Sorting, paging, or a new search clears the export selection.
35. **Inferred.** Country on exports is both `refCountryID` and `strCountry`. Script `Add_mstCountry_and_refCountryID_FKs.sql` adds the foreign key on `mstExports` and `mstCustomer` and backfills. Whether the live database has that key is **Unknown**.

## Utilities

36. **Implemented.** Month End Process may be run only by full admin. It materialises query `bfbe-Sales_Month_to_Date` for the chosen `yyyyMM` period and today's date, as a tab-separated file under `BFBE\`. It does not send the email method.
37. **Implemented.** Integrity inserts from master to warehouse, or warehouse to master, are full admin only and run only for checked grid rows.
38. **Implemented.** Home period defaults to the current `yyyyMM`. The grid is sales dates at or after that period, plus a total row.
39. **Inferred.** `Duodecimal` treats a period as `year * 12 + month - 1` and is how Integrity labels the current and previous calendar months. It is not a 13-period fiscal calendar despite the class name.

## Scripts that encode data rules

| Script | Rule |
|---|---|
| `Add_SC_Admin_Superbowl_Group.sql` | One `SC Admin` group at level 4. A same-named row with another level or system aborts |
| `Add_mstCountry_and_refCountryID_FKs.sql` | `refCountryID` on customer and exports; FK added `WITH NOCHECK` |
| `Add_mstCustomer_default_refCountryID_SouthAfrica.sql` | Default `mstCustomer.refCountryID` to 13 only when `mstCountry` already has `CountryID = 13` and `strCountry = South Africa`. The script header says Superbowl Daily `cognos09.txt` inserts omit the column, and the column is NOT NULL after the country FK script. It does not backfill old rows |
| `Add_mstCustomerGroup_and_refCustomerGroupID.sql` | Customer group table and customer foreign key |
| `Create_mstProductCompetitorSet_Table.sql` | Competitor rows, position, active flag; header says up to 10 per primary product |
| `Migrate_NonRGBC_To_mstCustomer.sql` | Insert missing customer numbers only; do not delete the source table; do not fill `dwdCustomer` |
| `Fix_Null_Rep_Refs_In_mstCustomer.sql` | Repair null rep references. Thresholds are in the script |
| `Add_blnCompProduct_Column.sql`, `Add_CompSetName_Column.sql` | Competitor-product flag and set name |
| `Backup_Tables_Before_mstCountry_Migration_20260424.sql` | Copies tables to `*_backup_20260424` before the country change |

These scripts are not invoked by the web app. Applying them is an operator action. **Unknown** which have been run in production.

## Concurrency (inferred)

40. **Inferred.** List page index, filter, and "show more" are stored in application `appSettings`. That state is shared by every session. TASK-001 already forbids using appSettings for authorization for this reason. The list keys were left in place.
