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

15 — Flutter Implementation Guide (CRM Module)

How to build the CRM feature slice in the Flutter client. Extends 00-shared/11_Flutter_App_Architecture.md (folder structure, DI, dio, router, testing). Forward-looking spec — no client repo exists yet.


1. Module structure

lib/features/crm/
├── data/
│   ├── dto/
│   │   ├── lead_dto.dart              # LeadDto.fromJson → Lead
│   │   ├── admission_dto.dart         # AdmissionDto + nested (workflow, documents, interview, conversion)
│   │   └── campaign_dto.dart
│   ├── models/
│   │   ├── lead.dart                  # Lead, LeadStatus, LeadSource, FollowUp
│   │   └── admission.dart             # Admission, AdmissionStatus, AdmissionDocumentType, WorkflowStage
│   ├── repositories/
│   │   ├── lead_repository.dart
│   │   ├── admission_repository.dart
│   │   └── campaign_repository.dart
│   └── data_sources/ (none extra; dio via AppDio)
├── domain/
│   ├── follow_up_summary.dart         # pure: next due / overdue from FollowUp[]
│   └── convert_rules.dart             # pure: canConvert(lead) mirroring crm.service.ts:118-149
└── presentation/
    ├── cubit/
    │   ├── leads_cubit.dart
    │   ├── lead_detail_cubit.dart
    │   ├── lead_form_cubit.dart
    │   ├── follow_up_cubit.dart
    │   ├── convert_cubit.dart
    │   ├── admissions_cubit.dart
    │   ├── admission_detail_cubit.dart
    │   ├── admission_form_cubit.dart
    │   ├── interview_cubit.dart
    │   ├── decision_cubit.dart
    │   ├── campaigns_cubit.dart
    │   └── campaign_form_cubit.dart
    ├── pages/lead_list_page.dart, lead_detail_page.dart, lead_form_sheet.dart,
    │         admissions_page.dart, admission_detail_page.dart,
    │         admission_form_page.dart, campaigns_page.dart, campaign_form_sheet.dart
    └── widgets/lead_status_chip.dart, lead_source_icon.dart, status_badge.dart,
              workflow_timeline.dart, document_card.dart, interview_card.dart,
              funnel_stat_card.dart, convert_dialog.dart, decision_dialog.dart,
              follow_up_composer.dart, filter_chip_row.dart

2. Enums & models (mirror server exactly)

  • LeadStatus: new, contacted, qualified, converted, closed (lead.schema.ts:7-13); LeadSource: website, referral, walk_in, phone, campaign, other (lead.schema.ts:15-22).
  • FollowUp: note, scheduledAt(DateTime), completedAt?, createdBy?, createdAt.
  • AdmissionStatus: 9 values (admission.schema.ts:7-17); AdmissionDocumentType: tc, marksheet, certificate, photo, other (:27-33).
  • WorkflowStage: from, to, approver?, comment?, at.
  • CampaignStatus/CampaignType: campaign.schema.ts:7-20.
  • DateTime from ISO strings; refs stay String ObjectIds (display resolved via other modules' repositories).

3. Repositories (via AppDio)

RepoMethods → endpoint
LeadRepositorylist({page, limit, status}) → GET /crm/leads; get(id) → GET /crm/leads/:id; create(dto) → POST; update(id, patch) → PATCH; addFollowUp(id, dto) → POST /crm/leads/:id/follow-ups; convert(id) → POST /crm/leads/:id/convert
AdmissionRepositorylist({page, limit, status}), stats() → GET /crm/admissions/stats, get(id), create(dto), update(id, patch), addDocument(id, dto), scheduleInterview(id, dto), decide(id, dto), convert(id)
CampaignRepositorylist({page, limit}), create(dto)
  • All map envelope → data + meta (pagination mixin, 00-shared/06 §3.2); errors surface as ApiException(status, code, message, fieldDetails) (00-shared/07 §3).
  • Full endpoint table + rule codes: 12_API_Mapping.md §1,§4.
  • List cache keys: crm.leads:{status} / crm.admissions:{status} / crm.campaigns (SWR, 5 min; detail never cached).

4. Domain logic (pure, unit-tested)

  • followUpSummary(lead) → {nextDue?, overdue} — earliest uncompleted scheduledAt (09 §3); drives S1 row subtitle + S2 overdue styling.
  • convertRules(lead) → {pass, missing: [...]} — status ∉ {converted, closed} + gradeId/academicYearId/classId present (crm.service.ts:118-149); gates Convert button with tooltip (06 §S4).

5. Cubits

One cubit per screen/flow (13_State_Management.md §1), all pure-Dart, DI via get_it lazy factories. Key policies:

  • List cubits: PaginatedListMixin + status filter; client-side source/mine filters with "filtered on device" hint; refresh bypasses cache.
  • Detail cubits: no cache; pessimistic for convert/decision/follow-up; optimistic only for field/status/assignment PATCHes with rollback (13 §3).
  • Form cubits: 409-duplicate state (lead create) with "edit details" path; 400 field mapping; double-submit guard.

6. Routing

RoutePageGuard
/crm/crm/leadsLeadListPagecrm.read
/crm/leads/newLeadFormSheetcrm.lead.manage
/crm/leads/:idLeadDetailPagecrm.read
/crm/admissionsAdmissionsPagecrm.read
/crm/admissions/newAdmissionFormPagecrm.lead.manage
/crm/admissions/:idAdmissionDetailPagecrm.read
/crm/campaignsCampaignsPagecrm.read
/crm/campaigns/newCampaignFormSheetcrm.campaign.manage
  • Permission gate: permissionGuard('crm.read') etc. (strings from permissions.constants.ts:34-36); server enforcement (planned).
  • Deep link: push tap → /crm/leads/:id (forward-looking).

7. Widgets

Module components per 07_Component_Library.md; goldens for badges/timeline/stat cards/tiles. No widget fetches data directly — cubit-provided state only (00-shared/06 §2).

8. i18n

Keys under crm.* namespace (crm.status.*, crm.source.*, crm.adm.status.*, crm.rule.* for the exact business-400 messages in 12 §4); server business text fallback only for unmapped 4xx (00-shared/11 §9).

9. Offline policy

  • Reads: SWR cache + AppOfflineBanner; writes require connectivity (no offline queue — explicit design decision, 13 §12); disabled submit buttons when offline with explanatory copy.

10. Testing plan

  • Unit: followUpSummary, convertRules, DTO mappers, form validators, cubits (mock repos): status filter, 409 duplicate, optimistic rollback, convert pre-check, decision gating.
  • Widget: three-state machine per list/detail; convert dialog disabled states; workflow timeline rendering; goldens (14 §13-14).
  • Integration: journey tests — lead capture → follow-up → qualify → convert; admission submit → documents → interview → approve → convert; duplicate-email banner flow (03_User_Journey.md).
  • E2E: same journeys against live backend with seeded tenant (parity with 14 §14).

11. Build order

  1. Enums/models + DTO mappers + repositories (contract-locked to 12 §1).
  2. LeadsCubit + S1 (list/filter/pagination).
  3. S2 detail + S3 form + 409 handling.
  4. Convert flow (rules + dialog) — server-rule parity tests.
  5. Admissions slice (S5-S9) + stats tiles.
  6. Campaigns slice (S10/S11).
  7. Permissions gating + deep links + i18n pass + performance profile.