# PRD — glob-1 (Global Welness Centre)

Evidence date: 2026-10-13.

Source: `git@bitbucket.org:overdrive/glob-1.git`, `main` at `f26707a` (2026-04-14).

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/glob-1/` before this file. Requirements below are recovered from the tenant web fork, the business layer, configuration, and the VAT policy type. They are not a new product decision.

The platform calls itself Palette in `core_custom/GLOB-1/Core.master` (title "Palette") and Evolution Software Framework in assembly metadata. The practice-facing brand in config is Global Welness Centre (GLOB-1). See `project-plan.md` for repository scope and the spelling note.

## Product purpose

GLOB-1 is the Palette tenant used by Global Welness Centre (GWC in code comments) to:

- publish a public site (default page, products, cart, search, media, canvas);
- register patients and sign users in;
- capture patient records on configurable surface applications, including a PatientProfile surface;
- run a calendar whose subject and tooltip are specialised for this tenant family;
- post medical billing lines, produce statements and invoices, and default the tariff to Private;
- sell and invoice through the sales module, with a VAT cut-over from 2026-04-01;
- administer users, campaigns, content, and medical reference data (practices, doctors, schemes, tariffs, ICD-10, procedures, symptoms, conditions, trauma).

**Implemented** across `core_custom/GLOB-1`, `business/`, and `tools/scheduler`. Which framework modules are turned on in the live database is **Unknown / Requires Confirmation**. The module catalogue is the enum `pNums.Module` in `business/enums/pNums.cs` (Content through Purchases, ids 1–74). The admin menu is built from database rows, not from a static menu in git.

## Users and roles

Roles are `pNums.UserType` in `business/enums/pNums.cs`.

| Actor | Enum | What the code does | Provenance |
|---|---|---|---|
| Public visitor | none until a session user exists | Can open pages that use `Core.master` (`Default.aspx`, `Products.aspx`, `Cart.aspx`, `Search.aspx`, `Media.aspx`, `Reset.aspx`, `Unsubscribe.aspx`). | **Implemented** |
| Website user | `WebsiteUser = 1` | On GLOB-1 and MEDI-1, an inactive website user is refused login ("need to verify first", comment dated 2017-08-08). On finish of a surface save, a website user on GLOB-1, GLOB-2, or MEDI-1 is sent a registration email. | **Implemented** in `Core.master.cs` and `controls/surface/surface_form.ascx.cs` |
| Power user | `PowerUser = 2` | Surface edit thresholds. GLOB-1 patient-registration mail is addressed to a Power User whose name equals the site and whose surname is "Admin". | **Implemented** |
| Super user | `SuperUser = 3` | Named in the enum. No separate GLOB-1 screen was found. | **Implemented** as an enum value. Assignment in data is **Unknown** |
| Admin user | `AdminUser = 4` | Named in the enum. Admin pages exist (`Admin.aspx`, `Administration.master`). The exact permission check lives in the shared handler. | **Implemented** pages. The full permission matrix is **Inferred** from the shared framework |
| Custom user | `CustomUser = 5` | Numeric value is greater than Power User. The EVOL-1 login bypass uses `userType > PowerUser`, so a Custom User with `customerCode` EVOL-1 also matches. Whether that is intended is **Unknown / Requires Confirmation** | **Implemented** comparison in `Core.master.cs` |
| Evolution operator | `customerCode` `EVOL-1` and `userType` greater than Power User | May sign in even when `customerCode` is not the tenant code. | **Implemented** |
| Tenant user | `customerCode` equals `setup.code` | Password check must also match the tenant code, unless the EVOL-1 bypass applies. | **Implemented** |
| Patient at reception | not a separate enum | Controls `medicalPatientSignin.ascx` and `medicalPatientQue.ascx` exist. A PatientProfile status button writes `oMedicalPatientVisitLog`. | **Implemented** controls. Whether reception uses them in production is **Unknown** |

Login stores the user in session and updates `dateLoggedOn`. Session timeout is 60 minutes (`Web.config`). Anonymous identification is enabled.

Passwords are not stored as hashes in this login path. `Core.master.cs` encrypts the typed password with a hardcoded key and compares it to `pal_User.password`. The key value is not repeated here.

## Major workflows

### Sign in

Trigger: the login controls on `Core.master`.

Inputs: email, password.

Behaviour:

1. Encrypt the password and load `pal_` users by email and password.
2. Accept the user when `customerCode` equals the setup code, or when `customerCode` is `EVOL-1` and `userType` is greater than Power User.
3. On GLOB-1 and MEDI-1, if the user is a Website User and `isActive` is false, reject the login.
4. Otherwise update `dateLoggedOn`, put the user in session, and continue.

Output: a session user, or a failed login. The inactive-user message text was not read beyond the comment. Exact UI copy is **Unknown**.

Error: exceptions redirect to `/error` in the surface and enquiry controls. The login failure path was not fully traced. **Unknown** beyond "userValid stays false".

### Patient profile and ideal x-rays

Trigger: save of a new surface item on an application named `PatientProfile`, when setup code is GLOB-1, GLOB-2, or MEDI-1.

Behaviour (`GLOB1_InsertIdealXrays` in `surface_form.ascx.cs`):

1. Load the surface named `XRays`.
2. Read one item whose `ParentSurfaceItemId` is 0 (the master copy).
3. Create a new surface item for the patient and copy image field values from that master row. The parent id on the copy is the new patient item.

When an image value on PatientProfile does not start with the current item id, the edit link is disabled. The comment (2017-10-24) calls this the master x-ray.

The surface names `PatientProfile` and `XRays` are not defined in git. If the database has no such surfaces, these branches do nothing useful. **Unknown / Requires Confirmation** that live data uses those names.

### Welgemoed patient numbers

Trigger: a controlled unique number field is saved, setup code is GLOB-1, and the site value is the string `Welgemoed`.

Behaviour: the next number is 50001 when that site has no rows, otherwise one more than the highest `PatientInformation_PatientDetails_PatientNumber` for `PatientType_PatientType_Site='Welgemoed'`. This branch does not write the new counter back to `oSurfaceField.controlledValue`. Other sites increment `controlledValue` and save the field.

**Implemented.** The site list (which practices exist besides Welgemoed) is data, not code. **Unknown.**

### Registration email

Two sends exist.

**To the new user** (`SendRegistrationEmail` on `Core.master.cs`): picks an outbound template whose name contains "registration". A comment says the patient must not receive the template named "Patient Registration Notification". The comparison lowercases only the stored name, then compares it to the mixed-case literal, so the inequality does not exclude that template. **Implemented** behaviour does not match the comment. **Requires Confirmation** whether the wrong template is sent in production.

**To the practice** (`surface_form.ascx.cs`, website user, finish, codes GLOB-1, GLOB-2, or MEDI-1): loads the template whose lowercased name is `patient registration notification`, then for GLOB-1 and MEDI-1 sets the subject to `New Patient Registration: {first} {surname}` and addresses it to a Power User with name equal to the site and surname `Admin`. If none, the `admin` appSetting is used. An optional `registration` appSetting is copied to CC. That key is not in the committed GLOB-1 `Web.config`.

SHOU-1 website users take a different branch (email must be non-empty; GLOB does not check the email string in that condition, it checks `finish`).

### Calendar

On GLOB-1, GLOB-2, and MEDI-1, the appointment subject becomes users, then the existing subject, then the event type, in the form described by the 2017-11-30 comment: surname and first name, then subject, then type. The tooltip adds patient number and patient name from the linked surface when the calendar module is Surface.

**Implemented** in `calendar.ascx.cs` and `calendarEventToolTip.ascx.cs`.

### Medical billing

Trigger: enquiry billing on a patient account (`controls/module/enquiry.ascx.cs`).

Behaviour that is specific to this tenant family:

- Default selected tariff plan is the item whose value is `PRIV`, when that item exists (GLOB-1, GLOB-2, MEDI-1). Other tenants take the plan from the patient's medical scheme.
- Default statement email body, for GLOB-1 and GLOB-2 only: a fixed sentence telling the patient not to submit the attached invoice to medical aid, and to request a stamped "PAID BY PATIENT" invoice from the front desk.
- Account lines sort by `dateOfService DESC, sequence`. The comment cites GWC-71 (2019-06-18). The sort is not wrapped in a setup-code check in this fork, so every account opened on the GLOB-1 site uses it.
- VAT rate stored on the account line is `InvoiceVatPolicy.EffectiveMedicalInvoiceVatRate(dateOfService, practiceVatRate)`. From 2026-04-01 that rate is 0. Before that date the practice rate is kept. See FR-001.

Statements and invoices for GLOB-1 and GLOB-2:

- The place-of-service column prints the surface column `PatientType_PatientTypeandPracticeLocation_Site` instead of `placeOfService`.
- The main member name is merged from surface field `PatientInformation_MedicalAidifapplicable_MainMembersName` because, per the 2018-07-13 comment, it is not saved on `pal_Account` for GWC.
- The account holder (title, names, id, postal address, email) and the patient demographics are read from the "person responsible for account payment" and patient-detail surface columns. Dependant code uses column `PatientInformation_MedicalAidifapplicable_DependentCode` (comment GWC-30). The non-GLOB path uses `DependantCode`. Injury-on-duty fields are assigned on the general path and left commented out on the GLOB/MEDI path.

**Implemented** in `business/classes/xDebtors.cs` and the enquiry control. Column names must exist on the surface query. If they do not, the code uses empty strings. Live column names are **Unknown / Requires Confirmation**.

### Sales tax invoices

Trigger: a sales transaction of item type `TI` (tax invoice). Quotes (`QT`) are unchanged.

From 2026-04-01 (server local date), `sales.ascx.cs` stores `vatRate` 0 on the sales lines and skips the exclusive/VAT split. Before that date, the split is shown only when the company setup is VAT registered and `vatRate` is greater than 0.

**Implemented.** There is no feature flag. The date is a constant.

### Copy patient onto the account holder

Button id `PatientInformation_PersonResponsibleforAccountPayment_CopyFromPatient` copies title, names, id, date of birth, phones, email, and postal and physical address lines from the patient fields onto the person-responsible fields.

**Implemented** in `surface_form.ascx.cs`. The button exists only if the surface definition includes that field id. **Inferred** that the live PatientProfile surface defines it.

### Visit status

Button id `PatientProfile_PatientStatus`:

- Text `Status: Pending` closes any open visit logs for the patient (sets `signOutDate` to now), inserts a new `oMedicalPatientVisitLog` with `signInDate` now, and sets the button to `Status: In Progress`.
- Any other text signs out all logs for that patient and sets the button back to `Status: Pending`.

**Implemented.**

## Functional requirements

### FR-001 — Zero VAT on tax invoices from 1 April 2026

Requirement: A medical account line and a sales tax invoice (`TI`) whose service or transaction date is on or after 2026-04-01 stores VAT rate 0. Quotes are not forced to 0. Dates before the cut-over keep the practice VAT rate. MediSwitch remarks state that line amounts come from the persisted account row, which carries that rate.

Who: a user posting billing or a sales tax invoice.

Trigger: save of an enquiry account line, or save of sales transactions of type `TI`.

Inputs: invoice or service date, practice `vatRate`, item type.

Expected behaviour: `InvoiceVatPolicy.EffectiveMedicalInvoiceVatRate` and `IsZeroVatInvoiceDate`. Sales UI hides the VAT split for those tax invoices.

Outputs: `oAccount.vatRate` or `oSales.vatRate` of 0, or the practice rate when the date is earlier.

Validation: date comparison uses the date portion only (`invoiceDate.Date`).

Error conditions: none specific. A missing practice rate is the caller's problem; the policy returns 0 whenever the date has passed, even if the practice rate is already 0.

Dependencies: `App_Code/InvoiceVatPolicy.cs`, `controls/module/enquiry.ascx.cs`, `controls/module/sales.ascx.cs`, `App_Code/MediSwitch.cs`.

Implementation: **Implemented** on the GLOB-1 site fork only. `core` and `core-dev` were not found to reference `InvoiceVatPolicy`.

Confidence: High for the rule in code. Whether the business approved 1 April 2026 as the cut-over is **Requires Confirmation**. The commit message records the work; no separate requirements document does.

### FR-002 — Private tariff default

Requirement: On GLOB-1, GLOB-2, and MEDI-1, the billing tariff dropdown selects value `PRIV` when that list item exists. Otherwise the scheme's `planCode` is used.

Implementation: **Implemented** in `enquiry.ascx.cs`.

Confidence: High that the code does this. Whether a `PRIV` tariff plan exists in `Framework_GLOB-1` is **Unknown**.

### FR-003 — Website users must be active

Requirement: A Website User on setup code GLOB-1 or MEDI-1 cannot sign in when `isActive` is false.

Implementation: **Implemented**. GLOB-2 is not included in this check.

Confidence: High.

### FR-004 — Tenant isolation on login

Requirement: A user signs in only when their `customerCode` matches the site setup code, except EVOL-1 users whose type is above Power User.

Implementation: **Implemented**.

Confidence: High for the comparison. Whether Custom User should qualify is **Requires Confirmation**.

### FR-005 — Statements show the site, not the place-of-service code

Requirement: For GLOB-1 and GLOB-2, the statement line's place column is the patient site surface value, not `oAccount.placeOfService`.

Implementation: **Implemented** in `xDebtors.cs`.

Confidence: High.

### FR-006 — New patient notifies the site admin

Requirement: When a website user finishes a GLOB-1, GLOB-2, or MEDI-1 surface, the practice is emailed. For GLOB-1 and MEDI-1 the recipient is the Power User named `{site} Admin`.

Implementation: **Implemented**, dependent on template and user rows in the database.

Confidence: Medium. Template and user match are data.

## Integrations

| System | Purpose | Direction | Trigger | Auth as committed | Failure |
|---|---|---|---|---|---|
| SQL Server `Framework_GLOB-1` | Tenant data | App reads and writes | Every page that uses the data handler | Integrated security to localhost | Not traced beyond exception handlers that redirect to `/error` |
| SQL Server `Framework_EVOL-1` | WCF service catalog; login also reads `pal_` users | Service, and login | Service calls; sign-in | Integrated security to localhost on `service/Web.config` | **Unknown** |
| WCF `framework_service.svc` | `ProcessInteraction`, `GetVersion`, `SaveVersion` | Site is the client | Data and version file calls | Binding security mode None. Active endpoint `http://localhost:58913/framework_service.svc`. Commented alternative `http://service.mypalette.co.za/framework_service.svc` | Timeouts of 5–10 minutes are configured. User-visible failure is **Unknown** |
| MediSwitch | Medical-aid switching | Outbound SOAP | Enquiry and scheduler `HandleMediswitch` | Gateway URLs only. Active URLs are the QA hosts `wsgateway.mediswitch.co.za`, `wsgateway1`, `wsgateway2` under `/qa/`. Live URLs are commented. `MediSwitchResponses` is `0` | **Unknown** |
| Netgen SMS | SMS | Outbound SOAP `SMS_V2.asmx` at `sms.netgen.co.za` | When the app sends SMS | Username and password appSettings. Values not copied here | **Unknown** |
| SMTP `smtp.evolutionsoftware.co.za` port 25 | Mail | Outbound | Registration, statements, campaigns | Username and password in `mailSettings`. Values not copied here | **Unknown** |
| Google Sheets | Scheduler import when the first argument contains `googlesheetimport` | Pull | Manual or scheduled console args | `tools/scheduler/GoogleSheetAPISecretes.json` and `business/client_secrets.json` are committed. Contents were not copied | Log retention 14 days for that import |
| Country web service `webservicex.net` | Country, currency, GMT | Outbound HTTP | Only if used by shared framework code. `getCountriesFromWebService` is `0` on the GLOB-1 site | None in config | **Unknown**. The host is a third party and may be defunct |
| reCAPTCHA | Present on GLOB-3 `Web.config` only | Outbound verify | GLOB-3 config. Not in the GLOB-1 `Web.config` | Site and secret keys are committed on GLOB-3. Values not copied | **Unknown** |

## Constraints

- Culture appSetting `defaultCulture` is `en-ZA` (the key is duplicated). `system.web` globalization is `uiCulture="en"` and `culture="en-US"`. Which culture a given page uses is **Requires Confirmation**.
- Date format appSetting is `yyyy-MM-dd hh:mm tt`. Billing screens also format dates as `dd/MM/yyyy`.
- Upload limit `maxRequestLength` is 10048 (kilobytes in ASP.NET).
- JSON max length for script services is 50000000.
- WCF client security mode is None.
- `compilation debug="true"` is committed on the GLOB-1 site.
- No GLOB-1 SQL scripts are in `data/Scripts`. Schema is whatever the database already has. **Unknown** from git alone.
- Module availability is data-driven.

## Assumptions (not requirements)

- **Inferred:** GWC in comments means Global Welness Centre. The config brand string supports that. It does not prove a registered company name.
- **Inferred:** `core_custom/GLOB-1` is the site that would be deployed for this product, because `GLOB-1.sln` includes that folder and not `core`.
- **Inferred:** `PRIV` means a private tariff. The code only selects that code. It does not define the plan.

## Planned

No planned work is written in the repository. The VAT cut-over date is in the past relative to this evidence date (2026-10-13), so from this date forward the zero-VAT branch is the one that runs for new invoices. That is **Implemented** behaviour, not a future flag.
