Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 — User Journeys (Organizations Module)

End-to-end journeys computed from organizations.controller.ts, organizations.service.ts, settings.controller.ts, feature-flags.controller.ts, auth.service.ts register(), and tenant-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 freecreate-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 slug is 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; version increments but no optimistic-lock guard in repo (organizations.repository.ts:49-58findOneAndUpdate unconditional); 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 topic org.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 settings object; 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 the settings collection — 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/halfDayThreshold and workingDays 0–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-flags accepts any Record<string,boolean>. Unknown keys render from the standalone GET /api/v1/feature-flags collection (feature-flags.controller.ts:23-28) which also carries labels/descriptions (schema feature-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) WS org.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

JourneyPermission denialOfflineTimeout/sessionConflictMulti-device
1. Provision403 screenwrite blocked, draft keptrefresh→re-login, draft kept409 slugvisible on refresh
2. Profile edithidden action / 403read cache, write blockedrefresh→re-loginlast-write-wins, no optimistic lockrefresh on focus
3. Branding403local color pick onlyrefresh→re-loginlast-write-winsnext theme refresh
4. Settings403read cache, write blockedrefresh→re-loginfull-replace wipe risk (submit-all-groups)refresh on focus
5. Flags403 + rollbackoptimistic only (no offline queue)refresh→re-loginlast-write-wins map replacenext fetch
6. Purge403blockedrefresh→re-logindouble-delete → 404 → refreshgone on refresh