# PRD — ieb-inde-1 (IEB Exams)

Evidence date: 2026-10-13. Inspected revision: `main` at `9bf5ac3`.

Provenance labels: **Documented** (readme or config text), **Implemented** (code path), **Inferred** (reasonable reading of that code), **Unknown**.

The website root used below is `INDE-1/Evolution Software Unified Framework/Evolution Software Unified Framework/core_custom/INDE-1/`.

## Product purpose

`INDE-1/ReadME.txt` names **Exams = INDE-1**. **Documented.**

The implemented custom website keeps an examination subject, the people assigned to it, and the document packs those people upload and moderate. Status changes and collaboration notes send email whose subject starts with `IEB:`. **Implemented.**

The same website still contains the framework's admin, catalogue, cart, loyalty, medical, and sales pages. Those files are present. This PRD does not treat them as exam requirements unless a custom INDE-1 class calls them. `controls/module_custom/billingInstructions.ascx.cs` was not found on an exam save path. Whether a database canvas still opens it is **Unknown / Requires Confirmation**.

## Users and roles

`business/enums/pNums.cs` defines framework user types:

| Value | Name |
|---|---|
| 1 | WebsiteUser |
| 2 | PowerUser |
| 3 | SuperUser |
| 4 | AdminUser |
| 5 | CustomUser |

**Implemented.**

Exam work adds a second role, stored on the subject row, not in `pNums`. `examNums.SubjectResourceRole`:

| Value | Name | Stored on |
|---|---|---|
| 1 | Examiner | `SubjectOverview_SubjectResources_Examiner` |
| 2 | Reviewer | `SubjectOverview_SubjectResources_Reviewer` |
| 3 | InternalModerator | `SubjectOverview_SubjectResources_InternalModerator` |
| 4 | ExternalModerator | `SubjectOverview_SubjectResources_ExternalModerator` |
| 5 | AssessmentSpecialist | `SubjectOverview_SubjectResources_AssessmentSpecialist` |

`GetSubjectResourceRole` compares the signed-in user's `recId` to those fields in that order and returns the first match. If one person is stored in two fields, examiner wins. A user who matches none returns `0`. **Implemented.**

How the framework combines `userTypes`, `isRestricted`, and canvas security at page-render time was not fully traced. **Unknown / Requires Confirmation.**

## Major workflows

### 1. Open an examination subject

Trigger: populate of Examination Authoring and Moderation. Custom region dated 2018-02-06. `ExaminationAuthoringandModeration_form.ascx.cs` `PerformCustomPopulateAddOn`.

- Subject code is disabled. Document password is read-only.
- If the password text is empty, the form fills it with four random letters, a number from 0 to 999, and four random letters. A button copies that text to the clipboard.
- Once the surface item exists, year, type, level, paper, and subject name are disabled.
- If the signed-in user type is below SuperUser, the five resource dropdowns are disabled and `btnSave` and `btnSaveDropdown` are hidden.
- The status dropdown is enabled only when the signed-in user's `recId` equals the reviewer field. Otherwise it is disabled.
- A WebsiteUser also gets a startup script that adds CSS class `noShow` to a hardcoded internal-log "new" button id.
- The "new examination document pack" child button (`btnNewChild_3037_...`) is shown for examiner and reviewer, and hidden for assessment specialist, internal moderator, and external moderator. Role `0` does not change that button.

**Implemented.** Whether live reviewers are SuperUser or above — which is what makes the save buttons visible — is **Unknown / Requires Confirmation**.

### 2. Change subject status

Trigger: save of Examination Authoring and Moderation, inside the custom include dated 2018-02-08, before the generated assignment writes the status field.

The block runs only when all of these are true:

- The stored status differs from the selected status.
- The signed-in user is the reviewer on the subject.
- The surface item already has a `recId`.
- At least one active `publ_ExaminationDocumentPack` row exists for that item. The query orders by `DocumentPackInformation_Revision DESC` and uses the first row.

When it runs, it writes a note:

- External log (field `94`, title "External Moderation Log") for statuses EM compliant minor changes, EM compliant translation ready, EM moderation required, and EM not compliant.
- Internal log (field `93`, title "Internal Moderation Log") for every other status in the switch, including print ready and post-translation change required.

The note caption includes "Status update", the reviewer's name, the document pack, the revision, and the selected status text. `SaveCollaborationLog` stores the note and emails the reviewer. If that save returns true, the code sets the new status, sets `TimeSinceStatusChange` to now, calls `EmailExamStatus`, and refreshes controls.

After that block, the generated assignment always writes the selected status onto the object, including when the log block did not run. **Implemented.**

`EmailExamStatus` loads template name `Examination Moderation Status` of type outbound response, merges the recipient, and sends subject `IEB: {subject name} ({status display})` with sender label `System Notice :: IEB Exams`. BCC is the `bcc` app setting (empty in the checked-in config). From address is setup `communicationEmail`, not the `from` app setting. Link target is `{WebAddy}/pages/examinationauthoringandmoderation?mode=form&id={itemID}`.

| Status id | Enum name | Email to |
|---|---|---|
| 1093 | NoActivity | nobody |
| 1083 | ExaminerUploadedReviewReady | reviewer |
| 2160 | ReviewerQuery | examiner |
| 1084 | ReviewerUploadedIMReleaseReady | nobody |
| 1085 | IMModerationRequired | internal moderator |
| 1086 | IMNotCompliant | reviewer, then examiner (semicolon-joined) |
| 1087 | IMCompliantEMReleaseReady | reviewer, then examiner |
| 2168 | IMCompliantMinorChanges | reviewer, then examiner. Comment dated 2019-03-12, "IEB-121". |
| 1088 | EMModerationRequired | external moderator |
| 1106 | EMNotCompliant | reviewer, then examiner |
| 1107 | EMCompliantMinorChanges | reviewer, then examiner |
| 1108 | EMCompliantTranslationReady | reviewer, then examiner |
| 1109 | PostTranslationChangeRequired | nobody |
| 1110 | PrintReady | nobody |

The status block in the body lists subject name, status display, the latest active document pack and revision, the user who updated, and `TimeSinceStatusChange`. **Implemented.**

A commented line would have forced the recipient to `graham@evolutionsoftware.co.za`. It is not active.

### 3. Create a document pack

Trigger: populate of a document-pack form when the pack number is empty or the revision text is `0`. Present on:

- `ExaminationDocumentPack_form.ascx.cs`
- `ExaminationDocumentPack_ExamDocs_form.ascx.cs`
- `ExaminationDocumentPack_InternalMod_form.ascx.cs`
- `ExaminationDocumentPack_ExternalMod_form.ascx.cs`

Revision: if the revision text is `0`, count active `publ_ExaminationDocumentPack` rows for the parent surface item and add 1. The count is a concatenated SQL string.

Pack number, joined with hyphens:

`{year}-{type}-{level}-{paper}-{subject code}-Rev{revision}`

Type, level, and paper come from `oSurfaceLookup` for the parent subject.

On the Exam Documents, Internal Moderation, and External Moderation forms, a non-empty `customCode` is used. If `customCode` is empty, display text is mapped: "year-end" to `Y`, anything else to `S`; grades 12 down to 8 to `G12`…`G8`, otherwise `G0`; papers 1–6 to `P1`…`P6`, otherwise `P0`.

On `ExaminationDocumentPack_form.ascx.cs` the same display-text map is used and `customCode` is ignored.

If the surface item `recId` is 0, populate calls `PerformSave` immediately. **Implemented.**

Which of the four forms the live canvas loads is database data. **Unknown / Requires Confirmation.**

### 4. Mark examination documents uploaded

Trigger: button `ExaminationDocuments_ExaminationDocuments_DocumentUploadComplete` on the Exam Documents form. Custom region in `surfaceButton_Click`.

The button and the "new examination document" child button are visible only when the signed-in user is the examiner or the reviewer on the parent subject.

The click writes an internal collaboration log on the parent subject ("Examination documents uploaded" for the examiner, "Revised examination documents uploaded" for the reviewer) and sets:

| Actor | New status |
|---|---|
| Examiner | ExaminerUploadedReviewReady (1083) |
| Reviewer | ReviewerUploadedIMReleaseReady (1084) |

It sets `TimeSinceStatusChange`, updates the subject, calls `EmailExamStatus`, then `SaveCollaborationLog`. Status 1084 sends no status email. `SaveCollaborationLog` still emails the reviewer. **Implemented.**

### 5. Complete internal moderation

Trigger: save of the Internal Moderation form when overall compliance (`IM12ComplianceSummary`) is non-empty and differs from the stored value, and the signed-in user is the internal moderator.

The form writes an internal log titled "Internal moderation completed", with a criterion table (`ReturnComplianceStatusTable`, internal), the overall lookup display, and a colour class. On success it sets `IMDateModerationComplete` and refreshes the summary label. It copies the lookup display into `DocumentPackInformation_InternalModerationStatus`. It does not change the parent subject's status enum.

Populate enables editing only for the internal moderator (`SetState(true)`). Other roles, and role `0`, get `SetState(false)`. Save buttons are hidden. The summary is built for the user stored as internal moderator. **Implemented.**

### 6. Complete external moderation

Same shape as internal moderation, on the External Moderation form, for the external moderator, using the external log (field `94`), external criterion fields, `EMDateModerationComplete`, and `DocumentPackInformation_ExternalModerationStatus`. Subject status is not changed here. **Implemented.**

### 7. Post a collaboration note

Trigger: `surfaceNotes.ascx.cs` `HandleEmailLog`, when the note field is internal (`93`) or external (`94`).

Template name is `Examination Moderation Log`. Subject is `IEB: {subject name} - Log Update by {role name}`.

| Log | Poster | Recipients |
|---|---|---|
| Internal | Examiner | internal moderator |
| Internal | Internal moderator | examiner |
| Internal | Reviewer | examiner and internal moderator |
| Internal | Assessment specialist | examiner and internal moderator |
| External | Examiner | external moderator and internal moderator |
| External | Internal moderator | examiner and external moderator |
| External | External moderator | examiner and internal moderator |
| External | Reviewer | examiner, internal moderator, and external moderator |
| External | Assessment specialist | examiner, internal moderator, and external moderator |

Internal-log posts by the external moderator have no case, so that path sends nobody. External-log posts do not include a lone reviewer as a recipient when someone else posts. **Implemented.**

`SaveCollaborationLog`, used by the status-change and upload-complete paths, always emails the reviewer and labels the sender role "System Post". That is a second mail path, separate from the table above. **Implemented.**

### 8. Open or change an examination file

`controls/admin/attachments.ascx.cs`, custom regions dated 2018-02-10.

On a surface module, an assessment specialist sees the file icon and no link. Other roles get a link. Examiner and reviewer keep the new-attachment button. Assessment specialist, internal moderator, and external moderator have it hidden.

Edit and remove are hidden for assessment specialist, internal moderator, and external moderator. Remove is also hidden for every user below SuperUser, which covers those roles again and also examiner or reviewer below SuperUser.

When the session field name is `ExaminationDocuments_ExaminationDocumentDetails_Files`, the edit column is given CSS class `noShow`. **Implemented.**

Files are served from `~/upload/...`. `FileProtectionHandler` is registered for path `upload`. A GET that is not the site root and not `{WebAddy}/admin` redirects to admin when the session has no user. **Implemented.**

## Data the product keeps

Published prefix `publ_`. Configuration prefix `pal_`.

| Object | Table implied by the prefix | What it holds |
|---|---|---|
| `oExaminationAuthoringandModeration` | `publ_ExaminationAuthoringandModeration` | Subject year, type, level, paper, name, code, password, status, time since status change, five resource ids, summary labels. |
| `oExaminationDocumentPack` | `publ_ExaminationDocumentPack` | Pack number, revision, internal and external criterion scores, overall compliance, moderation dates, moderation status text. |
| `oSubjectManagement` | `publ_SubjectManagement` | Subject name, code, level. Looked up when a status email needs the subject name. |
| `oComplianceFeedbackCRITERION` | procedure result | Number, reason for non-compliance, recommendation. Loaded by `sp_INDE_GetCriterionFeedbackData`. |
| `oNote` | framework notes | Collaboration log title, caption, field 93 or 94. |
| `pal_Template` | templates | `Examination Moderation Log` and `Examination Moderation Status`. |

The procedure body is not in this repository. **Unknown / Requires Confirmation.**

Compliance per criterion (`examNums.ComplianceStatus`): no compliance `1125` (red), limited `1126` (red), most respects `1127` (orange), all respects `1128` (green).

Overall (`examNums.ComplianceStatusOverall`): not approved `1132` (red), conditionally approved and must be submitted again `1131` (red), conditionally approved and not to be submitted again `1130` (orange), approved `1129` (green). The words shown on screen come from `oSurfaceLookup.display`, not from the enum name. **Implemented.**

Feedback child surfaces hardcoded in the summary:

| Side | Criterion surfaces |
|---|---|
| Internal | 3062, 3063, 3065, 3066, 3067, 3068, 3069, 3070, 3071, 3072, 3073 |
| External | 3088, 3089, 3091, 3092, 3093, 3094, 3095, 3096, 3097, 3098, 3099 |

3064 and 3090 are not in those lists. What those surface ids contain in the database is **Unknown / Requires Confirmation**.

## Integrations

| System | Direction | Trigger | What moves |
|---|---|---|---|
| SQL Server | Outbound queries and writes | Every surface save and the exam helpers | Subject, pack, user, template, lookup, note rows. Active catalog `Framework_INDE-1_Demo`. |
| SMTP `smtp.assetonline.co.za` port 587 | Outbound | Status change, upload complete, moderation complete, collaboration note | HTML mail. From `ieb-exams@assetonline.co.za` in `mailSettings`. Password omitted. |
| File system `~/upload` | Read and write | Attachments | Examination files. Handler requires a session user. |

MediSwitch, SMS, and the country web services are in `Web.config`. No exam class that was read calls them. **Inferred** leftover framework configuration.

## Constraints

- Session timeout is 20 minutes. Anonymous identification is enabled. Compilation debug is true.
- `maxRequestLength` is 10048 KB.
- Culture `en-US`. App setting `defaultCulture` is `en-ZA`. Date format app setting is `yyyy-MM-dd hh:mm tt`.
- `WebAddy` is `http://localhost:27559`. Mail subject prefix app setting is `IEB:: Exams (INDE-1) - {content}`.
- Support, admin, and cc app settings are `maanie@overdrive.co.za`. The exam status mail uses the template and `communicationEmail` from setup, not those app settings, except where a path reads `bcc`.
- Resource picklists and the main save buttons on the subject form are limited to SuperUser and above.
- Status dropdown changes are limited to the reviewer, and the log-and-email side effect also requires an active document pack.

## Error behaviour

Exam helpers catch exceptions, call `exception.HandleException`, and return false or an empty string. Surface custom regions redirect to `/error`. A missing email template leaves the body empty and can still attempt a send when a recipient was found. A status with no recipient skips the send. **Implemented.**

## Requirements that are not confirmed

- Production URL, production catalog, and whether demo data is what operators use.
- The canvas and surface rows that choose which generated form loads.
- The body of `sp_INDE_GetCriterionFeedbackData`.
- Whether hiding save for users below SuperUser blocks the reviewer workflow in practice.
- Whether four silent statuses (no activity, reviewer uploaded, post-translation, print ready) are intentional.
- The full organisation name for IEB.
