Skip to content

feat: complete events by any property worker (not just task-assigned)#988

Merged
renemadsen merged 4 commits into
stablefrom
feat/task-complete-all-property-workers
Jun 8, 2026
Merged

feat: complete events by any property worker (not just task-assigned)#988
renemadsen merged 4 commits into
stablefrom
feat/task-complete-all-property-workers

Conversation

@renemadsen

Copy link
Copy Markdown
Member

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.ts openSelectWorkerModal: drop the assignedTo intersection; list full getLinkedSites(propertyId, true), sorted localeCompare(…, 'da').
  • calendar-container.ts onToggleCompleteRequested: fetch property workers via getLinkedSites instead of the task's assigneeIds/workerNames; sorted. Single-worker pre-select preserved in both.

Backend (allow any active property worker to complete):

Test:

Blast-radius note

DeployForRotationAsync's other caller (automated/mobile EnsureDeployedAsync) pre-narrows candidates to PlanningSites and never reaches the new fallback branch, so the guard relaxation does not regress #932/#1377 for those flows. PropertyWorker.WorkerId is the SDK site id (matches getLinkedSites and the gate).

🤖 Generated with Claude Code

renemadsen and others added 4 commits June 8, 2026 16:33
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>
Copilot AI review requested due to automatic review settings June 8, 2026 15:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.DeployForRotationAsync now allows a site if it is either in PlanningSites or an active PropertyWorker for the planning’s property.
  • Tests: adds an integration test asserting an active property-worker (not in PlanningSites) can deploy via EnsureComplianceForOccurrenceAsync.

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;
@renemadsen renemadsen merged commit c95f53f into stable Jun 8, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants