# Business rules — por (Purchase Order Requisition)

Evidence date: 2026-10-06.

Source: `git@bitbucket.org:overdrive/por.git`, `master` at `6fabdc090d2a3693f369f9fe50dea47990109349` (2026-06-23).

No business-rules document exists in the source repository. Rules below are taken from PHP, SQL strings, `README.md`, and `CHANGELOG.md`. Anything marked **Inferred** is a conclusion from that evidence, not a written policy.

## Identity and access

### BR-001 — Sign-in is a directory login matched to a POR user

**Implemented.**

`loginUser` accepts the Windows account only when `mstUser.strDomainLogin` equals one of: the raw value, the name after a backslash, `DOMAIN\name` in upper and lower domain, or `RGBC\` / `rgbc\` plus a bare name. The user must join to `mstUserGroupPOR`.

The on-screen text says login is controlled through Active Directory. This code does not bind to LDAP.

Evidence: `include/system.functions.inc.php`, `login.php`.

### BR-002 — Internet Explorer 6 through 11 cannot sign in

**Implemented.**

A Trident or `MSIE` version from 6 to 11 clears the login action and shows a message to contact IT for Firefox or Chrome.

### BR-003 — A POR group is required

**Implemented.**

The login query is an inner join. A `mstUser` row with no `refUserGroupPORID` match does not sign in. Login debug text says to assign `refUserGroupPORID` when the user exists but the join returns nothing.

### BR-004 — Admin and Dev see imports and reports

**Implemented.**

`strUserGroup` must be exactly `Admin` or `Dev`. Other groups do not get those menu links. The pages themselves do not repeat this check after the menu. **Inferred:** opening the URL directly still runs the import if a session exists.

Evidence: `include/layout.menu.inc.php`.

### BR-005 — Special flag is a separate gate from Admin/Dev

**Implemented.**

`blnSpecial == 1` shows Settings and unlocks Close PO, the complete-balance button on Partially Receipted, re-receipt, and vendor-code editing. The forms' comments call this Dev, Admin, or Exec. That mapping is a comment, not a query.

### BR-006 — Dev login override replaces the Windows user

**Documented** and **Implemented.**

When `RGBC_POR_DEV_LOGIN_OVERRIDE` is set, that value is looked up instead of `LOGON_USER`. README says not to set it in production.

### BR-007 — The shell requires a session

**Implemented.**

`include/layout.full.inc.php` redirects to `login.php` when `$_SESSION['USER']` is missing. `ajax.functions.php` does not. See FR-002 in `prd.md`.

## Purchase-order approval

### BR-008 — Limits and authorisers come from user-group access

**Implemented.**

`viePOR_UGAccess` supplies `Lvl1_UpperLimit`, `Lvl2_UpperLimit`, `Lvl3_UpperLimit` and the auth tokens. The constant `$SALESMANAGERMAXVALUE` (20000) is unused.

A level applies only when the requisition total is less than or equal to that level's upper limit. Level 1 is tried first, then level 2, then level 3. The first success stops the chain.

Tokens `SELF`, `MAN`, and `ALT` are defined in `porUgManagerDirectAuthMatch` and `porUgTryLevel3`. Other tokens are emails.

### BR-009 — Who may edit a purchase order depends on status

**Implemented** in `requisitionForm.php` (button and field attributes). The save/approve methods do not repeat the same check.

| Status | Non-special user |
|---|---|
| Draft | Requester, manager, exec, group 23, or group 29 may edit. Approve control stays disabled |
| Pending | Requester may edit except Approve. Manager or exec may approve |
| Approved, Completed, Cancelled | Editing disabled |
| Declined, Partially Receipted | Complete-balance button for manager or exec only |

Special users follow a shorter switch: Close PO on Approved and Partially Receipted; complete-balance on Partially Receipted; edit disabled when Completed.

### BR-010 — Submit and approve require a known GL combination

**Implemented.**

Full GL is account, hyphen, region, then optional brand, key account, PE, and TME segments. It must equal `mstGLCode.FullGLCode`. Otherwise the item is listed (on submit) and status does not change. The warning text says the combination must exist in Accpac.

An Approved or Declined PO cannot be submitted again (`checkStatus`).

### BR-011 — PO number sequence

**Implemented.**

`strPONumber` is `PORGB` plus `intPONumber` zero-padded to 10 characters. `intPONumber` is `MAX(intPONumber)+1`. Copy uses the same rule. A 2019 comment says this replaced an autonumber.

### BR-012 — Cancel withdraws the PO and its receipts

**Implemented.**

Both `porRequisition` and related `porReceipt` rows become `Cancelled`. Existing `sysCronJob` rows for `RequisitionID={id}` are set to status `Removed`. A new `POR_2CancelledRequisition` job is inserted when the id is greater than 0.

### BR-013 — Close and "complete balance" are different outcomes

**Implemented.**

Close PO writes `Completed` and does not insert a cron row.

The Completed button writes `Complete balance no longer applicable` and inserts `POR_CompletedRequisition`. Its return text says "Partially Completed PO set to Completed".

### BR-014 — ESN-named vendors are not offered on a purchase order

**Implemented.**

Vendor dropdown and vendor XML search exclude `strVendor LIKE 'ESN%'`, while still allowing the vendor already stored on the requisition.

### BR-015 — New purchase orders start on payment term 1

**Implemented.**

`refPaymentTermID = 1` on a new form. A comment says Standard. The label stored in `mstPaymentTerms` is **Unknown** from this repository alone.

### BR-016 — Bill-to customers are promotional accounts

**Implemented.**

`ajaxLoadRegionBillToAccountsXML` requires `blnActive = 1`, matching `strRegionDesc2`, and `blnPromotionalAccounts = 1`, or an exact customer number already chosen. Ship-to search (`ajaxLoadRegionShipToAccountsXML` and `ajaxSearchShipToAccountXML`) does not require `blnPromotionalAccounts`.

## Receipts

### BR-017 — Receipts are only for approved, partially receipted, or completed POs

**Implemented.**

List and receipt queries filter `porRequisition.strStatus` to those three values. Draft, Pending, Declined, and Cancelled POs are not receipt targets. Cancelled is also blocked on the form, with the cancelled-PO warning.

### BR-018 — Only one draft receipt at a time

**Implemented.**

`hasOpenRCP` is true when another `porReceipt` for the requisition has status `New`. Save then returns the open-RCP warning and does not insert.

### BR-019 — Invoice number is required to submit a receipt

**Implemented.**

Trimmed empty `strInvoiceNumber` blocks submit.

### BR-020 — Receipt quantity cannot exceed the PO line

**Implemented.**

`jsCheckReceiptItemQty` allows the post when already-receipted quantity (all other receipts, any status) plus the entered quantity is less than or equal to `porItem.intQuantity`.

**Unknown:** whether the form blocks save when the AJAX check returns 0, or only warns. The server `Save` method writes posted quantities without repeating this check.

### BR-021 — Fiscal period can move the receipt date

**Implemented.**

If `POR_FISCAL_PERIOD`.`memQuery` parses as a date and the receipt date is later, the stored receipt date becomes that period end (date plus current time) and the user sees the fiscal-period warning. A receipt date before the PO date blocks non-special editing under the synthetic status `FISCAL PERIOD LOCKED`.

### BR-022 — Group 23 may receipt another user's PO

**Implemented.**

Comment: Accounts user group. The id is hardcoded. The group name in `mstUserGroupPOR` was not queried.

### BR-023 — Re-receipt is limited to special users

**Implemented.**

The control is hidden otherwise. The action sets cron type `POR_3Receipted` for that `ReceiptID` back to `New`. It does not reopen the receipt row.

## Vendors

### BR-024 — New vendor records start as Draft

**Implemented.**

`blnActive` is set to 1 on insert. `strMailStatus` is `Mail Pending`. Number format is `VEN` plus five digits.

### BR-025 — Vendor workflow statuses are a fixed set

**Implemented** transitions are the action table in `prd.md` WF-6. No other status strings are written by `vendors.cls.php`.

### BR-026 — Some vendor actions are limited to fixed user ids

**Implemented.**

When `blnSpecial` is not 1:

- After Approved, extra edit is tied to user ids `189` and `154` (or special, which cannot be true in this branch).
- Complete is editable for user ids `11` and `1278`.
- Finance Approved enables vendor code for user ids `154` and `189`.
- Pending treats the session user as the manager only when `UserID` equals that same row's `refManagerID`.

A code comment names people for ids 154 and 189. Those names are **not confirmed** here.

**Requires confirmation:** whether these ids are still the intended operators.

### BR-027 — PO vendors and onboarding vendors are different tables

**Implemented** reads.

Purchase orders select `mstVendor`. Onboarding writes `porVendor`. This repository does not copy one into the other.

**Inferred:** `POR_VendorDone` or another cron type is how finance creates the accounting vendor. **Requires confirmation.**

## Imports, reports, and settings

### BR-028 — Import replaces the entire allocation table

**Implemented.**

User-group access import truncates `porUGAccess` and inserts from `stgUGAccess`. GL import does the same for `porUGGL`. There is no merge.

### BR-029 — Settings are any query row named like POR

**Implemented.**

The grid is `sysQuery` where `strName LIKE 'POR%'`. Save overwrites `memQuery`. `POR_FISCAL_PERIOD` is one such row and controls receipt dating.

Who may open the page: `blnSpecial == 1` on the menu. The page does not re-check the flag before `Setting::save`.

## Queue and notifications

### BR-030 — Status changes queue a job instead of calling the accounting system

**Implemented.**

Cron types written by this app:

`POR_1NewRequisition`, `POR_2ApprovedRequisition`, `POR_2DeclinedRequisition`, `POR_2CancelledRequisition`, `POR_CompletedRequisition`, `POR_3Receipted`, `POR_NewVendor`, `POR_ApprovedVendor`, `POR_VendorDeclined`, `POR_FinanceApproved`, `POR_VendorCompleted`, `POR_VendorInCompleted`, `POR_Activate`, `POR_VendorDone`.

Param is `RequisitionID={id}`, `ReceiptID={id}`, or `VendorID={id}`.

No code in this repository selects `sysCronJob` in order to process it.

### BR-031 — There is no in-app mail send

**Implemented** absence.

Vendor transitions set `strMailStatus` to `Mail Pending`. Purchase-order approval does not set a mail status column in the statements read for this document. **Unknown** which component sends mail.

## Data rules that are easy to miss

### BR-032 — List totals are rounded for display

**Implemented.**

List pages format money with `number_format(..., 0)` and a note "Rounded Values". Stored `dblTotal` on lines is quantity times cost. Receipt line totals use `round(..., 2)`.

### BR-033 — Ignore-dates without another filter is refused

**Implemented.**

The list shows "Slow search avoided" and asks for a date range, status, region, vendor, brand, requested by, manager, or search.

### BR-034 — Time zone

**Implemented.**

`Africa/Johannesburg` in `include/system.inc.php`.
