# Business rules — ieb-inde-4

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

Rules below are taken from code or from `INDE-4/ReadME.txt`. A rule marked **Inferred** is a reading of that evidence, not a separate written policy. A rule marked **Documented** is text in the readme. Everything else in this file that cites a method is **Implemented**.

Website root: `INDE-4/Evolution Software Unified Framework/Evolution Software Unified Framework/core_custom/INDE-4/`.

## Identity

### BR-001 — The readme calls this project Docs and says it uses the INDE-2 user table

**Documented.** `INDE-4/ReadME.txt`.

**Not confirmed in configuration.** The active `conn` key uses catalog `Framework_INDE-4_Demo`. Commented keys use catalog `Framework_INDE-4`. No app setting names an INDE-2 database. Custom user queries filter `pal_User` by `handler.ReturnSetup().code`, which is the setup customer code of whatever database `conn` opens.

**Requires Confirmation** before anyone treats the INDE-2 user table as the live rule.

### BR-002 — Two website titles are configured

**Implemented** in `Web.config`. Key `website` appears as `IEB (INDE-4 Local)` and later as `IEB CRM`. The subject key remains `IEB (INDE-4 Local) - {content}`.

Which string the running configuration returns was not executed. **Requires Confirmation.**

### BR-003 — This tree was built from an INDE-2 working copy

**Inferred.** MSBuild file lists and `.vs/INDE-2/config/applicationhost.config` use `C:\DEV work\NEW_IEB\INDE-2\...`, including `core_custom\INDE-2`. That supports a copy origin. It does not by itself prove a shared user table. See BR-001.

## Schools and contacts

### BR-004 — A user below PowerUser may edit a school only as its IEB liaison

**Implemented.** `Schools_grid.ascx.cs`, dated 2018-05-15. For `userType` below PowerUser, the edit link is visible when `publ_` `oSchoolContacts` has `ParentSurfaceItemId` equal to the school item id, `SchoolContacts_SchoolContacts_Name` equal to the user id, and `SchoolContacts_SchoolContacts_IEBLiaison` equal to `1`.

The edit column header is set visible unconditionally. The commented role check under that header is not executed.

Principal (`SchoolContacts_SchoolContacts_Principal`) is stored and is not used in this check.

The check does not filter the grid query. A user can still be shown a school they cannot edit, unless some other framework filter applies. `linkUser` limiting is present in the shared grid code and depends on the surface flag. Whether the schools surface sets `linkUser` is **Unknown** (database).

### BR-005 — A school contact name is a user id for this customer

**Implemented.** `BindUserPicklist` loads active `pal_User` rows where `customerCode` equals the setup code. Display text is `surname + ', ' + name`. The stored field `SchoolContacts_SchoolContacts_Name` is an int.

"Add New" is inserted with value `-1` and opens `$('#modContactAdd').modal()`.

### BR-006 — Choosing a contact copies email and mobile from the user

**Implemented.** `populateUserInfo` sets mobile from `oUser.tel` and email from `oUser.email`. If `usr.title` is non-empty and a list item with that text exists, the code sets `SelectedItem.Text` to that title. It does not set `SelectedValue`.

### BR-007 — School name and school type on the contact are not copied in this form

**Implemented** absence: no assignment to `SchoolContacts_SchoolContacts_SchoolName` or `SchoolContacts_SchoolContacts_SchoolType` was found in `SchoolContacts_form.ascx.cs`. The properties exist on the object. A database action could still fill them. **Unknown.**

### BR-008 — Contact and member active years are stored as 1 January

**Implemented** for cluster members. When the year text is non-empty, `ClusterMembers_MemberDetails_MemberActiveYear` becomes `new DateTime(year, 1, 1)`. Display uses `.Year` when the stored year is greater than 1901 on load, and greater than 1900 when copying from a school contact.

The school-contact active-year control was not given a separate custom converter in the blocks that were read. **Unknown** whether it uses the same year-only pattern through generated date code.

## Clusters

### BR-009 — Cluster code format

**Implemented.** When the code box is empty and level, subject, and province all resolve:

`{ClusterLevels_ClusterLevels_CodeAbbreviation}-{Subjects_SubjectDetails_SubjectCode}-{province lookup customCode}-{nn}`

`nn` is `01` when no existing code has that prefix. Otherwise it is one more than `COUNT(*)` of `publ_ClusterInformation` codes `LIKE` that prefix, formatted with `D2`. A count that increments past 99 becomes three digits. **Implemented** by the format string.

If any of the three parts is missing, the code is left empty.

`PerformCustomSaveAddOn` returns `result`, which stays false, and the caller ignores the return. The code is written to the textbox before the generated save reads the textbox, so the generated save can persist it.

The count query concatenates the prefix into SQL text.

### BR-010 — After a code exists, only some identity fields lock

**Implemented.** The code textbox is always disabled. When it is non-empty, province, level, and subject name are disabled. Cluster name and area are not disabled in this method.

### BR-011 — Subject choices follow the selected cluster level

**Implemented.** `BindSubjectNames` loads active `publ_SubjectManagement` rows whose `Subjects_SubjectDetails_ClusterLevel` equals the selected level, ordered by subject name, and inserts a "Select" item with value `0`. Changing the level picklist calls this method again.

### BR-012 — A cluster inherits a template resource list

**Implemented.** If no `publ_ClusterResources` row has this cluster's `itemID` as parent, every resource with `ParentSurfaceItemId = 0` is copied onto new items of surface `3048`. The new surface item is active, created now, and created by the current user.

What those template rows are is **Unknown** (database).

### BR-013 — Users below PowerUser add cluster children only when they are a resource

**Implemented.**

- New member button (surface `3046`) is visible when some resource on the cluster has `ResourceName` equal to the user's id.
- New meeting button (surface `3076`) is visible when that same resource's type is exactly `Cluster Administrator` or `Cluster Leader`.

The loop stops at the first resource whose name matches the user. If two resource rows exist, only the first match is considered.

PowerUser and above do not pass through this visibility block.

### BR-014 — Meeting edit and remove are forced on, then limited per row

**Implemented.** On the cluster child repeater, a meetings grid always shows the edit and remove headers. For a user below PowerUser, the row edit and remove links are visible only when `AllowMeetings` is true. Other child grids keep the framework role checks.

### BR-015 — Cluster member names are this year's contacts of the selected school

**Implemented.** The picklist query requires `ParentSurfaceItemId` of the school contact to equal the selected school, and `YEAR(SchoolContacts_SchoolContacts_ActiveYear)` to equal `DateTime.Now.Year`. The user query then requires `isActive = 1`, `customerCode` equal to the setup code, and `recId` in that id list.

Email, SACE number, and active year on the member form are disabled. They are filled from the matching school contact when a user id greater than 0 is chosen. The contact lookup uses `SchoolContacts_SchoolContacts_Name` equal to the selected user id and does not also filter by the selected school.

### BR-016 — Cluster member "Add New" is commented out

**Implemented.** The list item `("Add New", "-1")` is commented. `populateUserInfo` still opens the contact modal when the selected id is negative.

### BR-017 — A meeting stores a copy of the parent cluster's labels

**Implemented.** On every save, area, cluster code, cluster name, level name, province display, and subject name are overwritten from the parent cluster and its lookups. Province uses `oSurfaceLookup.display`. A comment in the method mentions `customCode` and does not use it. The copy is not described as a live link.

### BR-018 — Cluster resource names are all active users for this customer

**Implemented.** The picklist is not limited to a school or a year. "Add New" (`-1`) is inserted and opens `#modContactAdd`. Choosing a user copies `oUser.email`. Resource type is disabled on populate.

Session `contAdded`, when present, rebinds the list, selects that id, fills the email, and clears the session. The school contact form does the same with its own picklist.

### BR-019 — Editing a resource from a new cluster saves the cluster first

**Implemented.** In the cluster form's resource edit handler, if the surface item id is 0, or if the cluster has no `publ_ClusterResources` children, `PerformSave(false, false, true)` runs and the handler returns when that save returns false. The clicked repeater index is then resolved to the edit command argument.

## Imports

### BR-020 — A new imported person becomes a WebsiteUser

**Implemented.** Both custom imports set `userType` to WebsiteUser, `isActive` true, and `customerCode` to the current setup code. The password is `utils.RandomString(8, true)`, stored with the framework encryption helper. The passphrase is a source literal and is not repeated here.

The schools import also sets title, mobile, and RSA ID number. The cluster-member import does not set those, and neither block sets `isReset`.

If email plus customer code already exists, the user row is not updated.

The user-creation block runs only when `surf.isPublished` is true. An unpublished child surface still imports rows and does not run this user block.

### BR-021 — Imported names are split on comma, otherwise on space

**Implemented.** Both imports use the same split:

- Comma: text before the comma is the surname. The remainder is the given name after `Replace(" ", "")` is applied to the whole original string and then substringed. That space strip can shift the given-name index. **Implemented** as written.
- Space, no comma: text before the first space is the given name. The rest is the surname.
- Neither: the whole value is the surname. Given name stays unset.

Schools import reads column `Name`. Cluster import reads column `Member Name`. A missing column throws, and the method redirects to `/error`.

## Examination mail

### BR-022 — Examination status ids exist in source

**Implemented** as constants in `examNums.SubjectStatus` and related enums. `examHandler` contains `EmailExamStatus` and loads criterion rows with `sp_INDE_GetCriterionFeedbackData`.

| Id | Name in `examNums` |
|---|---|
| 1083 | ExaminerUploadedReviewReady |
| 1084 | ReviewerUploadedIMReleaseReady |
| 2160 | ReviewerQuery |
| 1085 | IMModerationRequired |
| 1086 | IMNotCompliant |
| 1087 | IMCompliantEMReleaseReady |
| 1088 | EMModerationRequired |
| 1093 | NoActivity |
| 1106 | EMNotCompliant |
| 1107 | EMCompliantMinorChanges |
| 1108 | EMCompliantTranslationReady |
| 1109 | PostTranslationChangeRequired |
| 1110 | PrintReady |

Subject resource roles: Examiner 1, Reviewer 2, InternalModerator 3, ExternalModerator 4, AssessmentSpecialist 5.

The recipient chosen for each status was not line-traced for this document. Whether INDE-4 publishes an examination surface is **Unknown / Requires Confirmation**.

## Permissions and access

### BR-023 — New-button visibility follows grid options, with a CustomUser mimic

**Implemented** on the schools grid. The new button is visible when `userType >= addAccessType` and the user is not CustomUser, or when the user is CustomUser and `mimicUserType >= addAccessType`.

`addAccessType` comes from grid options stored for the surface. The numeric threshold is **Unknown** (database).

### BR-024 — Website user link

**Implemented** in the shared grid code. If the surface has `linkUser` and the user is a WebsiteUser, the grid query includes that user id. Whether the INDE-4 custom surfaces set `linkUser` is **Unknown** (database).

### BR-025 — Setup code STUD-1 changes save buttons

**Implemented** and inactive for a setup code other than `STUD-1`. Several forms, including schools and school contacts, rename the save button to "Profile Save" when the surface name is `Profiles`, and disable the save dropdown when the surface name starts with `Device`. Those branches do not describe Docs behaviour unless the database setup code is `STUD-1`.

## Data and configuration rules that affect behaviour

### BR-026 — The active database key is the demo catalog

**Implemented** in `Web.config`. The uncommented `conn` value uses catalog `Framework_INDE-4_Demo`. Commented keys name `Framework_INDE-4` with integrated security and with a SQL login. Host names and passwords are omitted here.

### BR-027 — MediSwitch and loyalty mail are switched off

**Implemented** in config. `MediSwitchResponses` is `0`. The active gateway URLs are the QA hosts. Live hosts are commented. `loyaltyEmail` is `0`. `getCountriesFromWebService` is `0`.

### BR-028 — Failures in custom surface methods redirect to /error

**Implemented.** The custom methods that were read catch exceptions, call `exception.HandleException` with the session user, and `Response.Redirect("/error", false)`.

## Explicitly not inferred

- That "Docs" means a document-management product beyond the cluster Document Library tab and the framework file explorer.
- That production users are rows in an INDE-2 database.
- That `IEB CRM` is the title operators see.
- That examination moderation or billing instructions are in use on the INDE-4 site.
- That a website user cannot see schools where they are not the liaison. The code limits the edit link, not the row set, in the custom block that was read.
