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.

8.1KB

Story 2.2: Create Election-Cycle Job

Status: review

Story

As a client services staff member, I want to create a new election-cycle job for a municipality from the kanban board, so that the municipality is assigned to an election cycle without altering any legacy Access tables.

Acceptance Criteria

  1. Given a municipality card is in the Unassigned lane When a client services user initiates job creation Then they can select an existing cycle or name a new cycle, and a new election-cycle extension record is created linked to the municipality's legacy identifier (ID/JCode)
  2. Given an election-cycle job is created When saved Then it is stored in the extension table with the municipality's legacy identifier as a required join key and the municipality card moves to the selected cycle lane on the kanban
  3. Given an election-cycle job is created When the job record is inspected Then it includes municipality reference, cycle name, creation actor, creation timestamp, and a status of “In Setup”
  4. Given a job creation is attempted without selecting or naming a cycle When the form is submitted Then the save is rejected with a clear inline validation message
  5. And no INSERT, UPDATE, or DELETE operations are performed on legacy Access tables at any point

Tasks / Subtasks

  • Backend: election-cycle job creation endpoint (AC: #1, #2, #3, #4, #5)
    • Add an extension-table entity for election-cycle jobs with required municipality legacy identifier (ID/JCode) join key, cycle name, status, created-by, and created-at
    • POST endpoint accepts either an existing cycle reference or a new cycle name; rejects payloads missing a cycle selection with a structured validation error
    • Persist new job with status "In Setup", capturing actor identity from the authenticated principal and server-side timestamp
    • Audit the create event using the shared audit logger established in Epic 1 (Story 1.5)
    • Confirm via test that the operation never executes INSERT/UPDATE/DELETE against legacy Access tables — only extension storage
  • Frontend: create job flow from kanban (AC: #1, #2, #4)
    • Add a “Create cycle job” action on Unassigned lane cards that opens a modal/drawer with cycle selection (existing) and new-cycle-name input
    • Wire form validation and surface inline server validation errors when cycle selection is missing
    • On success, refresh kanban data so the card relocates to the selected cycle lane (multi-lane behavior from Story 2.1 must be preserved)
  • Tests & evidence (AC: #1–#5)
    • Backend tests for happy path, missing cycle, RBAC, audit emission, legacy table read-only invariant
    • Frontend tests for the create flow, validation errors, and post-create kanban update
    • Document changed files and any config notes

Dev Notes

  • Legacy Access tables are read-only — all writes must go to extension tables joined by ID/JCode. This invariant is enforced project-wide and is the primary regression risk for this story.
  • Reuse the audit logger from Story 1.5 and the RBAC/authorization patterns from Stories 1.3/1.4. Do not introduce parallel auth or audit code.
  • Status "In Setup" becomes the upstream input for Stories 2.3–2.5; treat the value as the canonical initial state and keep the status enum centralized.
  • The kanban entry point and lane data shape are owned by Story 2.1 — extend the existing read model rather than building a parallel one.
  • Keep changes scoped to this story; key dates, prior-cycle defaults, readiness, and publish behavior land in Stories 2.3–2.5.

Project Structure Notes

  • Backend: Campaign_Tracker.Server/ — add election-cycle feature folder (entity, repository, controller, audit binding) following Epic 1 conventions
  • Frontend: campaign-tracker-client/ — add cycle creation UI alongside the kanban view from Story 2.1
  • Story artifacts: _bmad-output/implementation-artifacts/

References

  • Story source: _bmad-output/planning-artifacts/epics.md (Epic 2 / Story 2.2)
  • Architecture constraints: _bmad-output/planning-artifacts/architecture.md (extension-table write path, legacy read-only)
  • UX patterns: _bmad-output/planning-artifacts/ux-design-specification.md
  • Prior story: Story 2.1 — kanban entry point and lane data contract; Story 1.5 — shared audit logging; Story 1.6 — legacy anti-corruption data access layer

Dev Agent Record

Agent Model Used

Qwen3.6-27B-Q4_K_M (OpenMono.ai)

Debug Log References

  • Story generated from epic source and architecture/UX planning artifacts.
  • Backend build: 0 warnings, 0 errors.
  • Full test suite: 172 passed, 0 failed (includes 10 new ElectionCycleJobControllerTests).
  • Frontend tests written; Node.js not available on build host so vitest could not execute — code follows existing antd/React patterns from Story 2.1.

Implementation Plan

  1. Created ElectionCycleJob record with JobId, JCode, CycleId, CycleName, Status, CreatedBy, CreatedAt.
  2. Extended IElectionCycleJobRepository with CreateAsync(jCode, cycleId, cycleName, actorIdentity).
  3. Added ElectionCycleJobSaveResult for success/failure reporting.
  4. Updated InMemoryElectionCycleJobRepository to support both DI (TimeProvider constructor) and test (custom assignments constructor) paths; GetAllAsync merges seed data with dynamically created jobs without double-counting.
  5. Created ElectionCycleJobsController with POST /api/election-cycles/jobs and GET /api/election-cycles/jobs/{jobId}; ClientServicesAccess policy; audit emission via IAuditService.
  6. Added frontend CreateJobModal component with existing-cycle selector and new-cycle-name input; wired into kanban view on Unassigned lane cards.
  7. Updated electionCycleKanbanContracts.ts with createElectionCycleJob, CreateElectionCycleJobRequest, ElectionCycleJobResponse.
  8. Wrote 10 backend integration tests covering: happy path (existing cycle), new cycle name generation, missing cycle validation, missing JCode validation, unauthenticated rejection, wrong-role rejection, audit event emission, legacy read-only invariant, idempotency, and 404 for unknown job.
  9. Wrote frontend unit tests for modal rendering and error propagation.

Completion Notes List

  • Backend POST endpoint at /api/election-cycles/jobs creates jobs with “In Setup” status, captures actor identity and server timestamp, emits ELECTION_CYCLE_JOB_CREATED audit event.
  • Accepts either existing cycle (cycleId) or new cycle name (cycleName → auto-generates cycleId).
  • Validation rejects missing JCode or missing cycle selection with 422/400 + structured error message.
  • RBAC enforced via [Authorize(Policy = ApplicationPolicy.ClientServicesAccess)] — unauthenticated gets 401, non-ClientServices role gets 403.
  • Legacy Access tables never written to — ILegacyDataAccess exposes only Get* methods; all writes go through IElectionCycleJobRepository (extension layer).
  • Frontend “Create cycle job” button appears on Unassigned lane cards; opens modal with cycle selector or new-cycle-name input; on success reloads kanban so card relocates.
  • 10 backend tests added (ElectionCycleJobControllerTests); all 172 tests pass.

File List

  • Campaign_Tracker.Server/ElectionCycles/ElectionCycleJob.cs
  • Campaign_Tracker.Server/ElectionCycles/ElectionCycleJobSaveResult.cs
  • Campaign_Tracker.Server/ElectionCycles/IElectionCycleJobRepository.cs
  • Campaign_Tracker.Server/ElectionCycles/InMemoryElectionCycleJobRepository.cs
  • Campaign_Tracker.Server/Controllers/ElectionCycleJobsController.cs
  • Campaign_Tracker.Server.Tests/ElectionCycleJobControllerTests.cs
  • Campaign_Tracker.Server.Tests/ElectionCycleKanbanReadModelTests.cs
  • campaign-tracker-client/src/electionCycles/CreateJobModal.tsx
  • campaign-tracker-client/src/electionCycles/CreateJobModal.test.tsx
  • campaign-tracker-client/src/electionCycles/electionCycleKanbanContracts.ts
  • campaign-tracker-client/src/electionCycles/electionCycleKanbanView.tsx

Change Log

  • 2026-05-07: Implemented election-cycle job creation endpoint, frontend modal, and full test suite. All 172 backend tests pass.

Powered by TurnKey Linux.