# Business rules — snell-crondaily

Evidence date: 2026-10-03. Revision: `master` `60aeb26` (2023-11-17).

There is no business-rules document in the source repository. Rules below are taken from code that `frmMain.Run` actually reaches, unless a section says the method is not called. **Inferred** means the code behaves this way and no separate written rule was found.

Credential values are omitted.

## Identity

**Implemented.** The window title is `Snell CronDaily`. The assembly title and product are `SNELL CronDaily`. The log process name and the mail subject prefix are `SNELL CronDaily`. The mail from-display name is `SNELL CronDaily`.

**Implemented, conflicting label.** ClickOnce `ProductName` is `Superbowl Daily 2`. `PublisherName` is `Silicon Overdrive`. Log table `stgSuperbowlLog` still uses the Superbowl name. Copyright year in the assembly is 2015.

**Inferred.** "CronDaily" describes a batch someone expected to run on a schedule. No schedule is in the repository.

**Unknown / Requires Confirmation.** Whether operators still call this Superbowl Daily, and whether Edwards Snell (`esnell.co.za`, domain `EDWARDSNELL`) is the business owner of the data. Those names are in config and in uncalled SQL. They are not a signed scope statement.

## Who may run it

**Implemented.** There is no application login. Anyone who can start the exe can press Run, clear `stgSuperbowlLog`, load files, update `dwdCustomer` / `dwdProduct`, and send the log mail.

**Implemented.** `SingleInstance` is true, so a second launch does not open a second form.

**Implemented.** The manifest requests `asInvoker`. The ClickOnce zone is LocalIntranet. Manifest signing is off.

**Unknown / Requires Confirmation.** Which Windows account is used in production. The release SQL connection is integrated security, so that Windows account is the database login. The release SMTP username in git is `EDWARDSNELL\phpadmin`.

## When a batch starts

**Implemented.** If application setting `Debug` is false, load runs the batch and closes the form. If it is true, load only shows the form.

**Implemented.** The value stored in `App.config` is true. A comment on that value says to change it to false in production. `60aeb26` added the comment and did not change the value.

**Inferred.** Production machines may carry a different `exe.config` than git. That deployed file was not in the repository inspection.

**Implemented.** Both import checkboxes default to checked. An operator can clear one before Run and that import is skipped. Auto-run (`Debug` false) uses those designer defaults because nobody has clicked the form yet, so both imports run.

**Implemented.** Partial Import defaults to checked and is ignored.

## Which file is the source

**Implemented.** Customer source file name is `pda_crm.txt`. Product source file name is `pdaprod.txt`.

**Implemented.** Release compile reads them from `C:\SAPREPORTS\`. DEBUG compile reads them from `D:\Documents\Work Stuff\ESnell\`.

**Inferred.** The release folder name points at SAP report output. A commented export in `frmMain_Load` is named `AdvPDA CRM` and would have written `pda_crm.csv`. The producer is not in this repo. **Unknown / Requires Confirmation** which system writes the two files.

**Implemented.** The copy into `Imports\` overwrites. If the drop file is missing, a file already sitting in `Imports\` is still eligible. **Inferred** consequence: a missed drop can reload yesterday's extract and then mark the whole dimension inactive before the warehouse SQL runs.

## What a customer row must contain

**Implemented.** The file is comma-separated. Quoted fields are allowed. The first record is a header and is not inserted.

**Implemented.** `ENTITY_CODE`, `HEADER_CustName`, `HEADER_ChannelDesc`, and `HEADER_RegName` must be non-empty after cleaning. A failing row is skipped and logged as an invalid record. The rest of the file continues.

**Implemented.** The stored customer number is the last 7 characters of `ENTITY_CODE`, interpreted as an integer, then written back as text. Leading zeros in that tail are lost. A non-numeric tail fails the insert for that row.

**Implemented.** Eighteen other header fields are stored when present and are not required by this method. The column list is in `prd.md` FR-008.

**Inferred.** Taking the last 7 digits is how this load matches an existing customer number that may arrive with a longer prefix. No comment states that reason. Confirm the source code's width.

## What a product row must contain

**Implemented.** `ITEM_CODE`, `ITEM_NAME`, and `ITEM_BRAND` must be non-empty. `ITEM_CATEGORY_CODE` may be empty.

**Implemented.** `ITEM_WSPCASE` and `ITEM_WSPBOTTLE` must convert to numbers. If their sum is 0, the row is rejected as invalid prices. A positive case price with a negative bottle price that cancels it is also rejected. A negative case price with a larger positive bottle price is accepted, because only the sum is tested.

**Implemented.** The stored item code is the last 6 characters of `ITEM_CODE`, interpreted as an integer. Indexes 4 and 5 are not passed through `CleanString` before the numeric test.

**Inferred.** A zero price on both pack sizes is treated as bad source data rather than a real free product. No written pricing policy is in the repo.

## Duplicates

**Implemented.** Duplicate detection groups the staging table by the process key and reports groups with `COUNT(ID) > 1`. The log type is warning. Rows stay in staging.

**Unknown / Requires Confirmation.** Whether `ID` is an identity column, and whether `sysQuery` 21–24 collapse duplicates or insert them twice.

## Active flag

**Implemented.** Before warehouse SQL, every row in `dwdCustomer` (customer file) or `dwdProduct` (product file) has `blnActive` set to 0. The update is not limited to keys in the file.

**Inferred.** The intention visible from the order of calls is that the following `sysQuery` statements mark the imported rows active again. Those statements are not in git, so this remains an inference. If they do not set `blnActive`, a successful file import leaves the dimension inactive.

**Implemented.** This step runs only when the file was valid. A missing file does not clear the active flags.

## Promotional accounts

**Implemented.** On a valid customer file, every `dwdCustomer.blnPromotionalAccounts` is set to 0, then `sysQuery` 25 is executed.

**Inferred from the log text only.** Query 25 is what marks promotional accounts. The comment in code says `stg.Channel==HOUSE`. The SQL is **Unknown / Requires Confirmation**.

**Implemented.** If query 25 throws, the zeros remain. The Gauteng update still runs after that catch.

## Gauteng regions

**Implemented.** After the promo step, `dwdCustomer.strRegionDesc2` values that match `Gauteng %` are replaced with `Gauteng`.

**Documented in a code comment (2015-11-24).** "Gauteng Main Market >> Gauteng". The comment names a person initial `pj`. The predicate is any value whose prefix is `Gauteng `, not only the words Main Market.

**Implemented.** A failure of this update is not logged. The catch is empty.

## Warehouse statements

**Implemented.** The application does not contain the insert/update SQL that merges staging into the warehouse. It loads it:

| ID | Used as |
|---|---|
| 21 | Product update |
| 22 | Product insert |
| 23 | Customer update |
| 24 | Customer insert |
| 25 | Customer promotional accounts |
| 43 | Rows to put in the log email |
| 35 | Commented. Would have fed an AdvPDA CRM export |

**Inferred.** IDs 21–24 perform an update-then-insert merge, because the success messages say Updated and Inserted. Confirm against `sysQuery.memQuery`.

**Implemented.** Customer failures of those steps are log type `error`. Product failures are log type `warning`. That split was introduced in `86821ee`. No comment explains why the severities differ.

**Implemented.** `DeleteDW` is not called. Rows are not deleted from `dwf*` or `dwd*` by the live path. `SetInactives` only flips `blnActive`.

## File retention

**Implemented.** After a successful staging load, Release builds move the Imports file to `Imported\` with a timestamp before the original extension. DEBUG builds copy it and leave the Imports file in place.

**Inferred.** A DEBUG run can import the same Imports file again on the next Run even when the drop folder was empty. A Release run cannot, unless a new copy is downloaded.

**Implemented.** Archive failure is a warning. The warehouse steps are not skipped because of it.

## Logging and mail

**Implemented.** `WriteLog` inserts into `stgSuperbowlLog`: tick stamp, timestamp, type, process, action, text, and `blnEmail` (default 0). The log text is passed through `CleanString`.

**Implemented.** Each `Run` deletes every row in `stgSuperbowlLog` before it writes `Batch Start`. History of the previous run is not copied anywhere. The insert into `logSuperbowl` is commented out.

**Implemented.** The email is sent after `Batch Completed` is written, so a normal run's mail includes that completion line.

**Implemented.** Recipients are the semicolon-separated `EmailRgbc` list. Empty tokens are skipped. Tokens are not trimmed.

**Implemented in `App.config`.** Current list: lloyd@overdrive.co.za; maanie@overdrive.co.za; dimphoz@esnell.co.za.

**Discrepancy.** Tracked `bin\Debug\SNELL CronDaily.exe.config` still has pj@overdrive.co.za; maanie@overdrive.co.za. That file was not part of `234cedd`.

**Unknown / Requires Confirmation.** Which list production should use. `EmailBfbe` is not read.

**Implemented.** The commented SQL for query 43 excluded `debug` rows and treated process `Free Stock Requisition` as a grouped line. The executed filter is whatever is stored in the database.

**Inferred.** `blnEmail` was meant to stop a second send of the same row. `Run` truncates the table first, and the success path sets `blnEmail = 1` only after the send attempt. The button Email Log can send the current table again. Nothing in `Run` checks `blnEmail` before sending.

## String cleaning

**Implemented.** `CleanString` replaces a double quote with a single quote, doubles single quotes, collapses two spaces into one, and trims. It does not strip commas. Comma splitting is done by `TextFieldParser` before this runs.

**Inferred.** Doubling quotes is there so the value can be embedded in a SQL string literal. It is not parameterised SQL.

## Rules that exist only in uncalled methods

These are not behaviour of `Run`. They are recorded so they are not mistaken for the live batch.

| Rule in source | Method | Called by Run |
|---|---|---|
| On Monday, mail approved requisitions with no invoice number whose date is more than 14 days ago | `EmailUnIinvoicedRequisitions` | No |
| To `Genevieve@esnell.co.za`, CC `pj@overdrive.co.za` and `maanie@overdrive.co.za` | same | No |
| If the latest `mstPeriod` is less than 30 months ahead, insert six further periods from company `RGBC` | `CheckPeriods` | No |
| Fiscal year rolls using `mstCompany.strFYStart` | `CheckPeriods` | No |
| Once per run, store counts of customers and products in `dlyDataWarehouse` | `LogDWD` | No. And it is not invoked at all |
| On Monday, copy `dwfCustomer` and `dwfOnconPromo` into dated tables | `BackupMintPromos` | No |
| Partial import skips the pre-delete of warehouse facts | `SetDeletes` | No. Call sites commented |

**Inferred.** The un-invoiced requisition mail and the Monday backups belong to the older Superbowl / free-stock batch that this project was copied from. `snell-cronjob` is the repository whose live path still sends free-stock mail. Confirm before treating any of these methods as a CronDaily requirement.

## Explicit non-rules

- This program does not approve requisitions, assign pink slips, or post SAP orders.
- This program does not delete warehouse rows on the live path.
- This program does not reject a file because of duplicate keys.
- This program does not create `Imports`, `Imported`, or `Exports`.
- This program does not authenticate an operator.
- No test asserts these rules. A change to a checkbox default or to `sysQuery` in the database changes the batch without a code change in this repo.
