critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

ActivityType is an immutable Dart class (all fields final) with a const constructor
All fields match the database column mapping: id (String), orgId (String), displayName (String), orgLabelOverride (String?), description (String?), defaultDurationMinutes (int), isTravelEligible (bool), isReportRequired (bool), isReimbursementTrigger (bool), bufdirCategory (BufdirCategory), status (ActivityTypeStatus), createdAt (DateTime), updatedAt (DateTime)
fromJson(Map<String, dynamic> json) factory constructor correctly parses all fields including nullable fields and DateTime.parse for timestamps
toJson() returns a Map<String, dynamic> with snake_case keys matching database column names exactly
copyWith() method accepts all fields as optional named parameters and returns a new instance with only the specified fields changed
== operator and hashCode are overridden based on all fields (or at minimum on id)
BufdirCategory enum contains all controlled Bufdir category values as defined in task-003
ActivityTypeStatus enum contains 'active' and 'archived' values with toJson/fromJson helpers
Unknown or null bufdir_category from JSON is handled gracefully — either throws a descriptive exception or maps to a BufdirCategory.unknown fallback
The class is placed in lib/models/activity_type.dart (or equivalent project model directory) following existing project file conventions
A toString() override is present for debugging
No generated code (build_runner) required unless the project already uses freezed/json_serializable — match existing model patterns

Technical Requirements

frameworks
Flutter
Dart
apis
Supabase REST API (JSON response shape)
data models
activity_types (Supabase table from task-001)
BufdirCategory enum (task-003)
performance requirements
fromJson must handle lists of 200+ ActivityType objects in < 16ms to avoid jank during list rendering
Model must be suitable for use in BLoC state without causing unnecessary rebuilds — equality must be correct
security requirements
No sensitive fields should be logged in toString() — avoid logging description content
orgId field must never be settable from UI — it is always derived from the authenticated user context

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Check existing model files in the project (e.g., Contact, Feature) and mirror their exact pattern — if the project uses freezed, use freezed; if it uses manual classes, use manual classes. Never mix patterns. The BufdirCategory enum should be defined in the same file as ActivityType for co-location, or in a dedicated enums file if the project has one. Use DateTime.parse() for createdAt/updatedAt; Supabase returns ISO 8601 strings.

For nullable String fields, use json['key'] as String? rather than json['key']?.toString() to avoid accidentally stringifying null. The ActivityTypeStatus enum should have a fromString factory to safely parse the 'active'/'archived' string from JSON.

Testing Requirements

Write unit tests in test/models/activity_type_test.dart: (1) fromJson round-trips correctly for a fully populated JSON object; (2) fromJson round-trips correctly for a minimal JSON object with all nullable fields as null; (3) toJson produces correct snake_case keys; (4) copyWith changes only the specified fields; (5) two ActivityType instances with identical field values are equal (== returns true); (6) two instances with different ids are not equal; (7) fromJson throws or returns a fallback when bufdir_category has an unexpected value; (8) DateTime fields are parsed correctly including timezone handling. Target 100% line coverage for the model class.

Component
Activity Type Form Screen
ui medium
Epic Risks (3)
high impact medium prob dependency

The Bufdir reporting category list is defined externally by Bufdir and may change between reporting years. If the dropdown in ActivityTypeFormScreen is hardcoded, existing activity type mappings could become invalid after a Bufdir schema update, breaking export validation for all organisations.

Mitigation & Contingency

Mitigation: Store the valid Bufdir category list in a Supabase configuration table (bufdir_categories) rather than as a Dart constant, so it can be updated by an admin without a mobile app release. Load the list in the form screen via a lightweight repository call cached locally.

Contingency: If the Bufdir category list cannot be externalised before the admin screen ships, expose a manual override field that allows coordinators to enter a raw Bufdir category code as a fallback, and schedule the configuration table migration as a follow-up task.

medium impact medium prob technical

Reusing ActivityTypeFormScreen for both creation and editing requires careful Riverpod provider scoping. If the form provider is not properly reset between navigation events, stale values from a previously edited type may pre-populate a new creation form, leading to incorrect data being saved.

Mitigation & Contingency

Mitigation: Scope the form state provider to the route using Riverpod's autoDispose modifier, ensuring the state is torn down when the screen is popped. Write a widget test that navigates to edit type A, pops, navigates to create new, and asserts all fields are empty.

Contingency: If provider scoping proves complex with the current router setup, fall back to separate widget implementations for create and edit that share a common form widget but maintain independent provider instances.

high impact low prob integration

Archiving an activity type must not break historical Bufdir export queries that filter activities by type. If the export pipeline performs an INNER JOIN against only active activity types, archived types will cause historical activities to be silently excluded from exports, producing incorrect reporting data.

Mitigation & Contingency

Mitigation: Audit all downstream query builders (Bufdir export, stats aggregation) before shipping the archive feature to confirm they join against all activity types regardless of is_active status. Add an integration test that archives a type, then asserts historical activity records for that type still appear in export queries.

Contingency: If a downstream query is discovered to filter on is_active post-launch, apply a targeted Supabase view fix that unions active and archived types for export contexts without requiring a mobile app update.