15 — Flutter Implementation Guide (CRM Module)
- 1. Module structure
- 2. Enums & models (mirror server exactly)
- 3. Repositories (via
AppDio) - 4. Domain logic (pure, unit-tested)
- 5. Cubits
- 6. Routing
- 7. Widgets
- 8. i18n
- 9. Offline policy
- 10. Testing plan
- 11. Build order
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.DateTimefrom ISO strings; refs stayStringObjectIds (display resolved via other modules' repositories).
3. Repositories (via AppDio)
| Repo | Methods → endpoint |
|---|---|
LeadRepository | list({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 |
AdmissionRepository | list({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) |
CampaignRepository | list({page, limit}), create(dto) |
- All map envelope →
data+meta(pagination mixin,00-shared/06 §3.2); errors surface asApiException(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 uncompletedscheduledAt(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
| Route | Page | Guard |
|---|---|---|
/crm → /crm/leads | LeadListPage | crm.read |
/crm/leads/new | LeadFormSheet | crm.lead.manage |
/crm/leads/:id | LeadDetailPage | crm.read |
/crm/admissions | AdmissionsPage | crm.read |
/crm/admissions/new | AdmissionFormPage | crm.lead.manage |
/crm/admissions/:id | AdmissionDetailPage | crm.read |
/crm/campaigns | CampaignsPage | crm.read |
/crm/campaigns/new | CampaignFormSheet | crm.campaign.manage |
- Permission gate:
permissionGuard('crm.read')etc. (strings frompermissions.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
- Enums/models + DTO mappers + repositories (contract-locked to
12 §1). LeadsCubit+ S1 (list/filter/pagination).- S2 detail + S3 form + 409 handling.
- Convert flow (rules + dialog) — server-rule parity tests.
- Admissions slice (S5-S9) + stats tiles.
- Campaigns slice (S10/S11).
- Permissions gating + deep links + i18n pass + performance profile.