# Business rules — sabullion (SA Bullion)

Evidence date: 2026-09-28. Source: `master` `a5cd347`.

Rules are quoted from code or from copy that ships in the repository. **Inferred** means the behaviour is visible in code but no comment or page states it as a rule. Where marketing copy, the mandate template, and the calculator disagree, both sides are recorded. This file does not pick a winner.

## Facilities

**BR-001 — Two facilities.** The public site offers BullionGold and HasanaGold. HasanaGold is described as Halaal. **Documented** in `content/home-content.txt` and `content/baraka-pricing-feat.txt`. **Implemented** as parallel directories under `investment/` and `savings/`.

**BR-002 — What the client owns.** About copy says every ounce is stored in the client's name, insured, and leaves the vault only on the client's instruction. **Documented** in `content/about-story.txt`. No vault-release workflow was found in code.

**BR-003 — Where coins are stored.** Pricing FAQ says Krugerrands are vaulted under the client's title at Rand Refinery Limited in Germiston. **Documented** in `content/bullion-pricing-feat.txt`. No Rand Refinery integration was found.

## Contributions

**BR-004 — Lump sum or debit order.** Both facilities describe a lump sum and a monthly debit order. The debit order accumulates cash until it can buy a coin. **Documented** in `content/bullion-pricing-feat.txt`.

**BR-005 — Recommended minimum.** Pricing pages say "Recommended minimum investment of R25,000." **Documented**. No `25000` check was found in PHP. Treat the figure as copy, not as an enforced gate, until confirmed.

**BR-006 — Debit order minimum and dates.** Pricing FAQ says South African clients may debit on the 1st and the 15th, minimum R500 per month, and that EFT deposits have no minimum. Cash deposits are allowed at any Nedbank branch, and the copy says the bank charges for cash deposits. **Documented** in `content/bullion-pricing-feat.txt`. Enforcement in the signup API was not found.

**BR-007 — Annual fee payment choice.** Application payment steps offer "pay by debit order" or paying the annual fee from the cash balance in the facility. **Implemented** in signup templates and in the PDF wording inside `webadmin/includes/registration.cls.php`.

## Fees — three sources, they do not match

**BR-008 — Marketing fee card.** BullionGold and HasanaGold pricing content both say: 1% initial fee on cash contributions, Krugerrands at a 3% premium over the gold price, facility fee 2% per annum for vaulting, insurance, and administration. Fees are negotiable above R1 million. Adviser initial fee up to 3.5% of the investment, agreed between client and adviser. All of those marketing fees are stated as VAT exclusive. **Documented** in `content/bullion-pricing-feat.txt` and `content/baraka-pricing-feat.txt`.

**BR-009 — Mandate fee card.** The client mandate template says SA Bullion I.S. charges 1.00% excluding VAT on each new cash contribution, an adviser may charge a maximum advice fee of 3.50% excluding VAT, and SA Bullion charges an annual administration fee of 1.50% excluding VAT. The dashboard mandate text says the administration fee is "as detailed in section 12" and that the adviser receives a percentage of it. **Documented** in `templates/all/client-mandate.php` and `dashboard/templates/all/mandate.php`.

**BR-010 — Calculator fee card.** **Implemented** in `dashboard/api/calc/index.php`:

- Initial fee is `amount / 100 * 1` (1%), hardcoded.
- Krugerrand premium is `ROUND(dblPremium * 100, 2)` from `tblProductPricing` where `ProductID = 1850`. The previous constant `3` is commented out (comment dated 2023-11-07, "Get Krugerrand Premium from Online Shop").
- Annual fee starts at `1.5` and adds `perc_annual_fee` when that query parameter is present, otherwise the add-on is 0.
- Adviser initial fee is `perc_initial_fee` percent of the amount when the parameter is present.
- Adviser VAT is that fee times `SystemSettings["VAT"]`.

**Discrepancy:** marketing says 2% per annum and a 3% premium. The mandate and the calculator use 1.50% administration. The premium is a database column, not the constant 3. **Requires Confirmation** which figure is charged.

**BR-011 — Adviser fee cap in the form.** Signup bank steps build the fee dropdown with `for ($i = 0; $i <= 3.5; $i += 0.5)`. **Implemented**. This matches the 3.50% cap in the mandate. It does not prove the API rejects a posted value above 3.5.

**BR-012 — Minted bar premium.** `dashboard/api/calc/minted-bar-index.php` sets refining and minting premium to 2.5 in the lump-sum path (comment 2019-06-11) and to 3 in the monthly debit history path. The price uses `gold_price * 3.215`, which is the gram-to-ounce factor for a 100 g bar. **Implemented**. Marketing pages reviewed for this pass describe Krugerrand premiums, not this 2.5% bar premium.

## VAT

**BR-013 — VAT rate comes from the database.** `config/site.php` sets `$SystemSettings["VAT"]` from `sysVat.dblVat`. **Implemented**.

**BR-014 — Historical rate change.** A comment dated 2018-03-26 says VAT changed from 14% to 15% on 1 April 2018. **Documented** as a comment. The value used at runtime is BR-013.

**BR-015 — Mandate says fees exclude VAT.** **Documented** in the mandate template. The calculator adds VAT on the adviser fee using the system setting. How VAT is applied to the 1% initial fee is a separate formula in `dashboard/api/calc/index.php` (`amount / 100 * (1 + VAT) - initial fee` in one branch, and `'0'` in another). **Requires Confirmation** which branch runs for a normal quote.

## Dealing cut-off

**BR-016 — 13:00 cut-off.** The mandate says the business cut-off for an instruction is 13:00. Funds must reflect in the SA Bullion I.S. bank account and documents plus proof of deposit must be received. An instruction after 13:00 is processed the next business day, excluding weekends and public holidays. Purchases of one-ounce Krugerrands happen at the earliest dealing opportunity. Acceptance of an application is at SA Bullion I.S.'s discretion. **Documented** in `templates/all/client-mandate.php`. No clock check was found in the signup API during this pass, so enforcement is **Unknown**.

**BR-017 — Confirmations.** The same mandate says SA Bullion I.S. sends a welcome letter after processing, and a transaction advice after every purchase or redemption. **Documented**. Email code exists (`webadmin/email.php`, signup email endpoints). Which template is the welcome letter was not matched to this sentence.

## Registration and FICA-style documents

**BR-018 — Three documents.** Bank, identity (ID or passport), and residential address. **Implemented** as `DocumentBank`, `DocumentPassport`, `DocumentResidentialAddress`.

**BR-019 — Document age.** Reminder email copy says bank proof and address proof must be less than 3 months old. **Documented** in `webadmin/cron.documents.reminder.php`. No date comparison against the file was found in the snippet that builds the email.

**BR-020 — All three must be approved.** `webadmin/registration.php` multiplies the three `blnApproved` flags and only then treats the set as approved. Status must also already be `Approved` for the transition that was read at that line. **Implemented**.

**BR-021 — Status values.** **Implemented** strings: `On hold`, `Cancelled`, `Approved`, `Active Client`, `Active Client - EFT outstanding`. The working registration list excludes `Active Client` and `Cancelled`. Cancelled applications have their own screen.

**BR-022 — Cancel.** If `blnCancelApplication` is checked and the row is not already `Cancelled`, status becomes `Cancelled`. **Implemented** in `registration.cls.php`.

**BR-023 — EFT outstanding.** Upload to the access database can set `Active Client - EFT outstanding` instead of `Active Client`. The outstanding-payment cron selects that status where `dblAmountOnceOff > 0`. **Implemented**.

**BR-024 — Unapproved portal users upload documents.** A session whose system is `UNAPPROVED-CLIENT` is shown the documents page only. **Implemented** in `dashboard/index.php`.

## Pricing data

**BR-025 — Spot source.** Gold and silver jobs scrape `https://www.sharenet.co.za/v3/spot.php` for rows such as "Gold R", "Silver $", and "USD / ZAR". **Implemented**.

**BR-026 — Silver product.** Product id `1846` is commented as the 1 ounce Silver Krugerrand. Its price is silver USD times USD/ZAR. **Implemented** in `cron.silver.price.update.php`.

**BR-027 — Shop prices are WordPress meta.** The gold cron writes `vat_value`, `_regular_price`, and `_price` on `wp_postmeta`. **Implemented**. This is shop behaviour, not the facility calculator.

**BR-028 — Ounce conversion.** Silver cron comment says ounce-to-grams is `SystemSettings["OunceToGrams"]` with the figure 31.103561 written beside it. **Implemented** as a settings lookup. The numeric fallback in the comment was not confirmed as the stored value.

## Portal and staff access

**BR-029 — Five portal modes.** `GLOBAL_ADMIN`, `HOUSE`, `IFA`, `CLIENT`, `UNAPPROVED-CLIENT`. **Implemented**.

**BR-030 — Staff groups are data.** A staff user must belong to an active `sysSecurityGroup`. Menu item labels are rows in `sysMenuLevel2`. **Implemented**. The actual group names are **Unknown** without the database.

**BR-031 — Staff can open the portal without logging in again.** If the referrer host path contains `/webadmin` and the global-admin login status is `Success`, `dashboard/login.php` does not destroy the session. **Implemented**.

**BR-032 — Audit rows.** The audit screen lists `sysAuditLog` with date, entity type, entity id, name, and revision. **Implemented** as a reader. Which saves write a row was not fully traced.

## Payments

**BR-033 — FNB success code.** `validateFNBPayment.php` treats FNB `responseCode` `000` as approved and sends the browser to `/checkout`. Any other code goes to `/cart` with the status message. **Implemented**.

**BR-034 — Those shop URLs are not in the PHP tree.** **Inferred** that checkout and cart belong to a WordPress shop (the `.wpress` backup or `development`). **Requires Confirmation**.

## Legal pages

**BR-035 — Shipped POPI, terms, privacy, and `content/client-mandate.txt` are not SA Bullion text.** They are a Harry's / HF Global template, including "at least 13 years of age" and a US class-action waiver. **Documented** by the file contents. **Discrepancy** against the SA Bullion I.S. mandate in `templates/all/client-mandate.php`. **Requires Confirmation** before anyone treats those content files as the firm's rules.

## File permissions (FIM)

**BR-036 — FIM normalises modes.** When it records a directory that is not `0755`, it chmods it to `0755`. When it records a file that is not `0644`, it chmods it to `0644`. **Implemented** in `fim_2.0/fim.cls.php`.

**BR-037 — FIM excludes some paths.** The default config wildcard list skips `.git`, `.old`, `.bak`, `error_log`, pdf, png, xls, xlsx, csv, `pdfs`, `cached-files`, and `sesFile.txt`, among others. **Implemented** in `fim_2.0/fim.config.php`.

## Rules that look unfinished

**BR-038 — Client balance cron is filtered to one id.** `cron.client.balance.php` documents a full refresh, then queries `web_client_detail` with `WHERE ID = 1436 LIMIT 100`. **Implemented** as that query. **Requires Confirmation** whether this is intentional.

**BR-039 — Gold sales class name.** `includes/gold.sales.cls.php` declares `class Appointment`. **Implemented**. Effect on gold-sales versus appointment behaviour **Requires Confirmation**.

## Not found

No code was found that encodes FSCA licensing, FICA as a named statute, POPIA consent storage, or a 13:00 dealing clock. Those topics are either absent or only present as the mismatched legal templates above.
