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 (Parents Module)

End-to-end journeys computed from parent.controller.ts, parent.service.ts, student-parent-link.service.ts, student.controller.ts, and users.service.ts. Each journey: entry, intent, decision points, system responses, loading, failures, recovery, exit, back navigation, abandonment, permission denial, offline. (planned) / (forward-looking) marks per global rules.


1. Create parent profile

entry: admin/admission staff → Parents → "Add guardian"
intent: record a guardian's business profile for an existing user
precondition: the user account exists (POST /users, users.service.ts:49)
sequenceDiagram
    actor A as Admin/Admission staff
    participant F as ParentFormPage
    participant R as ParentsRepository
    participant API as POST /parents
    A->>F: search & pick user (users list), fill occupation/company/flags
    F->>R: submit(CreateParentDto)
    R->>API: {userId, occupation?, company?, annualIncome?, relationshipNotes?,<br/>emergencyContactPriority?, pickupAuthorization?}
    alt success
        API-->>R: 200/201 envelope.data = parent doc (id, userId, flags, timestamps)
        R-->>F: navigate to parent detail
        Note over API: ParentCreated → in-app 'parent-created' (event-queue-map.ts:37)
    else 409 DUPLICATE_RESOURCE
        API-->>R: "Parent profile already exists for this user." (parent.service.ts:32-34)
        F-->>A: inline banner + link to existing profile
    else 400 VALIDATION_ERROR
        API-->>R: field details (userId not MongoId, etc.)
        F-->>A: per-field errors
    end
  • Decision points: which User (search by name/email via GET /users?q=, users.service.ts:90-115); pickup authorization default off (parent.schema.ts:28).
  • Loading: CTA spinner; anti-double-submit (00-shared/08 §6).
  • Failure covers: duplicate user (409), invalid id (400), offline (banner, blocked), rate limit (429 countdown), 5xx (generic + requestId).
  • Exit: success → detail; back = list. Abandonment: form state lost; user pick kept client-side only.
  • Privacy: parentId is tenant-scoped; no cross-tenant read possible (base.repository.ts:20-30).
entry: parent detail → "Link to student"; student detail → "Add guardian"
intent: create one relationship row with role, primary flag, pickup, financial flags
precondition: parent and student docs exist in the same tenant
sequenceDiagram
    actor A as Admin/Admission staff
    participant S as LinkSheet
    participant R as ParentsRepository
    participant API as POST /parents/link/:studentId
    A->>S: search student (admission number/name) or pick from student page
    S->>R: submit(LinkParentDto)
    R->>API: {parentId, relationship, isPrimaryGuardian?, financialResponsibility?,<br/>pickupAllowed?, emergencyPriority?}
    API->>DB: studentRepo.findById(studentId) — result DISCARDED (student-parent-link.service.ts:27)
    API->>DB: linkRepo.create({studentId, parentId, ...}) — tenantId injected (base.repository.ts:33-35)
    alt success
        API-->>R: envelope.data = link doc (relationship, flags, timestamps)
        R-->>F: sheet closes; guardian list refreshes (link appears)
    else 404
        API-->>R: "Student not found." — only if findById throws (today: no-op, OQ-3)
    end
  • Decision points: relationship (6 values, student-parent-link.schema.ts:7-14); primary guardian toggle; pickup default true on the link (student-parent-link.schema.ts:33-34).
  • Known server gaps (design around them):
    • Duplicate link not detected → second row appears (OQ-2) → client should pre-check existing links and warn "already linked".
    • Missing student not detected → link row created anyway (OQ-3).
    • relationship is free text server-side (link-parent.dto.ts:19-20) → client must send only enum values (OQ-6).
  • Primary-guardian UX: if the student already has a primary, warn before creating a second primary (server allows it, OQ-5).
  • Exit: success → sheet closes; back = cancel sheet.
entry: parent detail → linked child row → menu; or student detail → guardian row → menu
intent: remove a relationship (transfer, custody change, error) or promote another guardian
sequenceDiagram
    actor A as Admin
    participant D as UnlinkDialog
    participant R as ParentsRepository
    participant API as DELETE /parents/link/:linkId
    A->>D: "Unlink guardian from student?"
    D->>R: confirm(linkId)
    R->>API: DELETE /parents/link/:linkId
    API->>DB: linkRepo.softDelete(linkId) (student-parent-link.service.ts:38-41)
    alt success
        API-->>R: envelope.data = undefined (void)
        R-->>F: row removed; snackbar
        Note over API: no ParentDeleted/update event fired for unlink (only ParentUpdated/Deleted, event-queue-map.ts:38-39)
    else 404 RESOURCE_NOT_FOUND
        API-->>R: "Link not found." (student-parent-link.service.ts:40)
        F-->>A: treat as already removed
    end
  • Switch primary guardian (two-step, no dedicated endpoint): PATCH the new primary link → {isPrimaryGuardian:true} then PATCH the old → {isPrimaryGuardian:false} (PATCH /parents/:id cannot touch links — links are only changed by create/delete; so the client calls link-creation? No — there is no link-update endpoint at all: StudentParentLinkService exposes only linkStudentParent, findByStudent, removeLink (student-parent-link.service.ts:16-41). → Switching primary requires delete + recreate the links (OQ-4/OQ-5).
  • Unlink of the primary guardian: warn "This student will have no primary guardian"; server does not auto-promote (OQ-4).
  • Unlink never deletes the parent — parent profile persists even with zero links (04-Modules/Parents.md:58).
  • Permission denial: JWT-only today; UI gates by intended role (planned).

4. Parent logs in → sees own children (self-service, (forward-looking))

entry: parent opens app → login → home
intent: land on "My children", switch between kids, deep-dive each child
precondition: parent has ≥ 1 link; user has parent role (role.schema.ts:50)
sequenceDiagram
    actor P as Parent
    participant H as Home/MyChildrenPage
    participant R as ParentsRepository
    participant API as (planned) GET /parents/me or /parents?userId=
    P->>H: open My Children
    H->>R: loadMyChildren()
    R->>API: (forward-looking — no endpoint today, OQ-1)
    Note over R,API: BLOCKER: no /parents/me, no userId filter,<br/>no "links for my profile" query (parent.service.ts:55-67, findLinks by id only)
    alt endpoint exists (future)
        API-->>R: links[] → child studentIds
        R->>API: GET /students/:id ×N (join, OQ-10)
        H-->>P: child switcher (avatar list) + per-child dashboard
    else today
        R-->>H: (forward-looking) — client cannot resolve; show "coming soon"
    end
  • Privacy boundary: the intent is "only linked children" (USER_PERSONAS.md:48-53); today the parent role only carries student.read and has no API to enumerate its own profile (OQ-1). This journey is the top (forward-looking) item.
  • Child switcher: persists selected child in app state (13_State_Management.md); deep links studylyon://students/:id open a child if linked.
  • Offline: last-good children list cached (00-shared/06 §3.3).

5. Parent updates own profile (self-service, (forward-looking))

entry: my profile → edit
intent: update occupation/company/emergency priority/pickup authorization
sequenceDiagram
    actor P as Parent
    participant F as MyProfilePage
    participant R as ParentsRepository
    participant API as PATCH /parents/:id
    P->>F: change pickupAuthorization + emergencyContactPriority
    F->>R: submit(UpdateParentDto)
    R->>API: PATCH /parents/:id {pickupAuthorization, emergencyContactPriority}
    API->>DB: findById → updateById ($set + $inc version) (parent.service.ts:74-77)
    alt success
        API-->>R: envelope.data = updated parent doc
        F-->>P: snackbar "Profile updated"; audit job ParentUpdated (event-queue-map.ts:38)
    else 404
        API-->>R: "Parent not found." (parent.service.ts:77)
    else 400
        API-->>R: validation details
    end
  • Caveat: PATCH /parents/:id accepts any caller with a JWT (no owner check, OQ-8). When RBAC lands, self-edit must still work for the parent's own profile — the UI must know the parent's id (via the (forward-looking) my-profile resolution).
  • Cannot update userIdUpdateParentDto has no userId (update-parent.dto.ts:4-34).
  • Cannot update link-level flags here — relationship/primary/pickup-per-child live on the link and are immutable after create (no link PATCH endpoint) (OQ-4).

6. Cross-cutting

EntryBehaviorStatus
Deep link studylyon://parents/:idopen parent detail (admin)client (forward-looking)
Deep link studylyon://students/:idopen child (parent: only if linked)client (forward-looking)
Push "new guardian linked"open parent detail(planned) push infra (00-shared/12 B3)
ParentCreated in-app notification"guardian profile created" job parent-created (event-queue-map.ts:37)implemented queue, UI (planned)

Abandonment & exit rules: back = previous screen (never shell home); timeout none (server stateless); permission denial → 403 screen (planned); offline = cached list, writes blocked with banner.