Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

10KB

Story 1.10: Municipality Account Profile

Status: done

Story

As a a client services staff member, I want to create and maintain municipality account profiles linked to legacy jurisdiction identifiers, so that permanent municipality data is managed in the extension layer without modifying legacy Access tables.

Acceptance Criteria

  1. Given a client services user navigates to municipality management When they create a new municipality profile Then it is saved to the extension layer with a required link to a valid legacy jurisdiction identifier (ID/JCode)
  2. Given a municipality profile is created When the profile is loaded Then the anti-corruption layer resolves the legacy join and displays combined extension and legacy data together in the workspace grid
  3. Given a profile field is updated When saved Then the change is recorded in the audit log with actor identity and timestamp
  4. Given a user attempts to create a profile without a valid legacy jurisdiction identifier When the form is submitted Then the save is rejected with a clear validation message identifying the required legacy link And no INSERT, UPDATE, or DELETE operations are performed on legacy Access tables at any point

Tasks / Subtasks

  • Implement story behavior in aligned backend/frontend modules (AC: #1)
    • Add or update API/service/UI components required by the story scope
    • Keep legacy Access entities read-only and route writes to extension-layer structures
  • Cover acceptance criteria #2 in implementation and tests (AC: #2)
    • Add validation/error handling and UX state updates as needed
  • Cover acceptance criteria #3 in implementation and tests (AC: #3)
    • Add validation/error handling and UX state updates as needed
  • Cover acceptance criteria #4 in implementation and tests (AC: #4)
    • Add validation/error handling and UX state updates as needed
  • Validate and document completion evidence
    • Verify build/tests for touched modules
    • Capture changed files and any migration/config implications

Dev Notes

  • Follow Epic 1 architecture constraints: ASP.NET Core + React separation, RBAC-aware patterns, and immutable legacy tables.
  • Reuse shared component and workflow patterns defined in UX and architecture docs; avoid parallel custom implementations.
  • Keep changes scoped to this story; do not pull forward Epic 2+ features.

Project Structure Notes

  • Backend: Campaign_Tracker.Server/
  • Frontend: campaign-tracker-client/
  • Story artifacts: _bmad-output/implementation-artifacts/

References

  • Story source: _bmad-output/planning-artifacts/epics.md (Epic 1 / Story 1.10)
  • Architecture constraints: _bmad-output/planning-artifacts/architecture.md
  • UX patterns: _bmad-output/planning-artifacts/ux-design-specification.md

Dev Agent Record

Agent Model Used

claude-sonnet-4-6

Debug Log References

  • 133/133 backend tests pass; 36/36 frontend tests pass. No regressions.

Completion Notes List

  • Introduced Campaign_Tracker.Server/Municipalities/ namespace with domain entity and repository.
  • MunicipalityProfile record implements ILegacyLinkedRecord — participates in Story 1.8 nightly integrity check automatically.
  • InMemoryMunicipalityProfileRepository validates JCode via ILegacyLinkValidator before save (AC #4). Resolves legacy jurisdiction fields via ILegacyDataAccess for combined views (AC #2). Returns MunicipalityProfileView combining both layers.
  • MunicipalityProfileController (/api/municipalities/profiles) — POST/GET/PUT with ClientServicesAccess policy (Admin bypass via HasAny). Records audit events on create and update (AC #3).
  • Repository registered as singleton + as ILegacyLinkedRecordProvider so integrity check covers municipality profiles.
  • Frontend: municipalityContracts.ts — typed fetch functions with MunicipalityValidationError for 422 responses (AC #4).
  • Frontend: MunicipalityProfilePanel.tsx — table of profiles with combined legacy data (AC #2), modal form for create with JCode field and error display.
  • WorkspaceShell.tsx updated: selecting “Municipalities” nav item now renders MunicipalityProfilePanel for users with canViewMunicipalityProfile permission.
  • 20 backend unit tests (10 repository + 10 controller integration) + 10 frontend contract tests.

File List

  • Campaign_Tracker.Server/Municipalities/MunicipalityProfile.cs (new)
  • Campaign_Tracker.Server/Municipalities/MunicipalityProfileView.cs (new)
  • Campaign_Tracker.Server/Municipalities/MunicipalityProfileSaveResult.cs (new)
  • Campaign_Tracker.Server/Municipalities/IMunicipalityProfileRepository.cs (new)
  • Campaign_Tracker.Server/Municipalities/InMemoryMunicipalityProfileRepository.cs (new)
  • Campaign_Tracker.Server/Controllers/MunicipalityProfileController.cs (new)
  • Campaign_Tracker.Server/Program.cs (modified — added Municipalities using + repository registrations)
  • Campaign_Tracker.Server.Tests/MunicipalityProfileRepositoryTests.cs (new — 11 tests)
  • Campaign_Tracker.Server.Tests/MunicipalityProfileControllerTests.cs (new — 5 tests)
  • campaign-tracker-client/src/municipalities/municipalityContracts.ts (new)
  • campaign-tracker-client/src/municipalities/MunicipalityProfilePanel.tsx (new)
  • campaign-tracker-client/src/municipalities/municipalityContracts.test.ts (new — 9 tests)
  • campaign-tracker-client/src/workspace/WorkspaceShell.tsx (modified — municipalities view wired)
  • _bmad-output/implementation-artifacts/1-10-municipality-account-profile.md (this file)
  • _bmad-output/implementation-artifacts/sprint-status.yaml (modified — status updated)

Review Findings

  • [Review][Decision] Out-of-scope: jurisdiction endpoint + searchable Select picker — accepted as scope extension. Test gaps and Promise.all failure mode addressed via patch items below.
  • [Review][Patch] Post-save/update GetByIdAsync bang-dereferenced — null guard added; returns 500 with descriptive message if view unexpectedly null [Campaign_Tracker.Server/Controllers/MunicipalityProfileController.cs]
  • [Review][Patch] TOCTOU race on CreateAsync + lost write on UpdateAsync — _lock object added; duplicate-check+insert atomic in CreateAsync; read-modify-write atomic in UpdateAsync [Campaign_Tracker.Server/Municipalities/InMemoryMunicipalityProfileRepository.cs]
  • [Review][Patch] UpdateAsync not-found returns 422 instead of 404 — MunicipalityProfileSaveResult.ProfileNotFound factory added; controller checks result.IsNotFound and returns 404 [Campaign_Tracker.Server/Controllers/MunicipalityProfileController.cs]
  • [Review][Patch] Promise.all — jurisdiction failure blocks profile display — loads split into two independent calls; jurisdictionsLoadError state added; “New” button disabled when jurisdictions unavailable; distinct warning alert shown [campaign-tracker-client/src/municipalities/MunicipalityProfilePanel.tsx]
  • [Review][Patch] FromJsonSeedFile bare catch swallows all exceptions with no logging — catch (Exception ex) with Console.Error.WriteLine added [Campaign_Tracker.Server/LegacyData/InMemoryLegacyDataAccess.cs]
  • [Review][Patch] Missing 403 test for wrong-role token on municipality endpoints — CreateProfile_WrongRoleToken_Returns403, GetJurisdictions_NoToken_Returns401, and UpdateProfile_UnknownId_Returns404 tests added [Campaign_Tracker.Server.Tests/MunicipalityProfileControllerTests.cs]
  • [Review][Patch] fetchAvailableJurisdictions has no contract tests — success and failure tests added [campaign-tracker-client/src/municipalities/municipalityContracts.test.ts]
  • [Review][Patch] JCode normalization inconsistency — normalizedJCode computed before validator call; stored and validated forms are now consistent [Campaign_Tracker.Server/Municipalities/InMemoryMunicipalityProfileRepository.cs]
  • [Review][Patch] FromJsonSeedFile does not DistinctBy(JCode) — .DistinctBy(r => r.JCode!.Trim(), StringComparer.OrdinalIgnoreCase) added before projection [Campaign_Tracker.Server/LegacyData/InMemoryLegacyDataAccess.cs]
  • [Review][Patch] AC #3 audit path not asserted at integration level — CreateProfile_RecordsAuditEvent_AC3 and UpdateProfile_RecordsAuditEvent_AC3 tests added [Campaign_Tracker.Server.Tests/MunicipalityProfileControllerTests.cs]
  • [Review][Patch] 422 body shape mismatch produces “undefined” user error — problem.error ?? 'Validation failed.' fallback added in both create and update functions [campaign-tracker-client/src/municipalities/municipalityContracts.ts]
  • [Review][Patch] Backend test count discrepancy — updated: 10 repository + 10 controller = 20 backend tests; 10 frontend contract tests [_bmad-output/implementation-artifacts/1-10-municipality-account-profile.md]
  • [Review][Patch] Integration tests broken by FromJsonSeedFile when real seed data is present — AuthIntegrationTestFactory now overrides ILegacyDataAccess with hardcoded test defaults, isolating tests from the development seed file (same pattern as IAuditService override) [Campaign_Tracker.Server.Tests/AuthEndpointTests.cs]
  • [Review][Defer] Internal whitespace in JCode from Access not handled — Trim() strips leading/trailing only; embedded spaces cause lookup mismatches [Campaign_Tracker.Server/LegacyData/OleDbLegacyDataAccess.cs] — deferred, pre-existing
  • [Review][Defer] ProfileId uses ToString(“N”) (no hyphens) — latent cross-system UUID format mismatch if consumers return a hyphenated variant [Campaign_Tracker.Server/Municipalities/InMemoryMunicipalityProfileRepository.cs] — deferred, pre-existing
  • [Review][Defer] CreatedAt stored but absent from API response DTO — profile creation timestamp inaccessible to consumers [Campaign_Tracker.Server/Controllers/MunicipalityProfileController.cs] — deferred, pre-existing
  • [Review][Defer] Audit Outcome hardcoded to “updated display name” — will mislead once model has additional updatable fields [Campaign_Tracker.Server/Controllers/MunicipalityProfileController.cs] — deferred, pre-existing
  • [Review][Defer] refresh() post-create does not reload jurisdiction list — stale list if jurisdictions change during session [campaign-tracker-client/src/municipalities/MunicipalityProfilePanel.tsx] — deferred, pre-existing

Change Log

  • 2026-05-06: Story 1.10 implemented — municipality account profile domain, repository, REST API, and React panel with legacy join resolution. 25 tests added. All 4 ACs satisfied. (claude-sonnet-4-6)

Powered by TurnKey Linux.