03 — User Journeys (Organizations Module)
- 1. Tenant registration & onboarding (Super Admin → first admin)
- 2. Org profile edit
- 3. Branding configuration
- 4. Settings update (attendance / academic / theme)
- 5. Feature-flag toggling
- 6. Tenant offboarding / purge
End-to-end journeys computed from
organizations.controller.ts,organizations.service.ts,settings.controller.ts,feature-flags.controller.ts,auth.service.tsregister(), andtenant-purge.worker.ts. Each journey: entry, intent, decision points, system responses, loading, failures, recovery, exit, back nav, abandonment, timeout, session expiry, permission denial, offline, network loss, conflict, multi-device, deep links, push/QR/email entry.(planned)/(forward-looking)marks per global rules.
1. Tenant registration & onboarding (Super Admin → first admin)
entry: platform console → "New tenant"; or sales handoff with slug already agreed
intent: create a tenant and get its first admin into a working session
sequenceDiagram
actor SA as Super Admin
participant F as TenantCreateScreen
participant R as OrganizationsRepository
participant API as POST /api/v1/organizations
participant RBAC as RbacService
actor A as First Admin (Org Admin)
participant REG as POST /api/v1/auth/register
SA->>F: name + optional slug/domain/contact/address/timezone/currency/plan
F->>R: submit()
R->>API: CreateOrganizationDto
alt 201 success
API-->>R: org doc (status=onboarding, slug auto-derived) — organizations.service.ts:55
API-->>RBAC: seedDefaults(slug) — 7 DEFAULT_ROLES inserted — organizations.service.ts:58
Note over API: EventBus emits OrganizationCreated → in-app queue<br/>job org-created-notification — event-queue-map.ts:13
F-->>SA: success snackbar + org detail (with slug to hand over)
else 409 DUPLICATE_RESOURCE
API-->>R: "Organization with slug X already exists." — organizations.service.ts:41-43
F-->>SA: inline slug/domain conflict, suggest alternatives
else 400 VALIDATION_ERROR
F-->>SA: field-level errors (name required — create-organization.dto.ts:59-60)
else 429 / 5xx / network
F-->>SA: retry-able error state; draft preserved
end
SA-->>A: share slug + URL (email) — email entry (forward-looking: no invite email in code)
A->>REG: firstName, lastName, email, password(≥8), tenantId=slug — register.dto.ts:4-29
alt 201 success
REG-->>A: {accessToken, refreshToken} — auth.service.ts:95-99 (org_admin role)
Note over REG: user created with tenantId=slug; rbac.addMember(org_admin); seedDefaults — auth.service.ts:60-93
A-->>REG: verify email → land /organization (status onboarding banner)
else 409 email exists
A-->>REG: "User with this email already exists." — auth.service.ts:56-58
else 429 (register 5/min — auth.controller.ts:31)
A-->>REG: countdown, no auto-retry — 07_API_Conventions.md §4
end
- Decision points: explicit slug vs auto-slugify; plan (default
free—create-organization.dto.ts:96-102); domain (must be globally unique); whether to seed branding/metadata now or later. - Loading: submit button spinner, anti-double-submit; provisioning includes role seeding (may take ~100–300 ms).
- Failures/recovery: 409 → edit slug/domain inline, resubmit; 400 → field errors; offline → form blocked with guidance (
10_QA_Baseline.md §2), draft kept in memory. - Exit: success → tenant detail; back → list (draft lost — warn on unsaved).
- Abandonment: cancel mid-form → nothing persisted (create is atomic server-side; no partial org).
- Timeout/session expiry: 401 → silent refresh, fail → re-login preserving draft (
00-shared/06 §3.6). - Permission denial: non-platform user → route hidden + 403 screen.
- Conflict: two admins creating the same slug → second gets 409; retry with new slug.
- Multi-device: creation visible to other platform admins on next refresh/pull.
- Deep links / push / QR / email:
(forward-looking)invite email with slug link; QR for onboarding stations.
2. Org profile edit
entry: /organization → "Edit profile"
intent: fix name, slug, domain, contact, address, logo, timezone, currency, academic year
sequenceDiagram
actor OA as Org Admin
participant F as OrgEditScreen
participant R as OrganizationsRepository
participant API as PATCH /api/v1/organizations/:id
OA->>F: change name / timezone / academic year / contact
F->>R: submit() — full UpdateOrganizationDto
alt 200 success
API-->>R: updated doc (version incremented — organizations.repository.ts:49-58)
F-->>OA: success snackbar; header + org overview refresh
else 409 slug/domain conflict
API-->>R: "Organization with slug X already exists." — organizations.service.ts:107-122
F-->>OA: inline conflict on that field only
else 404
F-->>OA: "Organization not found." — organizations.service.ts:105,125
else 400 / 429 / network
F-->>OA: retryable error; form state preserved
end
- Entry points: org overview header, avatar menu → Organization, quick action.
- Decision points: changing
slugis identity-critical (breaks tenantId link for future registers — warn: "existing admins unaffected, new registrations must use the new slug"); domain must be unique. - Loading: skeleton on load, spinner on save. Back: unsaved-changes guard (dialog) → discard/keep editing.
- Timeout/session expiry/permission denial: standard (P2 needs
organization.update). - Offline: read from last-good cache, banner; write blocked with guidance (no offline queue defined for this module —
00-shared/07 §10). - Multi-device conflict: two admins edit → last write wins;
versionincrements but no optimistic-lock guard in repo (organizations.repository.ts:49-58—findOneAndUpdateunconditional); UI refreshes on focus.
3. Branding configuration
entry: /organization → "Branding" tab
intent: set brand colors + logo so the tenant app carries the school identity
sequenceDiagram
actor OA as Org Admin
participant F as BrandingScreen
participant API as PATCH /api/v1/organizations/:id
participant S as StorageProvider (planned)
OA->>F: pick primary/secondary colors (color picker), upload logo
alt logo upload
F->>S: upload → logoFileId (R2/Appwrite — IMPLEMENTATION_PLAN.md:24-34, (planned))
S-->>F: file id → branding.logo = id
end
F->>API: branding {primaryColor, secondaryColor, logo, favicon} — update-organization.dto.ts:125-128
API-->>F: 200 updated doc (branding is Record<string,unknown>, no validation — dto.ts:128)
F-->>OA: live theme preview updates primary color ((proposed) ThemeData copy — 04_Design_System_Mapping.md §7.5)
- Entry: branding tab; exit: back to org overview.
- Failure: upload failure → retry upload, form state kept; 400/429 standard.
- Offline: color changes are local-only until reconnect (no write queue — blocked).
- Permission denial: needs
organization.update. - Multi-device: branding change applies to all devices on next theme refresh; no realtime push today (
(forward-looking)WS topicorg.branding.updated).
4. Settings update (attendance / academic / theme)
entry: /organization/settings (tabs: general, attendance, academic, grading, notification, theme)
intent: adjust attendance rules (grace/late/half-day/working days), grading, colors
sequenceDiagram
actor OA as Org Admin
participant F as SettingsScreen (tab)
participant API as PATCH /api/v1/organizations/:id/settings
OA->>F: change gracePeriod 5→10, workingDays [1,2,3,4,5]→[0..5]
F->>API: FULL settings object {attendance, academic, theme} — full-replace semantics (organizations.service.ts:134)
alt 200
API-->>F: updated org doc (settings replaced)
F-->>OA: snackbar "Settings saved"; theme tab live-preview
else 400 VALIDATION_ERROR
F-->>OA: field errors (e.g., workingDays element not number)
else 404 / 429 / network
F-->>OA: retryable
end
- Critical: the API replaces the whole
settingsobject; the client form must always submit every group (merged from last-known server state) or sibling tabs' data is wiped (OQ-6,organizations.service.ts:134). - Parallel surface: standalone
PUT /api/v1/settings/bulk(settings.controller.ts:43-47) targets thesettingscollection — used by future module-specific config; keep the two surfaces visually separated ("Organization settings" vs "System settings") to avoid confusion. - Decision points: numeric bounds for
gracePeriod/lateThreshold/halfDayThresholdandworkingDays0–6 are(proposed)client-side — no server min/max (update-organization-settings.dto.ts:8-12). - Abandonment/timeout/offline/permission (needs
organization.settings.update): standard patterns as §2.
5. Feature-flag toggling
entry: /organization → "Feature flags" tab
intent: switch tenant-level capabilities on/off (biometric, SMS, WhatsApp, AI reports …)
sequenceDiagram
actor OA as Org Admin
participant F as FeatureFlagsScreen
participant API as PATCH /api/v1/organizations/:id/feature-flags
OA->>F: toggle biometrics ON, WhatsApp OFF
F->>API: FULL map {…existing, biometrics:true, whatsapp:false} — full-replace (organizations.service.ts:150)
alt 200
API-->>F: updated map (only boolean keys — controller.ts:84-88, dto-free)
F-->>OA: optimistic toggle + rollback on error (00-shared/06 §3.5); snackbar
else 400 (non-boolean value)
F-->>OA: inline error on the toggle row
else 404 / 429 / network
F-->>OA: rollback + retry snackbar
end
- Decision points: flag catalog (key + label + module) is client-maintained; the API has no key whitelist and
PATCH :id/feature-flagsaccepts anyRecord<string,boolean>. Unknown keys render from the standaloneGET /api/v1/feature-flagscollection (feature-flags.controller.ts:23-28) which also carries labels/descriptions (schemafeature-flag.schema.ts:15-22). - Optimistic: toggles are safe mutations → optimistic + rollback per
00-shared/06 §3.5. - Effect visibility: toggles gate other modules' UI via
FeatureFlagsCubit(00-shared/06 §4); propagation to other devices on next fetch ((forward-looking)WSorg.feature-flags.updated). - Permission: needs
feature-flags.update(org_admin has it; custom roles may not).
6. Tenant offboarding / purge
entry: tenant detail → "Delete tenant" (platform) or Org Overview → "Delete organization" (self)
intent: remove a tenant from the platform
sequenceDiagram
actor SA as Super Admin
participant F as TenantDetailScreen
participant API as DELETE /api/v1/organizations/:id
participant W as TenantPurgeWorker
SA->>F: open "Delete tenant"
F-->>SA: typed-confirm dialog (type org name — 00-shared/05 §5)
F->>API: DELETE :id
alt 200
API-->>F: 200 (remove returns void — organizations.service.ts:156-159)
F-->>SA: snackbar "Tenant deleted"; row disappears from list (soft-deleted — organizations.repository.ts:60-66)
Note over W: purge enqueue (planned, OQ-5). Worker: deletes all models' docs with<br/>isDeleted=true AND deletedAt < now−30d — idempotent — tenant-purge.worker.ts:32-42
Note over W: GDPR erasure path exists for users (eraseUser) — tenant-purge.worker.ts:27-30,49-56
else 404
F-->>SA: already gone → refresh list
else 403
F-->>SA: permission screen (needs organization.delete)
end
- Decision points: typed confirm (type org name) because the operation is destructive-ish (soft delete; reversible by DB restore within 30 days — no restore endpoint in code).
- Recovery: accidental delete → contact platform ops; hard purge after 30 days is irreversible.
- Loading: delete spinner on confirm; exit: back to tenants list (item gone).
- Timeouts/offline/conflicts: standard; multi-device: other admins see the tenant vanish on refresh.
- Email/push entry:
(forward-looking)offboarding confirmation email.
Journey × robustness matrix
| Journey | Permission denial | Offline | Timeout/session | Conflict | Multi-device |
|---|---|---|---|---|---|
| 1. Provision | 403 screen | write blocked, draft kept | refresh→re-login, draft kept | 409 slug | visible on refresh |
| 2. Profile edit | hidden action / 403 | read cache, write blocked | refresh→re-login | last-write-wins, no optimistic lock | refresh on focus |
| 3. Branding | 403 | local color pick only | refresh→re-login | last-write-wins | next theme refresh |
| 4. Settings | 403 | read cache, write blocked | refresh→re-login | full-replace wipe risk (submit-all-groups) | refresh on focus |
| 5. Flags | 403 + rollback | optimistic only (no offline queue) | refresh→re-login | last-write-wins map replace | next fetch |
| 6. Purge | 403 | blocked | refresh→re-login | double-delete → 404 → refresh | gone on refresh |