04 — Information Architecture (CRM Module)
- 1. Navigation model
- 2. Information hierarchy — Lead
- 3. Information hierarchy — Admission
- 4. Information hierarchy — Campaign
- 5. Cross-entity relations
- 6. Filter & sort contract
- 7. Labels & status vocabulary (single source)
Screen tree, navigation, and data entities for the CRM client. Global navigation conventions: 00-shared/05_Global_Information_Architecture.md. Routes shown as
/crm/...under the authenticated shell.
1. Navigation model
The CRM is one top-level destination with three tabs and one sub-flow:
CRM (/crm)
├── Leads tab /crm/leads
│ ├── Lead detail /crm/leads/:id
│ │ ├── Lead editor (sheet) /crm/leads/:id/edit (S3)
│ │ ├── Follow-up composer (sheet) (S2 inline)
│ │ └── Convert flow (dialog + states) (S4)
│ └── New lead /crm/leads/new (S3)
├── Admissions tab /crm/admissions
│ ├── Admission detail /crm/admissions/:id
│ │ ├── Documents upload (sheet) (S6 inline)
│ │ ├── Interview scheduler (sheet) (S8)
│ │ └── Decision dialog (S9)
│ └── New admission /crm/admissions/new (S7)
└── Campaigns tab /crm/campaigns
└── New campaign /crm/campaigns/new (S11)
- Master-detail: on tablets the tab list and detail render side-by-side; on phones detail is a pushed route.
- Permission-gated nodes: writes (
crm.lead.manage,crm.campaign.manage) hide FABs/actions; reads needcrm.read(permissions.constants.ts:34-36). Enforcement on server is(planned)— client gates anyway.
2. Information hierarchy — Lead
Lead
├── Identity: firstName, middleName, lastName, email, phone
├── Pipeline: status (new/contacted/qualified/converted/closed)
│ closedAt, closedReason, convertedAt, convertedToStudentId
├── Source: source (website/referral/walk_in/phone/campaign/other)
├── Placement: gradeId, sectionId, academicYearId, classId
├── Ownership: assignedTo (Staff ref), notes
├── Follow-ups: [{note, scheduledAt, completedAt?, createdBy, createdAt}] (timeline)
└── metadata (opaque)
Fields per lead.schema.ts:41-99; BaseSchema adds tenantId/createdAt/updatedAt/version.
Derived values (client-computed, no dedicated field): next follow-up due =
earliest followUps[].scheduledAt without completedAt; "overdue" if due < now.
Gap: no nextContactDate column exists — see 08_Form_Specifications.md §5.
3. Information hierarchy — Admission
Admission
├── Identity: firstName, middleName, lastName, email, phone
├── Pipeline: status (9 states, admission.schema.ts:7-17), submittedAt, decidedAt
├── Placement: gradeId, academicYearId, classId
├── Documents: [{type, fileId, filename, uploadedBy, uploadedAt}]
├── Interview: {scheduledAt, panel[], mode, feedback}
├── Workflow: [{from, to, approver?, comment?, at}] ← the audit trail
├── Conversion: {studentId, convertedAt}
└── notes
4. Information hierarchy — Campaign
Campaign
├── name, description
├── type (email/sms/social/print/other), status (draft/active/paused/completed)
├── startDate, endDate
├── targetAudience (opaque)
└── metrics {leadsGenerated, converted, sent, opened, clicked} ← schema-only (planned write path)
5. Cross-entity relations
| Relation | Edge | Source |
|---|---|---|
| Lead → Student | convertedToStudentId after convert | lead.schema.ts:88-89 |
| Lead → Staff | assignedTo | lead.schema.ts:79-80 |
| Lead → Academic | gradeId/sectionId/academicYearId/classId refs | lead.schema.ts:64-74 |
| Lead → Campaign | implicit via source: campaign (no FK) | lead.schema.ts:58-59 |
| Admission → Student | conversion.studentId after convert | admission.schema.ts:83-89 |
| Admission → Staff | interview.panel[], workflow[].approver | admission.schema.ts:59-60,73-74 |
Client note: lead/admission payloads carry raw ObjectIds for refs; detail screens
resolve display names via the relevant module (students, staff, academics) —
cross-module GETs are (planned) where endpoints are missing.
6. Filter & sort contract
| List | Server filters | Server sort (fixed) | Source |
|---|---|---|---|
| Leads | status only (page/limit) | createdAt: -1 | crm.controller.ts:37-45; lead.repository.ts:31 |
| Admissions | status only (page/limit) | submittedAt: -1 | crm.controller.ts:103-111; admission.repository.ts:29 |
| Campaigns | none (page/limit) | createdAt: -1 | crm.controller.ts:77-81; campaign.repository.ts:25 |
- Gaps: no
assignedTo,source,q(search), orsortclient param on any CRM list. Client filters (my leads, source chip) must be client-side over one page or wait for(planned)server filters. Global search covers leads(planned)(IMPLEMENTATION_PLAN.md §3). - Pagination meta:
{page, limit, totalItems, totalPages, hasNext, hasPrevious}(00-shared/07 §2;buildPaginationMeta).
7. Labels & status vocabulary (single source)
- Lead statuses render as: New / Contacted / Qualified / Converted / Closed ("Closed" = dropped in CRM terms).
- Sources: Website / Referral / Walk-in / Phone / Campaign / Other.
- Admission statuses: Draft / Submitted / Documents pending / Interview scheduled / Under review / Approved / Rejected / Waitlisted / Converted.
- Decision actions: Approve / Reject / Waitlist (
admission-decision.dto.ts:4-8).