feat: complete events by any property worker (not just task-assigned)#988
Merged
Conversation
When completing a task from the task-tracker, the worker-selection modal now lists ALL workers assigned to the event's property (via getLinkedSites(propertyId, true)) instead of only the task-assigned subset, sorted alphabetically by name with a locale-aware 'da' sort so Danish characters (æ/ø/å) order correctly. The single-worker auto-select behaviour and the compliance=true flag are unchanged. The selected worker flows through the compliance/case route (:siteId) into ReplyRequest.SiteId; the backend Update sets foundCase.SiteId directly with no assigned-only validation, so any active property worker completes the case correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the task-tracker change (5f7d264) on the calendar completion flow. When the worker-selection modal opens on event complete, populate it from getLinkedSites(propertyId, true) — ALL non-removed property workers — sorted alphabetically by name (locale-aware 'da'), instead of only the task-assigned assigneeIds/workerNames. The modal (CalendarSelectWorkerModalComponent.sites) expects CommonDictionaryModel[]; getLinkedSites returns LinkedSiteModel (extends CommonDictionaryModel), so the shape matches and the selected worker id flows through unchanged. Note: the calendar's on-demand materialization path (future occurrences not yet deployed by the nightly batch) still gates the selected worker on PlanningSites and returns SelectedWorkerNotAssignedToTask for a property worker who isn't task-assigned. Already-materialized occurrences (the common case) ignore workerId downstream and complete on the case's own site, so any property worker is accepted there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… worker Relax the on-demand materialisation gate so completing a future/on-demand calendar occurrence works for ANY active worker of the event's PROPERTY, not just the task's assigned PlanningSites. Completes the feature whose frontend (calendar + task-tracker worker pickers now list all property workers) is already on this branch. - ToggleComplete: when a worker is picked, validate it is an active PropertyWorker for the ARP's property (same source as GetLinkedSites) and materialise the case for that site directly, instead of requiring the site to be in PlanningSites. Still rejects a stray site id that is not a property worker. No-worker default path unchanged (first non-removed PlanningSite). - EventDeployService.DeployForRotationAsync: widen the #932/#1377 anti-leak guard from "site in PlanningSites" to "site in PlanningSites OR active PropertyWorker for the property", so the on-demand path can deploy for a property worker not in PlanningSites while still refusing an unrelated site. Nightly EnsureDeployedAsync behaviour is unchanged (its candidates already satisfy the PlanningSites branch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… workers Add a positive integration test, EnsureComplianceForOccurrence_ SiteIsActivePropertyWorkerNotInPlanningSites_Deploys, mirroring the existing pin test but seeding an active PropertyWorker for the planning's property (target site absent from PlanningSites). Asserts the deploy guard does NOT throw and DEPLOYS: a fresh Compliance with a real SDK case and a PlanningCaseSite written for the property-worker site. The existing negative pin test is unchanged and still asserts the throw for the truly-unassigned (no PropertyWorker) case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the backend-configuration plugin’s “complete event” flow so the worker picker (calendar + task tracker) lists all workers linked to the event’s property (sorted alphabetically), and the backend deploy/complete logic is adjusted to allow on-demand materialisation for a property worker even if they are not in the task’s PlanningSites.
Changes:
- Frontend: worker selection modals now load property workers via
getLinkedSites(propertyId, true), sorted with Danish locale ordering. - Backend: deploy guard in
EventDeployService.DeployForRotationAsyncnow allows a site if it is either inPlanningSitesor an activePropertyWorkerfor the planning’s property. - Tests: adds an integration test asserting an active property-worker (not in
PlanningSites) can deploy viaEnsureComplianceForOccurrenceAsync.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/EventDeployService/EventDeployService.cs | Relaxes the deploy-site guard to allow active property workers (in addition to planning-assigned sites). |
| eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationCalendarService/BackendConfigurationCalendarService.cs | Changes on-demand completion to accept selected workers that are active property workers; preserves default behavior when no worker is selected. |
| eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/EventDeployServiceTest.cs | Adds a positive integration test covering the new “active property worker not in PlanningSites deploys” scenario. |
| eform-client/src/app/plugins/modules/backend-configuration-pn/modules/task-tracker/components/task-tracker-container/task-tracker-container.component.ts | Updates task-tracker worker picker to show all property workers and sort them alphabetically. |
| eform-client/src/app/plugins/modules/backend-configuration-pn/modules/calendar/components/calendar-container/calendar-container.component.ts | Updates calendar worker picker to fetch/sort property workers before opening the select-worker modal. |
Comment on lines
457
to
+473
| if (!siteIsAssignedToPlanning) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"EventDeployService refused to deploy planning {planning.Id} to sdkSiteId {sdkSiteId}: " | ||
| + "the site is not in the planning's (non-removed) PlanningSites. Deploying here would " | ||
| + "leak a case to a non-assigned worker (#932/#1377)."); | ||
| var siteIsActivePropertyWorker = await dbContext.PropertyWorkers | ||
| .AsNoTracking() | ||
| .AnyAsync(pw => | ||
| pw.PropertyId == areaRulePlanning.PropertyId | ||
| && pw.WorkerId == sdkSiteId | ||
| && pw.WorkflowState != Constants.WorkflowStates.Removed, | ||
| ct) | ||
| .ConfigureAwait(false); | ||
| if (!siteIsActivePropertyWorker) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"EventDeployService refused to deploy planning {planning.Id} to sdkSiteId {sdkSiteId}: " | ||
| + "the site is neither in the planning's (non-removed) PlanningSites nor an active " | ||
| + $"worker of property {areaRulePlanning.PropertyId}. Deploying here would leak a case " | ||
| + "to an unrelated worker (#932/#1377)."); |
Comment on lines
+617
to
+620
| const linked = await firstValueFrom( | ||
| this.propertiesService.getLinkedSites(task.propertyId, true), | ||
| ); | ||
| if (!linked?.success || !linked.model) return; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When completing an event, the worker-selection list now shows all workers assigned to the event's property, sorted alphabetically — instead of only the task-assigned subset.
Changes
Frontend (worker pickers → all property workers, sorted):
task-tracker-container.tsopenSelectWorkerModal: drop theassignedTointersection; list fullgetLinkedSites(propertyId, true), sortedlocaleCompare(…, 'da').calendar-container.tsonToggleCompleteRequested: fetch property workers viagetLinkedSitesinstead of the task'sassigneeIds/workerNames; sorted. Single-worker pre-select preserved in both.Backend (allow any active property worker to complete):
BackendConfigurationCalendarService.ToggleComplete: gate changed from "workerId ∈ PlanningSites" → "workerId ∈ active PropertyWorkers for the planning's property".EventDeployService.DeployForRotationAsync: the Calendar: case deploy writes PlanningCaseSites rows for workers not in the planning's assigned site list (Miljøtilsyn #1377) #932/#1377 anti-leak guard widened to allow a site that is in PlanningSites OR is an active PropertyWorker; still throws for truly-unassigned sites (no leak).Test:
EnsureComplianceForOccurrence_SiteIsActivePropertyWorkerNotInPlanningSites_Deploys— asserts a property-worker-not-in-PlanningSites now deploys. The Calendar: case deploy writes PlanningCaseSites rows for workers not in the planning's assigned site list (Miljøtilsyn #1377) #932/#1377 pin test (truly-unassigned → throws) is unchanged and still passes. Both run green locally against the MariaDB testcontainer.Blast-radius note
DeployForRotationAsync's other caller (automated/mobileEnsureDeployedAsync) pre-narrows candidates toPlanningSitesand never reaches the new fallback branch, so the guard relaxation does not regress #932/#1377 for those flows.PropertyWorker.WorkerIdis the SDK site id (matchesgetLinkedSitesand the gate).🤖 Generated with Claude Code