# Business rules — superbowl_cronjob

Evidence date: 2026-10-02. Revision: `master` `986893e` (2026-09-11).

Rules below are what the executable enforces. They are **Inferred** from code unless a comment or SQL script states them. This document does not promote them to approved policy. Secret values are omitted.

## Run control

### BR-001 — Debug suppresses the automatic cycle

**Inferred**, and visible in `app.config`.

`My.Settings.Debug` false: `Run` then `Me.Close` on load. `Debug` true: wait for the Run button. Committed value is `True`.

### BR-002 — One cycle, 40 minutes

**Inferred.**

`Run` calls `PreProcess` once, then walks `sysCronJob` rows with status `New`. If elapsed ticks reach `40 * 60` seconds, the loop stops. Afterwards every status like `%Processing%` is set back to `New`.

### BR-003 — Queue order

**Inferred.** Comment `v1.0.10` (2018-07-16) says the main loop order changed.

`ORDER BY Left(dtCronJob,10) DESC, sysCronJob.Type ASC`. Newest date prefix first, then type name ascending. `dtCronJob` is written by this app as `yyyyMMddHHmm` with no separators (`RepslyFSR_AM_Process`), so the left 10 characters are not a pure date for those rows. Effect on sort order versus older writers: **Unknown / Requires Confirmation**.

### BR-004 — Job status vocabulary

**Inferred.**

Writers use `New`, `Processing`, `Processed`, `Error`, and `Processed_MailError`. There is no handler for any other incoming status. Unknown `Type` values are not updated, so they stay `New`.

## Repsly import

### BR-005 — Incremental cursor

**Inferred**, and described in `.cursor/plans/repsly_order_integration_faa35b01.plan.md`.

The next request starts at `LastIDProcessed` from the latest `repslyImportRun` with status `Success`. If none exists, start at 0. A failed run's catch path completes the run with status `Error` and `LastIDProcessed` equal to the start id, so the cursor does not move forward.

### BR-006 — Identity of an order

**Inferred.**

`PurchaseOrderID` is unique on `repslyOrder` (script). Save updates that row and deletes then reinserts `repslyOrderItem`. `blnSentToESnell` is not cleared by the update statement.

### BR-007 — FSR versus sales order

**Inferred.** Comment in `RepslyOrder_ESnell_Process`: FSR does not go to Edward Snell on the order path.

`DocumentTypeName` trimmed and upper-cased `FSR` is excluded from the order SOAP query and is the only set `RepslyFSR_AM_Process` promotes.

## Free-stock promotion

### BR-008 — A promoted requisition starts Pending

**Inferred.**

Insert sets `strStatus` to `Pending`. Pink slip is `RG` plus `MAX(RequisitionID)` zero-padded to 7 digits, read after the insert. Line price is `dwdProduct.dblPrice`, total `Round(price * quantity, 4)`. First-edit user string is `Cronjob`.

### BR-009 — Who the requisition belongs to

**Inferred.**

`refUserID` comes from `mstUser` matched to `RepresentativeCode`. The match query is in `GetRefUserIdForRepresentative`. If it fails, the order is skipped.

### BR-010 — Bill-to account

**Inferred.** Comment in `RepslyFSR_AM_Process`: live API titles use `Province | Bill-to Account No`; some forms use `!` instead of `|`.

Exactly one of the five regional bill-to titles may be non-empty. The stored account is the leading number extracted from that value. `ClientCode` is the ship-to. Region stored on the requisition is the province of the chosen bill-to title.

### BR-011 — Delivery method

**Inferred.**

Attribute title `Delivery Method` with value `Delivery` sets `blnDelivery` 1. Every other value sets 0, which the SAP builder treats as collection.

### BR-012 — Request type

**Inferred.**

Resolve in order: custom attribute `Request Type` matched to `fsrRequestType.strRequestType`; else setting `RepslyFsrRequestTypeName` (committed value is empty); else the first `fsrRequestType` whose name contains `Free Stock`.

## Edward Snell / SAP

### BR-013 — Sales orders use reason 279

**Inferred.** Hardcoded in `IdocGenerator.BuildSoapXml`, commit message `3ca2689` ("RGBC PDA order").

`Bsart` is `OR`. Collection sets delivery block `ZG` and `Bstzd` `COLL`. Delivery or a missing delivery type leaves `Lifsk` and `Bstzd` blank on this path. That is a different rule from free-stock delivery (BR-015).

### BR-014 — Material number drops the unit suffix

**Inferred.** Commit `fbdf69c`.

A trailing `S` or `F` is removed from the RGBC product code before it is sent as `Idtnr`. On the free-stock path, `S` means unit `BT` and any other ending means `CS`.

### BR-015 — Approved free-stock orders use reason 285 and block Z5 when delivered

**Inferred**, and stated in `FsrOrder_ESnell_Process` and `AlterFsrRequisition_SapColumns.sql`.

- Export runs only when `FsrSapExportEnabled` is true. Committed value is false.
- Eligible rows: `strStatus` `Approved`, not yet `blnSentToSap`, retry count below `SapMaxRetryCount` (committed 5).
- `Augru` from `FsrSapAugru` (committed `285`). `Bsart` from `FsrSapBsart` (committed `OR`).
- `blnDelivery` false: `Bstzd` `COLL`, `Lifsk` `ZG`. `blnDelivery` true: `Lifsk` `Z5`, `Bstzd` empty. Commit `bb5b170`.
- Ship-to that starts with `A` is replaced by the bill-to for both sold-to (AG) and ship-to (WE).
- Partner numbers are the requisition account fields, not `dwdCustomer.strSAPCode`. They are left-padded with zeros to 10 characters.
- AG `Bname` is `PINKSLIP:` plus the pink-slip number.
- Z1 email defaults to the `FsrSapZ1Email` setting. Z2 is the requesting user's `mstUser.strEmail`, or `orders@esnell.co.za` if that is empty.
- Notes on the IDOC are at most 35 characters.

### BR-016 — Free-stock SAP failure handling

**Inferred.**

Data problems (no lines, zero quantities, missing accounts) store `strSapLastError` and do not increment `intSapRetryCount`, and still send the failure email. Transport or SOAP rejection increments the counter. The notify message is sent when the counter reaches the max, or immediately for those data problems. Recipient setting: `SapFailureNotifyEmail`.

### BR-017 — Repsly order SOAP failure is retried forever

**Inferred.**

`blnSentToESnell` stays 0. The next run selects the row again. No max retry on this path.

## Approvals and mail routing

`SalesManagerMaxValue` is committed as `20000`.

### BR-018 — New free-stock mail follows value

**Inferred.** Same test in the noon chase and in `FSR_NewRequisition`.

If the sum of `fsrItem.dblTotal` is less than or equal to `SalesManagerMaxValue`, mail the alternate manager. Otherwise mail the executive manager. The job records the manager ids back onto `fsrRequisition` during the noon chase. Debug compiles replace the recipient with a single Overdrive address.

Approved free-stock mail (`FSR_RequisitionApproved`) goes to `SnellEmailAddress` in a release build, not back to the manager.

### BR-019 — Noon window

**Inferred.**

FSR chase, POR chase, and vendor chase each require local `TimeOfDay` greater than 12:00:00 and less than 12:19:00. Outside that window they do nothing.

FSR chase: `Pending`, `DATEDIFF(day, dtDate, getDate()) > 3`, `dtEmailSentToAltManager` is null. After a successful send, that timestamp is set, so it does not send again.

POR chase: `Pending`, age greater than 1 day, same null timestamp. Comment `v2.0.2` (2018-10-16) says this changed from 48 hours to 24 hours, and that both the direct and alternate manager are included. The code adds the alternate address when it is non-empty. Routing uses `viePOR_UGAccess`: compare `dblTotal` with `Lvl1_UpperLimit` and `Lvl2_UpperLimit`, then resolve `SELF`, `MAN`, or a literal email in `LvlN_Auth` / `LvlN_Auth_Alt`.

### BR-020 — Purchase-order document routing

**Inferred** from the case comments and the recipient assignments read in `Run`.

- New PO: routed approver, requester on CC, `BCCAddresses`, PDF, and quote files that exist on the POR document share.
- Approved PO: accounts payable. Vendor accounts email is used when `strEmailAccountsPayable` is set; otherwise `PorEmailAddress` (committed `accountspayable@rgbc.co.za`). A CSV is written to the Sage export share.
- Receipted PO: similar mail plus invoice files, a receipts CSV, PO status updated from received quantity versus ordered quantity, receipt status set to `Processed`.
- Declined and cancelled: email, including the vendor (comments dated 2019-05-30).
- Completed: email using template 61 (added 2021-02-09, commit `fa35ee9` area).

Exact recipient for every branch inside each case was not re-listed here beyond what was read. The case body is the source.

### BR-021 — Vendor workflow mail

**Inferred.**

Queue types send HTML from `sysQuery` and address finance roles held in settings: `FinanceAccountant`, `FinanceExecutive`, `FinancialAccountant`, `Accounts`, `SuperUserAddresses`. `POR_Activate` always addresses `accountspayable@rgbc.co.za` in the release snippet that was read, and CCs the requesting user.

### BR-022 — Vendor noon reminder SQL does not match the FSR pattern

**Inferred.** Likely defect. **Requires confirmation** that this path is expected to run.

The query is concatenated as `SELECT * FROM porVendor` immediately followed by `WHERE`, producing `porVendorWHERE`. There is no `dtEmailSent` null check. On success it sets `strMailStatus` to `Mail re-Sent` and would be eligible again on the next noon run. Commented Approved and Completed vendor chasers are not active.

## Quantity forecast and archive

### BR-023 — Brand QF formula

**Documented in comments** at the top of `QFGenerator.vb`.

`QF[P][Sku][C] = (Sales[F][B][C] / Sales[F][B][R]) * (Sales[F][Sku] / SalesApplicable[F]) * QFA[P][B][R]`.

`BrandQF` jobs pass `FinYear` and `BrandID` into `GenerateBrand`. Table name setting `dwfQFTable` is `dwfQF`.

### BR-024 — Archiving a product keeps the unit suffix

**Inferred.** Comment: moved from the website because large brands timed out.

`ArchiveProduct` requires `strProdCodeOld` and `strProdCodeNew`. It rewrites codes ending in `F` and `S` separately (sales and QF updates were read; the result string expects 13 sub-steps).

## Access

### BR-025 — No user login inside this executable

**Inferred.**

The process uses the Windows identity for integrated SQL (release) and for UNC shares. Debug SQL uses a SQL login in config. There is no role check in the form. Authorisation of managers is data in `mstUser` and `viePOR_UGAccess`, enforced only by which email address is chosen.

## Rules not established

- Who may approve a free-stock or purchase-order requisition in the web application.
- Whether `20000` is still the business threshold.
- The production scheduler, time zone, and whether noon local time is the intended chase window.
- Whether Augru `279` and `285` are the codes Edward Snell has configured.
- Whether integrated security against `Benriach` is still correct after the file servers were renamed in code.
