# Business rules — sagegs1xml (BSBC Invoice Export)

Evidence date: 2026-10-04. Source: `git@bitbucket.org:overdrive/sagegs1xml.git`, `master` at `fc601bb`.

No business-rules document exists in the repository. Rules below are taken from the WinForms code and from `SUN_SAGE2XML.sql`. Rules recovered from code are marked **Inferred** when the code does not state them as policy, and **Implemented** when they are the behaviour itself.

Two pipelines exist in the tree. They do not implement the same rules.

- **App pipeline.** `frmMain.vb` PROCESS. CSV over FTP. This is what the executable does.
- **SQL script pipeline.** `SUN_SAGE2XML`. GS1 XML. Nothing in the executable calls it.

Do not merge them into one approved policy. Credential values are omitted.

## App pipeline — who can run an export

### BR-001 — No application login

The form opens straight onto the export screen. There is no user table, password prompt, or role check.

Provenance: **Implemented**.

### BR-002 — Windows profile holds settings

`My.Settings` are user-scoped (`Settings.settings` scope `User`, `allowExeDefinition` `MachineToLocalUser`). `SaveMySettingsOnExit` is true. One Windows user can store a different SQL server and folder from another user on the same PC.

Provenance: **Implemented**.

### BR-003 — Helpdesk is displayed, not integrated

Both tabs show "Helpdesk: 0861 XPROCURE or 0861 977-628". The program does not send mail or open a ticket.

Provenance: **Implemented** as label text.

## App pipeline — when an export runs

### BR-004 — A folder is mandatory

PROCESS returns immediately when `XMLPath` is empty, with the message that a directory must be set "where XML archives and conversion can be done".

Provenance: **Implemented**.

### BR-005 — Upload Only does not mean upload only

If the checkbox is checked, PROCESS does not export and does not upload. It shows the busy-query message even when the header worker is idle. The condition is `QueryExecuterIsIdle And chkUploadOnly.Checked = False`.

Provenance: **Implemented**. Treating the checkbox as a second mode would contradict the code.

### BR-006 — A busy header query blocks a new PROCESS

If `SQLHeader` is busy, the operator sees "A SQL request is still open. please wait or restart the application."

Provenance: **Implemented**. The check does not look at `SQLFooter`. A click that starts while a detail query is running can still start a new header query. That overlap is **Inferred** from the guard covering only `SQLHeader`.

### BR-007 — Dates are calendar days, not validated as a range

Both pickers start at the current date and time. The SQL text receives `yyyy-MM-dd` only. The code does not reject a from-date after the to-date. What the server procedure does with that pair is **Unknown**, because `SUN_GETINVOICES` is not in Git.

Provenance: **Implemented** for the client. Server rule **Unknown**.

### BR-008 — The SQL command text is concatenated

Invoice dates and the invoice reference are inserted into the `EXEC` string with single quotes. They are not `SqlParameter` values.

Provenance: **Implemented**.

## App pipeline — which invoices are exported

### BR-009 — The header procedure decides the invoice set

The client exports every row `SUN_GETINVOICES` returns for the two dates. It does not filter `TrCode` or account in VB before the loop. Account filtering happens only when choosing a path (BR-010).

Provenance: **Implemented** on the client. The server filter is **Unknown**.

### BR-010 — Only two account codes have a destination

| AccountLink | Code comment | Folder under `XMLPath` | FTP directory |
|---|---|---|---|
| `5261` | Checkers | `SHOPRITE` | `/ShopriteInvoices/` |
| `6960` | PNP | `PNP` | `/Invoices/` |

Any other `AccountLink` leaves the file path and the FTP path empty, and the loop still attempts to write.

Provenance: **Implemented**. The names Checkers and PNP are comments. Confirming them as current customers is **Requires Confirmation**.

### BR-011 — One file per header `Reference`

The file name is `Reference` plus `.csv`. Detail rows are loaded with `SUN_GETINVOICEDETAILS` for that reference. The client assumes the detail table has columns 0 through 22.

Provenance: **Implemented**. Column meanings are **Unknown / Requires Confirmation**.

### BR-012 — Files are appended, then removed after a good upload

The writer opens the path in append mode. After a successful FTP PUT, the local file is deleted. A failed PUT keeps the file. A later success appends to that leftover and uploads the combined contents.

The on-screen word "archives" is not a retention rule. The success path deletes the local copy.

Provenance: **Implemented**.

### BR-013 — FTP destination and credentials ignore the Settings tab

The URL host is `xpliquor.dyndns.org`. The FTP account and password passed to `FtpWebRequest` are literals in `FTPFile`. Saved `FTPServer`, `FTPUsername`, and `FTPPassword` are not read there.

Provenance: **Implemented**. Password omitted.

### BR-014 — Database name is BSBC

Both connectors are created with database `BSBC` when the saved SQL server is non-empty at startup. The operator cannot type a database name. Changing the server name on the Settings tab does not retarget the open connection until the next launch.

Provenance: **Implemented**.

### BR-015 — SQL login is fixed

`SetConnectionString` writes a connection string that uses a compiled user id and password. The `UserName` and `Password` arguments are unused. The password is omitted here. The login id in source is `pnp`.

Provenance: **Implemented**.

## SQL script pipeline — GS1 invoice (not called by the app)

These rules apply only if something executes `SUN_SAGE2XML`. The WinForms program does not.

### BR-016 — Which transactions the script selects

`ALTER PROCEDURE [SUN_SAGE2XML]` selects distinct invoice XML where:

- `T.TxDate` is greater than or equal to `@EFFECTIVE_FROM` and less than or equal to `@EFFECTIVE_TO`
- `T.TrCode = 'INV'`
- `AccountLink = 6960`

Default parameter values in the script, if the caller omits them, are dates 6 Dec 2015 and 10 Dec 2015. The comparison is on `DATETIME`. A date-only upper bound at midnight excludes later times on the to-date. That consequence is **Inferred** from the comparison as written. The app does not pass these parameters, because it does not call this procedure.

Provenance: **Implemented** in the script.

Shoprite / account `5261` is not in this filter. The app pipeline has a path for `5261`. The script pipeline does not.

Provenance: **Implemented** difference. **Requires Confirmation** which pipeline is authoritative.

### BR-017 — Document identity

For each selected invoice the XML sets:

- Standard `EAN.UCC`, type `INVOICE`, type version `2.5`, header version `1.0`, `MultipleType` false
- `documentStatus` `COPY`
- `invoiceType` `INVOICE`
- Currency `ZAR`, country of supply `ZA`
- `InstanceIdentifier` literal `100002` on every document
- `uniqueCreatorIdentification` set from `T.Reference` for the message, transaction, command, and invoice
- Creation time `getdate()` in style 127; invoice `creationDateTime` from `T.TxDate` style 127
- Order identification from `ExtOrderNum`

Provenance: **Implemented**.

### BR-018 — Party GLNs

Seller, sender, and content-owner GLN are the literal `6009900156101`.

Receiver, ship-to, and buyer GLN are `CLIENT.Post5` with the prefix `GLN ` removed (`REPLACE(C.Post5,'GLN ','')`).

The client row is joined by `T.LinkAccCode` → `_bvARAccountsFull.DCLink` → `CLIENT.DCLink`.

Provenance: **Implemented**. That `6009900156101` is the seller's GLN is **Inferred** from the element names. Confirm the GLN and confirm that `Post5` still stores `GLN ` plus the buyer number.

### BR-019 — Seller VAT number is taken from CLIENT

The seller extension `vATRegistrationNumber` is `C.Tax_Number` from that same client row. There is no separate company-master read in the script.

Provenance: **Implemented**. If `CLIENT` is the customer, the seller VAT element carries the customer's tax number. That reading is **Inferred** from the join. **Requires Confirmation**.

### BR-020 — Line items

Lines come from `_bvSTTransactionsPivot` joined to `STKITEM` on `StockLink`, for `P.Reference = T.Reference`.

| XML element | Source in the script | Provenance |
|---|---|---|
| Line number | `ROW_NUMBER()` over `P.id` | **Implemented** |
| GTIN | `Bar_Code` | **Implemented** |
| Additional id | `Code`, type `SUPPLIER_ASSIGNED` | **Implemented** |
| Quantity | `P.Quantity`, unit `EA` | **Implemented** |
| Ownership date | `P.TxDate` style 127 | **Implemented** |
| Description | `LTRIM(RTRIM(S.Description_1))` | **Implemented** |
| Line amounts named inclusive | `ActualSalesValue` | **Implemented** |
| After-tax amount | `ActualSalesValue * 1.14` | **Implemented** |
| Tax amount | `(ActualSalesValue * 1.14) - ActualSalesValue` | **Implemented** |
| Tax type / category / rate | `VALUE_ADDED_TAX`, `STANDARD_RATE`, rate `14` | **Implemented** |
| Invoiced weight | empty value and empty unit | **Implemented** |

`ActualSalesValue` is not qualified with an alias in the script text. Which table supplies it is **Unknown** if more than one joined table has that column. **Requires Confirmation** against the live views.

### BR-021 — Invoice totals do not use the 14% line formula

Totals are:

- `totalInvoiceAmount` = `Outstanding + Tax_Amount`
- `totalLineAmountInclusiveAllowancesCharges` = `Outstanding`
- `totalTaxAmount` = `Tax_Amount`

Those names are unqualified in the same way as `ActualSalesValue`. Line tax is a hard 14% of `ActualSalesValue`. Header tax is whatever `Tax_Amount` is on the transaction. The script does not reconcile them.

Provenance: **Implemented**. Whether both figures are supposed to match is **Requires Confirmation**. South African VAT changed from 14% to 15% on 1 April 2018. The form subtitle is 25 June 2018. The script still emits rate `14`. That the rate is stale is **Inferred** from the date in the form and the literal in the SQL. **Requires Confirmation** before anyone treats 14% as current policy.

### BR-022 — SOAP credentials inside the document

The envelope is a SOAP header for namespace `http://pnpportal.co.za/ServiceEanUcc` plus the standard business document. `@USERNAME` and `@PASSWORD` have default literals. If a parameter is null, `ISNULL` substitutes further literals. Values are omitted.

Provenance: **Implemented** in the script. The desktop app does not send this envelope.

## Rules that look like defects

These are implemented behaviours. They are not promoted to approved policy.

- Upload Only shows a busy-query error and does nothing (BR-005).
- Settings FTP and SQL passwords are not the credentials PROCESS uses (BR-013, BR-015).
- Account codes other than `5261` and `6960` still reach the file writer with an empty path (BR-010).
- Append plus delete means a failed upload is duplicated into the next success (BR-012).
- SQL dates and invoice numbers are concatenated into command text (BR-008).
- The GS1 script's seller VAT element uses the joined `CLIENT` tax number (BR-019).
- Line VAT is 14% while header totals use `Tax_Amount` (BR-021).
- `InstanceIdentifier` is the constant `100002` for every GS1 document (BR-017).
- `documentStatus` is `COPY` (BR-017).
- The app exports CSV while the only SQL file in Git builds XML, and the procedures the app calls are missing from Git.

Provenance: **Implemented**.

## Human confirmation

- Which pipeline is the business rule: CSV to `xpliquor.dyndns.org`, GS1 XML from `SUN_SAGE2XML`, or both?
- Are accounts `5261` and `6960` still Checkers/Shoprite and Pick n Pay?
- Is GLN `6009900156101` still the seller?
- Is VAT 14% still acceptable on any document this script might generate?
- Should `CLIENT.Tax_Number` be the seller VAT registration?
- Should leftover CSVs be retained as archives or deleted only after upload, and should a rerun replace rather than append?
