15 — Flutter Implementation Guide (Organizations Module)
- 1. Structure
- 2. Models & enums (exact from schema)
- 3. Repositories
- 4. Cubits (see
13) - 5. Navigation (go_router)
- 6. Theme
- 7. Extensions
- 8. Localization
- 9. Testing
- 10. Performance
- 11. Open items to wire when backend lands
Module extension of 00-shared/11. Structure, widgets, cubits, repositories, DTOs, models, navigation, theme, extensions, localization, testing, performance. Forward-looking.
1. Structure
lib/features/organizations/
├── data/
│ ├── dto/
│ │ ├── organization_dto.dart # fromJson/toJson for envelope payload
│ │ ├── organization_create_dto.dart # mirrors CreateOrganizationDto
│ │ ├── organization_update_dto.dart # mirrors UpdateOrganizationDto
│ │ ├── organization_settings_dto.dart # mirrors UpdateOrganizationSettingsDto
│ │ ├── setting_dto.dart # mirrors UpdateSettingDto (standalone)
│ │ └── feature_flag_dto.dart # mirrors UpdateFeatureFlagDto
│ ├── models/
│ │ ├── organization.dart # domain model (enums: status/plan/subStatus)
│ │ ├── organization_settings.dart
│ │ ├── org_feature_flags.dart
│ │ └── flag_catalog_entry.dart
│ └── repositories/
│ ├── organization_repository.dart # E1–E9
│ └── org_config_repository.dart # E10–E11 (settings + flags collections)
├── domain/
│ └── slugify.dart # pure fn mirroring organizations.service.ts:161-169
└── presentation/
├── cubit/
│ ├── org_detail_cubit.dart
│ ├── org_edit_cubit.dart
│ ├── org_settings_cubit.dart
│ ├── feature_flags_cubit.dart
│ ├── members_cubit.dart
│ └── tenants_cubit.dart
├── pages/
│ ├── org_overview_page.dart # S1
│ ├── org_edit_page.dart # S2
│ ├── branding_page.dart # S3
│ ├── org_settings_page.dart # S4
│ ├── feature_flags_page.dart # S5
│ ├── members_page.dart # S6
│ ├── tenants_page.dart # S7
│ └── tenant_detail_page.dart # S8 (+ create)
└── widgets/
├── org_header.dart
├── status_badge.dart
├── setting_field_group.dart
├── color_picker_field.dart
├── day_chips.dart
├── flag_switch_row.dart
├── tenant_row.dart
└── slug_preview.dart
2. Models & enums (exact from schema)
enum SubscriptionPlan { free, basic, premium, enterprise } // organization.schema.ts:6-11
enum SubscriptionStatus { active, inactive, suspended, trial }// :13-18
enum OrganizationStatus { active, inactive, suspended, onboarding } // :20-25
enum SettingGroup { academic, attendance, grading, notification, theme, general } // setting.schema.ts:7-14
Organization fields mirror organization.schema.ts:28-152: name, slug, logoFileId, domain, contact{email,phone,website}, address{street,city,state,country,zip}, timezone, currency, academicYear{startDate,endDate,month}, subscriptionPlan/Status, settings, branding{primaryColor,secondaryColor,logo,favicon}, status, metadata, createdAt/updatedAt, version. Ids String; dates DateTime (parse ISO from envelope timestamps). Never send DTOs to widgets (00-shared/11 §4).
3. Repositories
OrganizationRepository (dio, via AppDio bearer/refresh/error interceptors — 00-shared/11 §5):
createOrg(CreateOrgDto) → Organization(E1)listOrgs({page,limit,sort,q}) → Paginated<Organization>(E2)getOrg(id) → Organization(E3)updateOrg(id, UpdateOrgDto) → Organization(E4)deleteOrg(id) → void(E5)getSettings(id) → OrgSettings(E6),updateSettings(id, OrgSettings) → Organization(E7)getFlagMap(id) → Map<String,bool>(E8),setFlagMap(id, Map<String,bool>) → Map<String,bool>(E9)getSelfOrg() → Organization— OQ-1: calls(planned)/organizations/me; stopgap:listOrgs(q: tenantIdSlug)documented in code.
OrgConfigRepository: settings collection E10 + flags catalog E11.
Typed exceptions: ApiException(code, status, fieldDetails, message) from error interceptor (00-shared/06 §5).
4. Cubits (see 13)
OrgDetailCubit, OrgEditCubit, OrgSettingsCubit, FeatureFlagsCubit, MembersCubit, TenantsCubit (with PaginatedListMixin<Organization> — 00-shared/06 §3.2). Full-object save guard in OrgSettingsCubit.SaveAll (merge from lastServer + dirty). Optimistic rollback in FeatureFlagsCubit.Toggle. All pure-Dart, DI via get_it lazy factories (00-shared/11 §2).
5. Navigation (go_router)
GoRoute(path: '/organization', builder: OrgOverviewPage, guards: [authGuard, permissionGuard('organization.read')]),
GoRoute(path: '/organization/edit', ..., guards: [..., permissionGuard('organization.update')]),
GoRoute(path: '/organization/branding', ...),
GoRoute(path: '/organization/settings', ...),
GoRoute(path: '/organization/feature-flags', ...),
GoRoute(path: '/organization/members', ...),
GoRoute(path: '/admin/tenants', guards: [authGuard, platformAdminGuard]),
GoRoute(path: '/admin/tenants/new', ...),
GoRoute(path: '/admin/tenants/:id', ...),
GoRoute(path: '/admin/tenants/:id/edit', ...),
platformAdminGuard checks user.isPlatformAdmin (jwt-auth.guard.ts:54). Permission guards mirror permissions.constants.ts (organization.* :2-5, settings.* :75-77, feature-flags.* :78-80); server remains authoritative (403 → route redirect to 403 screen). Master-detail via StatefulShellBranch at ≥840 dp (00-shared/05 §3). Deep links (forward-looking): studylyon://organization/settings, studylyon://admin/tenants/:id.
6. Theme
Branding override: AppTheme.fromBranding(seed: org.branding.primaryColor) → ColorScheme.fromSeed (D §7.5, 02 §1); preview mode in S3 renders with overridden scheme without persisting. themeMode from system or org pin (proposed) (OQ/D). No literal tokens in widgets (02 §10, 04 §7).
7. Extensions
Reuse shared (00-shared/11 §8): DateTime.toDisplayDate, String.initials, int.toMoney, context.showAppSnackbar. Module additions: OrganizationStatus.displayName, Organization.statusSemantics, Map<String,bool>.flagKeysBy(module), String.toSlug (wraps slugify).
8. Localization
Keys under features/organizations/ namespace in .arb (en + fr + hi smoke): org.title, org.status.onboarding, org.settings.saveAll, org.flags.toggle.rollback, org.tenants.delete.confirm, org.slug.conflict, weekday labels for chips. Server messages rendered via error-code→key map with business-4xx fallback (00-shared/11 §9, 07 §11).
9. Testing
| Layer | Coverage |
|---|---|
| Unit | slugify parity tests vs organizations.service.ts:161-169 vectors ("St. Mary's School" → st-marys-school); enum mapping; formatters; full-object merge logic |
| Cubit | OrgSettingsCubit full-object save (assert sibling groups present in payload); FeatureFlagsCubit rollback; TenantsCubit pagination + search reset |
| Widget | S1 loading/error/empty; S4 3-tab dirty states; S5 toggle pending/rollback; S7 skeleton/infinite-scroll; typed-confirm disabled state |
| Golden | 8 screens × light/dark × 3 sizes; new components (07 §Golden) |
| Integration | provision → register → overview journey; settings save → reload → values intact |
| E2E (device cloud) | P0: platform creates tenant; admin registers; edits settings; toggles flag; deletes tenant |
Run: flutter analyze, flutter test, flutter test integration_test (00-shared/11 §12).
10. Performance
ListView.builderfor S5/S6/S7;AutomaticKeepAliveClientMixinfor S4 tabs;RepaintBoundaryaround S3 preview.- Flag list grouped sections render lazily; collapse via
AnimatedSizewithout layout rebuild storms. SlugPreviewdebounced 150 ms; search debounce 300 ms.- Caches keyed
sl:{tenant}:org…with TTLs from13 §8;RefreshIndicatorbypasses cache. - Profile on mid-range device against
10_QA_Baseline.md §1budgets.
11. Open items to wire when backend lands
GET /organizations/me(OQ-1) — remove stopgap lookup.- JWT+RBAC guard + tenant scoping on organizations controller (OQ-4) — swap
platformAdminGuardfor server truth. TenantPurgeJobenqueue (OQ-5) — surface "purge scheduled" state if API returns it.- Storage provider (R2/Appwrite) — logo upload real path (
IMPLEMENTATION_PLAN.md:24-34). - WS topics
org.branding.updated/org.feature-flags.updated— subscribe + cache invalidation. - Institution-type discriminator (Phase 6,
IMPLEMENTATION_PLAN.md:251-287) — addinstitutionTypeto model + onboarding step(planned).