# Business rules — rgbc_projects_net

Evidence date: 2026-10-05.

Source: `master` at `08268a8`.

Rules are taken from executable code. Comments are labelled when they are the only source. Nothing here is a policy decision.

Flag columns (`blnView`, `blnEdit`, `blnSpecial`, `blnManager`) are not defined in this repository. Their meaning is **Inferred** from the branches below.

## Identity

### BR-001 — Windows identity is the login key

**Implemented.** Forecast, Promotions, Score Card, Stock Control, and Superbowl Web look up `mstUser.strDomainLogin` using `Request.LogonUserIdentity.Name` converted to upper case. Forecast, Promotions, and Score Card pass that string through `CleanString`. Stock Control and Superbowl Web pass it through `CleanString` inside `sql_get_Users`.

The login pages do not check a typed password. Stock Control focuses a user text box and does not read it in the click handler.

### BR-002 — Module membership is a joined group row

**Implemented.**

- Forecast: inner join `mstUserGroupForecast` on `refUserGroupForecastID`. No row means no access.
- Promotions: inner join `mstUserGroupPromotion` on `refUserGroupPromotionID`.
- Score Card: inner join `mstUserGroupScoreCard` on `refUserGroupScoreCardID`.
- Stock Control: the group row's `SystemStockControl` must equal `StockControl`.
- Superbowl Web: `SystemSuperbowl` must equal `Superbowl`.

### BR-003 — Score Card can replace the Windows name

**Implemented.** If the query string contains `OVERRIDE_USERNAME`, that trimmed value is used instead of the Windows identity. There is no extra authorisation check around the override in `LogginUser`.

### BR-004 — Failed login message

**Implemented.** The user is told access is insufficient and that login is controlled through Active Directory. Stock Control's message says "Please contact _" (the name is an underscore in source). The others say to contact the system administrator.

### BR-005 — Usage log

**Implemented.** A successful web login inserts `mstUsageLog (mstUserID, dtStamp, strSystem)`. The stamp format is `yyyyMMddHHmmss`. A 13 millisecond sleep runs before the insert.

`strSystem` values: `Forecast` (Forecast, and also Promotions and Score Card), `StockControl`, `Superbowl`.

**Inferred.** Promotions and Score Card writing `Forecast` is a copy-paste error. **Requires confirmation** before anyone relies on `mstUsageLog` to separate those systems.

## Session and view rights

### BR-006 — Empty session returns to login

**Implemented.** Forecast, Promotions, and Score Card master pages redirect to `Index.aspx?M=1` when `Session("USER")` is empty, except DEBUG compiles, which load a hard-coded domain user (`SILICON\PJ`, `RGBC\PieterVDM`, or `SILICONOVERDRIV\pieter`).

Stock Control and Superbowl Web redirect when the app-setting user fields are empty. Superbowl Web also redirects when `ULEVEL` is `0`. DEBUG compiles fill dummy values (`UGROUP` `admin` or `Debug`, Superbowl `ULEVEL` `5`).

### BR-007 — View flag hides page content

**Implemented** for Forecast, Promotions, and Score Card. When `blnView` is not true, the master page removes content controls and shows: "You need View rights to access this page. Please contact you systems administrator."

The master load runs after the content page load, so a content page can still query the database before the content is cleared. **Implemented** (stated in a comment and follows ASP.NET page lifecycle).

## Forecast rights

### BR-008 — Edit flag controls adjustment save

**Implemented.** `Adjustments.aspx.vb` sets `btnSave.Enabled` from `blnEdit`.

### BR-009 — Special flag controls apply, planning, and open season

**Implemented.**

- Adjustments Apply button is enabled from `blnSpecial`.
- Planning redirects to Home with "Insufficient rights to access this function." when `blnSpecial` is not true. Planning Save is enabled from `blnSpecial`.
- Open Season redirects the same way when `blnSpecial` is not true, and enables the Set button only when it is true. The menu tooltip calls this "Administer Brand Adjustment Periods".

### BR-010 — Dev group sees Sandbox

**Implemented.** Forecast and Promotions remove the penultimate menu item when `strUserGroup` is not `Dev`. In the Forecast menu that item is Sandbox. In the Promotions menu Sandbox is also marked not selectable.

### BR-011 — Brand QF jobs are queued, and duplicates of a new job are removed

**Implemented.** `sql_insert_BrandQF_Job` inserts `sysCronJob` with type `BrandQF`, status `New`, and parameters `BrandID`, `Finyear`, `CopyBrandID`, `BrandName`. `sql_remove_duplicate_BrandQF_Job` sets matching `New` rows to status `Removed`.

### BR-012 — Fiscal year helper

**Implemented** in Forecast, Promotions, and Score Card `SystemFunctions`. Company id `1` supplies `strFYStart` and `strFYEnd`. The current `mstPeriod` row for `yyyyMM` supplies `intFiscalYear`. If `strFYStart` is `01`, the last completed fiscal start is 1 January of `intFiscalYear - 1` and the end is 1 December of `intFiscalYear`. Otherwise the start is month `strFYStart` in year `intFiscalYear - 2` and the end is month `strFYEnd` in year `intFiscalYear - 1`.

Which screens call these helpers was not fully traced. The functions exist.

## Score Card menu

### BR-013 — RSM and User lose administration menu items

**Implemented.** `Admin.Master.vb` removes menu entries by index for groups `RSM` and `User`: team setup, edit surveys, edit questionnaires, overall performance, and the separator items counted with them. `Admin` and `Dev` fall through and keep those items.

The comment dated 2018-07-11 says Team Setup is hidden for RSM. The code hides it for both `RSM` and `User`.

Other group names are not handled. They keep the full menu. **Implemented.**

### BR-014 — Mobile layout

**Implemented.** A mobile user-agent match or `Request.Browser.IsMobileDevice` selects `Mobile.Master`. Otherwise `Admin.master`.

## Stock Control edit rights

### BR-015 — Who may change BMA and orders

**Implemented** in `sql_Update_PP` when the caller does not pass an explicit `strEdit` clause:

| Group name contains | Columns written |
|---|---|
| `brand` | `dblBMA`, `strLastUser`, `dtStamp` |
| `shipping` or `order` | `dblOrders`, `strLastUserSM`, `dtStampSM` |
| `admin` | both BMA and orders, both user stamps |

Matching is case-insensitive and uses substring `Contains`, so a name that contains more than one of these words takes the last matching branch. Admin is tested last, so a name containing both `brand` and `admin` is saved as admin.

If the name contains none of these words, `strEdit` stays empty and the UPDATE sets no columns. **Implemented.**

`setSecurity` turns the order and BMA text boxes editable (blue, not read-only) for admin across a calculated period range. The brand and shipping branches exist further down the same function. Receipts are commented as not editable.

Product code `"0"` calls `setSecurity("Viewer")`.

### BR-016 — Imports menu is admin-only

**Implemented.** Stock Control enables menu item index 1 (Imports) only when the group name contains `admin`.

### BR-017 — Months-cover target

**Implemented.** Product period loads `mstBrand.dblVariance` and shows it as "Target Months Cover". Colouring treats months cover as out of band when it is greater than target + 0.5 or less than target − 0.5 (`ProductPeriod.aspx.vb`).

The variance report reads a column `Target Variance` and substitutes `2.5` when that value is null. The same ±0.5 test is applied (`BMAReport.aspx.vb`).

The constant `dblVariance = 2.5` on the product-period page is overwritten by the brand row when that row loads.

### BR-018 — Missing product-period rows are created as zeros

**Implemented.** While loading the grid, if `intPP = 0` and the product code is not empty and not `"0"`, the page inserts a `stkProductPeriod` row of zeros with last user `new`.

### BR-019 — Direct Sales row

**Implemented** (2021-03-08). The grid shows a row labelled Direct Sales.

- One product: sum of `dwfSales.lngUnitsTotal * mstProduct.dblFlat` for product codes with the same first five characters, grouped by period, top 15 periods in the grid range.
- Product code `"0"`: sum of `dwfSales.dbl9lTotal` for the brand, optional varietal and category `LIKE` filters.

No SQL predicate limits the sum to a direct-sales document type. **Requires confirmation** that this sum is what the business calls direct sales.

## Superbowl Web

### BR-020 — Level zero cannot stay signed in

**Implemented.** After login, `ULEVEL` comes from `mstUserGroupSuperbowl.intLevel`. The master page sends the browser back to `index.aspx` when that value is `0` (and when the group or domain is empty). What each positive level allows is **Unknown**. The menu does not branch on level; an older admin check is commented out.

### BR-021 — One user row carries every module's group

**Implemented.** Superbowl user save writes foreign keys for Stock Control, Superbowl, Forecast, Promotion, Score Card, Free Stock, and POR, plus `blnManager`, `blnManagerExecutive`, manager id, alternative manager, and executive manager.

A comment dated 2015-03-23 says manager id is not set on insert; a later update sets it. `sql_update_Users_refManagerID` as written does not include the keyword `SET` (`UPDATE mstUser refManagerID = ...`). **Implemented** SQL text. Whether that statement is called is a separate question; if it is called, SQL Server will reject it.

### BR-022 — Non-RGBC customers are a separate table

**Implemented.** Inserts and updates go to `mstNot_RGBC_Customer`, keyed by `strCustomerNo`. A 2010 comment says a duplicate-record check was disabled. That check was not re-opened.

## Cron and mail

### BR-023 — Only status New is picked up

**Implemented.** `Run()` counts and selects `sysCronJob` where `Status = 'New'`. A claimed row becomes `Processing`, then `Processed`, `Error`, or `Processed_MailError`. Result text includes a timestamp.

### BR-024 — Freestock and purchase-order mail

**Implemented** as cron cases. See FR-010 in [prd.md](prd.md). From-address on release builds:

- Freestock: `FreeStock Requisition <administrator@rgbc.co.za>`
- Purchase order: `Purchase Order Requisition <administrator@rgbc.co.za>`

Quote attachments for new PO mail come from `porDocument` where `strType = 'Quote'`. Completed requisition mail loads `sysQuery.ID = 61` and joins `porRequisition`, `mstUser`, and `mstVendor`.

### BR-025 — Missing manager

**Implemented** on the new-requisition cases that contain the check. The job result becomes `No Manager` and the success email path is skipped. The exact recipient rule (direct manager versus alternative) is in the case body and in header comments (alternate manager after 3 days for FSR; 24 hours for unapproved POs as of comment v2.0.2). The comment is **Documented** in the file header. The 24-hour figure was not re-read in the reminder loop.

### BR-026 — DEBUG mail is not the release host

**Implemented.** Inside `#If DEBUG`, cron mail uses `smtp.office365.com` port 587 with a compiled credential. `#Else` uses `My.Settings.SMTPHost` (value `10.0.0.17` in `app.config`). Superbowl Daily's setting is `tullamore.rgbc.local`. Daily 2's setting is `10.0.0.17`.

## Daily file processing

### BR-027 — Superbowl Daily starts itself unless debug is on

**Implemented.** `frmMain_Load` calls `Run()` when `My.Settings.blnDebug` is false.

### BR-028 — Daily 2 release reads Benriach shares

**Implemented.** Release compile uses `\\Benriach\Sales\ADM\` and `\\Benriach\Sales\2WaySystem\`. DEBUG uses `E:\work\RGBC\`. A partial-import flag is described in a 2014 comment (`chkPartialImport`, because Advantex-style files are day files rather than month-to-date). The control was not re-read.

### BR-029 — Un-invoiced requisition email was turned off

**Documented** in Daily 2 header, 2016-01-28: the Monday email for approved requisitions with no invoice number was disabled. **Requires confirmation** that `btnEmailRequisitions_Click` no longer sends it.

## Number display

### BR-030 — Positive percentages get a plus sign

**Implemented** in Forecast, Promotions, and Score Card `FormatPercent`. Values that fail to parse return `0.0`.

### BR-031 — Non-breaking space is replaced in numbers

**Implemented.** `FormatNumber` replaces character 160 with a comma. The comment says the RGBC server's number format used that character instead of a normal space. **Inferred** as the reason. The replacement itself is **Implemented**.

## Rules not established

- The allowed values of `strUserGroup` for Forecast and Promotions besides the `Dev` check.
- The numeric meaning of Superbowl `intLevel`.
- Who may open Stock Control brand and user admin pages. Those pages exist; a page-level role check was not found in the master page beyond the Imports menu.
- Deletion rules, approval thresholds, and pricing rules for master data.
- Any rule that exists only in the SQL Server database (triggers, procedures, constraints).
