From 6d83c90d6829263d0998065a7b909ad6d7cba9da Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 5 Sep 2026 21:49:03 +0200 Subject: [PATCH] feat(schemas): namespace the case task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `task` was claimed by three apps: planninq, pipelinq and this one. They share `description`, `priority` and `status` — what any task-shaped record carries, and nothing that identifies the record. planninq's project task is the largest and keeps the bare slug; pipelinq's became `crmTask`. WHAT THE SUITE FOUND THAT THE GREP DID NOT. The rename reached the descriptors, the config-key map and every store call on the first pass, and the suite still failed six times — twice for the same reason in two different places. KpiAggregationService::ids() and DemoCaseloadGateway::schemaIds() each build a LOCAL array keyed 'task' and hand it round as $ids['task']. Renaming the readers without the builders left $ids['caseTask'] resolving to null, which surfaced as a TypeError on a nullable argument three frames away, in a class that never mentions the slug. Then PHPStan caught the declared array shapes still saying `task: string` after the builders moved. Neither would ever appear in `grep -rn "'task'"`-shaped triage as a schema reference: the array key is not the slug, it is just spelled like it. This is the first rename tonight where the escaped sites were STRUCTURAL rather than lexical. tests/e2e/ci-seed.sh names the slug in its required-schema list and moved with it — that list is checked after the import and exits BEFORE Playwright, so a miss reports every spec as not run rather than as a failure. Decoys left alone: `itemType` in WorkQueueService and the `type` key in CaseReassignmentService, BulkReassignModal, taskApi and dashboardHelpers, all row labels for mixed lists. Full suite: 3,045 tests, 18,012 assertions, exit 0. phpcs, phpstan, psalm exit 0. --- lib/Repair/RenameCollidingSchemaSlugs.php | 4 +++ lib/Service/DemoCaseloadGateway.php | 4 +-- lib/Service/DemoCaseloadReport.php | 2 +- lib/Service/DemoCaseloadSeedDataService.php | 2 +- lib/Service/KpiAggregationService.php | 6 ++-- lib/Service/Settings/SchemaSlugMap.php | 2 +- lib/Service/Settings/SchemaSlugResolver.php | 2 +- .../Support/JsonEncodedStringProperties.php | 2 +- lib/Settings/dossiq_mock_register.json | 10 +++--- lib/Settings/dossiq_register.json | 6 ++-- .../namespace-the-case-task/proposal.md | 35 +++++++++++++++++++ .../specs/case-management/spec.md | 33 +++++++++++++++++ .../changes/namespace-the-case-task/tasks.md | 16 +++++++++ .../flow/TaskWaitingCaseSection.vue | 2 +- src/components/tabs/CaseTasksTab.vue | 2 +- src/manifest.json | 16 ++++----- src/store/modules/advice.js | 4 +-- src/store/modules/enforcement.js | 2 +- src/store/modules/inspection.js | 2 +- src/store/modules/workflow.js | 2 +- src/store/store.js | 2 +- src/views/widgets/MyTasksWidget.vue | 2 +- src/views/widgets/TaskRemindersWidget.vue | 2 +- tests/Unit/Controller/StoreControllerTest.php | 2 +- .../Flow/AskPersonHeartbeatRecoveryTest.php | 2 +- tests/Unit/Flow/CaseFlowDeclarationTest.php | 4 +-- tests/Unit/Flow/DossiqAskPersonNodeTest.php | 4 +-- .../Repair/MigrateSchemaApplicationIdTest.php | 8 ++--- .../Service/CaseReassignmentServiceTest.php | 10 +++--- .../Service/KpiAggregationServiceTest.php | 4 +-- .../SelectionReassignmentServiceTest.php | 2 +- .../Settings/SchemaKeyReconcilerTest.php | 10 +++--- .../Settings/SchemaSlugResolverTest.php | 12 +++---- .../Unit/Service/SubstitutionServiceTest.php | 4 +-- tests/Unit/Service/WorkQueueServiceTest.php | 6 ++-- .../ShippedEnumValueConformanceTest.php | 2 +- .../Settings/WorkflowEngineSchemaTest.php | 2 +- .../Settings/WorkflowGuardConformanceTest.php | 4 +-- tests/e2e/case-flow-live-journeys.spec.ts | 4 +-- tests/e2e/ci-seed.sh | 2 +- tests/e2e/demo-caseload.spec.ts | 8 ++--- tests/e2e/helpers/fixtures.ts | 2 +- tests/l10n/check-l10n.js | 2 +- tests/vitest/searchableSchemas.spec.js | 2 +- 44 files changed, 172 insertions(+), 84 deletions(-) create mode 100644 openspec/changes/namespace-the-case-task/proposal.md create mode 100644 openspec/changes/namespace-the-case-task/specs/case-management/spec.md create mode 100644 openspec/changes/namespace-the-case-task/tasks.md diff --git a/lib/Repair/RenameCollidingSchemaSlugs.php b/lib/Repair/RenameCollidingSchemaSlugs.php index 71d9ff57b..c45ca8145 100644 --- a/lib/Repair/RenameCollidingSchemaSlugs.php +++ b/lib/Repair/RenameCollidingSchemaSlugs.php @@ -69,6 +69,10 @@ class RenameCollidingSchemaSlugs implements IRepairStep { */ private const RENAMES = [ 'supplierInvoice' => ['to' => 'caseSupplierInvoice', 'with' => 'shillinq'], + // The case task. `task` was claimed by three apps and they share only + // `description`, `priority` and `status`. planninq's project task is the + // largest and keeps the bare slug; pipelinq took crmTask. + 'task' => ['to' => 'caseTask', 'with' => 'planninq, pipelinq'], ]; /** diff --git a/lib/Service/DemoCaseloadGateway.php b/lib/Service/DemoCaseloadGateway.php index aed094843..b9ffd90d6 100644 --- a/lib/Service/DemoCaseloadGateway.php +++ b/lib/Service/DemoCaseloadGateway.php @@ -65,7 +65,7 @@ public function __construct( /** * The register and schema ids the demo caseload reads and writes. * - * @return array{register: string, case: string, task: string, caseType: string, statusType: string} The ids. + * @return array{register: string, case: string, caseTask: string, caseType: string, statusType: string} The ids. * * @throws RuntimeException When the app is not configured against a register yet. * @@ -75,7 +75,7 @@ public function schemaIds(): array { $ids = [ 'register' => $this->config(key: 'register'), 'case' => $this->config(key: 'case_schema'), - 'task' => $this->config(key: 'task_schema'), + 'caseTask' => $this->config(key: 'task_schema'), 'caseType' => $this->config(key: 'case_type_schema'), 'statusType' => $this->config(key: 'status_type_schema'), ]; diff --git a/lib/Service/DemoCaseloadReport.php b/lib/Service/DemoCaseloadReport.php index e5a9ad924..b3caac4e5 100644 --- a/lib/Service/DemoCaseloadReport.php +++ b/lib/Service/DemoCaseloadReport.php @@ -92,7 +92,7 @@ public function buckets(?DateTimeImmutable $now = null): array { $tasks = $this->gateway->findMany( objectService: $objectService, registerId: $ids['register'], - schemaId: $ids['task'], + schemaId: $ids['caseTask'], filters: [] ); diff --git a/lib/Service/DemoCaseloadSeedDataService.php b/lib/Service/DemoCaseloadSeedDataService.php index 7933491d8..8e45ae200 100644 --- a/lib/Service/DemoCaseloadSeedDataService.php +++ b/lib/Service/DemoCaseloadSeedDataService.php @@ -257,7 +257,7 @@ private function createTasks( $id = $this->gateway->create( objectService: $objectService, registerId: $ids['register'], - schemaId: $ids['task'], + schemaId: $ids['caseTask'], data: $this->taskPayload(taskSeed: $taskSeed, caseId: $caseId, now: $now) ); diff --git a/lib/Service/KpiAggregationService.php b/lib/Service/KpiAggregationService.php index 672e4c6b3..9a16b0320 100644 --- a/lib/Service/KpiAggregationService.php +++ b/lib/Service/KpiAggregationService.php @@ -192,7 +192,7 @@ private function countCases(array $ids, array $filters): int { * @return int The count. */ private function countTasks(array $ids, array $filters): int { - return $this->countObjects(ids: $ids, schema: $ids['task'], filters: $filters); + return $this->countObjects(ids: $ids, schema: $ids['caseTask'], filters: $filters); }//end countTasks() /** @@ -450,13 +450,13 @@ private function toArray(mixed $object): array { /** * The register and schema ids these metrics read. * - * @return array{register: string, case: string, task: string}|null The ids, or null when unconfigured. + * @return array{register: string, case: string, caseTask: string}|null The ids, or null when unconfigured. */ private function ids(): ?array { $ids = [ 'register' => $this->appConfig->getValueString(Application::APP_ID, 'register', ''), 'case' => $this->appConfig->getValueString(Application::APP_ID, 'case_schema', ''), - 'task' => $this->appConfig->getValueString(Application::APP_ID, 'task_schema', ''), + 'caseTask' => $this->appConfig->getValueString(Application::APP_ID, 'task_schema', ''), ]; if (in_array('', $ids, true) === true) { diff --git a/lib/Service/Settings/SchemaSlugMap.php b/lib/Service/Settings/SchemaSlugMap.php index 3c426ccdc..bb068e454 100644 --- a/lib/Service/Settings/SchemaSlugMap.php +++ b/lib/Service/Settings/SchemaSlugMap.php @@ -48,7 +48,7 @@ class SchemaSlugMap { public const SLUG_TO_CONFIG_KEY = [ 'catalog' => 'catalogus_schema', 'case' => 'case_schema', - 'task' => 'task_schema', + 'caseTask' => 'task_schema', 'status' => 'status_schema', 'statusRecord' => 'status_record_schema', 'role' => 'role_schema', diff --git a/lib/Service/Settings/SchemaSlugResolver.php b/lib/Service/Settings/SchemaSlugResolver.php index ddacb27a6..e8a3fcaca 100644 --- a/lib/Service/Settings/SchemaSlugResolver.php +++ b/lib/Service/Settings/SchemaSlugResolver.php @@ -99,7 +99,7 @@ public function __construct( * and dropping the fallback would blank all three. * * @param object $schemaMapper The OpenRegister SchemaMapper. - * @param string $slug The schema slug, e.g. 'task'. + * @param string $slug The schema slug, e.g. 'caseTask'. * * @return object|null The live schema, or null when the slug does not resolve. * diff --git a/lib/Service/Support/JsonEncodedStringProperties.php b/lib/Service/Support/JsonEncodedStringProperties.php index 53273f11f..ae810371d 100644 --- a/lib/Service/Support/JsonEncodedStringProperties.php +++ b/lib/Service/Support/JsonEncodedStringProperties.php @@ -86,7 +86,7 @@ class JsonEncodedStringProperties { 'notificationChannel' => ['filters'], 'objection' => ['attachments'], 'resultType' => ['sourceDateArchiveProcedure'], - 'task' => ['checklist'], + 'caseTask' => ['checklist'], 'workflowTemplate' => ['nodePositions', 'steps', 'transitions'], ]; diff --git a/lib/Settings/dossiq_mock_register.json b/lib/Settings/dossiq_mock_register.json index 4822463d3..e4007d15a 100644 --- a/lib/Settings/dossiq_mock_register.json +++ b/lib/Settings/dossiq_mock_register.json @@ -13112,8 +13112,8 @@ } } }, - "task": { - "slug": "task", + "caseTask": { + "slug": "caseTask", "x-openregister-notifications": { "taskAssigned": { "trigger": { @@ -22676,7 +22676,7 @@ { "@self": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "slug": "task-voorbeeld-title-1-1", "id": "fab148e2-e14d-45ca-a0c2-58073d5e59b3" }, @@ -22695,7 +22695,7 @@ { "@self": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "slug": "task-voorbeeld-title-2-2", "id": "10d3503b-1077-45db-8516-ae4719f1a40f" }, @@ -22714,7 +22714,7 @@ { "@self": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "slug": "task-voorbeeld-title-3-3", "id": "0d4e9a65-535b-4ef8-bd93-453713b8fa78" }, diff --git a/lib/Settings/dossiq_register.json b/lib/Settings/dossiq_register.json index 8d8d8f54b..b68d52f99 100644 --- a/lib/Settings/dossiq_register.json +++ b/lib/Settings/dossiq_register.json @@ -1848,8 +1848,8 @@ }, "searchable": true }, - "task": { - "slug": "task", + "caseTask": { + "slug": "caseTask", "x-openregister-notifications": { "taskAssigned": { "trigger": { @@ -9230,7 +9230,7 @@ "documentType", "decisionType", "case", - "task", + "caseTask", "role", "result", "statusRecord", diff --git a/openspec/changes/namespace-the-case-task/proposal.md b/openspec/changes/namespace-the-case-task/proposal.md new file mode 100644 index 000000000..f9181d64e --- /dev/null +++ b/openspec/changes/namespace-the-case-task/proposal.md @@ -0,0 +1,35 @@ +# Namespace the case task + +## Why + +`task` was claimed by three apps: planninq, pipelinq and this one. They share +`description`, `priority` and `status` — what any task-shaped record carries, +and nothing that identifies the record. + +planninq's project task is the largest and keeps the bare slug. pipelinq's +became `crmTask`; this is the case task. + +## What the suite found that the grep did not + +The rename reached the descriptors and the store calls on the first pass, and +the suite still failed six times. Twice for the same reason, in two different +places: + +`KpiAggregationService::ids()` and `DemoCaseloadGateway::schemaIds()` each build +a local array keyed `'task'` and hand it round as `$ids['task']`. Renaming the +READERS without the BUILDERS left `$ids['caseTask']` resolving to null, which +surfaced as a `TypeError` on a nullable argument three frames away. + +Then PHPStan caught the array shapes that still declared `task: string` after +the builders moved. A grep for the slug would never have found either: the key +is not the slug, it just happened to be spelled like it. + +## The decoys + +`itemType` in `WorkQueueService`, `type` in `CaseReassignmentService`, +`BulkReassignModal`, `taskApi` and `dashboardHelpers` — all row and item type +labels for mixed lists, not schemas. + +`tests/e2e/ci-seed.sh` names the slug in its required-schema list and moved with +it. That list is checked after the import and exits before Playwright, so a miss +there reports every spec as not run. diff --git a/openspec/changes/namespace-the-case-task/specs/case-management/spec.md b/openspec/changes/namespace-the-case-task/specs/case-management/spec.md new file mode 100644 index 000000000..f688895b2 --- /dev/null +++ b/openspec/changes/namespace-the-case-task/specs/case-management/spec.md @@ -0,0 +1,33 @@ +# Case management + +## ADDED Requirements + +### Requirement: The case task is namespaced (REQ-CM-070) + +The case task schema SHALL be `caseTask` and SHALL NOT be `task`. planninq's +project task keeps the bare slug; pipelinq uses `crmTask`. + +The three claiming schemas share `description`, `priority` and `status` alone, +so all three are renamed apart rather than folded onto one owner. + +Every local schema-id map keyed by the slug SHALL move with it, including +`KpiAggregationService::ids()` and `DemoCaseloadGateway::schemaIds()`, together +with their declared array shapes. A reader renamed without its builder resolves +to null and fails several frames away, where the cause is no longer visible. + +`tests/e2e/ci-seed.sh` SHALL name the new slug in its required-schema list. + +The rename SHALL NOT touch `task` where it is a row or item type label: +`WorkQueueService`'s `itemType`, or the `type` key in +`CaseReassignmentService`, `BulkReassignModal`, `taskApi` and +`dashboardHelpers`. + +#### Scenario: The KPI counts still resolve their schema + +- **WHEN** the dashboard KPIs are computed +- **THEN** the task count resolves a schema id rather than null. + +#### Scenario: The demo caseload still seeds its tasks + +- **WHEN** the demo caseload is seeded +- **THEN** each task is created against a resolved schema id. diff --git a/openspec/changes/namespace-the-case-task/tasks.md b/openspec/changes/namespace-the-case-task/tasks.md new file mode 100644 index 000000000..7beeb0532 --- /dev/null +++ b/openspec/changes/namespace-the-case-task/tasks.md @@ -0,0 +1,16 @@ +# Tasks + +- [x] 1.1 Move the key, slug and register-list entry, and the manifest references + **files**: lib/Settings/dossiq_register.json, lib/Settings/dossiq_mock_register.json, src/manifest.json +- [x] 2.1 Move the slug in the config-key map and the JSON-encoded property map + **files**: lib/Service/Settings/SchemaSlugMap.php, lib/Service/Support/JsonEncodedStringProperties.php +- [x] 2.2 Move the schema-id map key in BOTH builders and their readers, and the declared array shapes + **files**: lib/Service/KpiAggregationService.php, lib/Service/DemoCaseloadGateway.php, lib/Service/DemoCaseloadReport.php, lib/Service/DemoCaseloadSeedDataService.php +- [x] 2.3 Follow the slug through the frontend object-type registration and store calls + **files**: src/store/store.js, src/store/modules/advice.js, src/store/modules/inspection.js, src/store/modules/enforcement.js, src/store/modules/workflow.js, src/views/widgets/TaskRemindersWidget.vue, src/views/widgets/MyTasksWidget.vue, src/components/flow/TaskWaitingCaseSection.vue, src/components/tabs/CaseTasksTab.vue +- [x] 3.1 Add the rename to the colliding-slug map + **files**: lib/Repair/RenameCollidingSchemaSlugs.php +- [x] 4.1 Move the slug in the e2e seed's required-schema list + **files**: tests/e2e/ci-seed.sh +- [x] 4.2 Repoint the test stubs, leaving item and row type labels alone + **files**: tests/Unit/, tests/vitest/ diff --git a/src/components/flow/TaskWaitingCaseSection.vue b/src/components/flow/TaskWaitingCaseSection.vue index 641761ae0..60431bb55 100644 --- a/src/components/flow/TaskWaitingCaseSection.vue +++ b/src/components/flow/TaskWaitingCaseSection.vue @@ -116,7 +116,7 @@ export default { try { this.task = - (await this.objectStore.fetchObject('task', taskId)) || null + (await this.objectStore.fetchObject('caseTask', taskId)) || null } catch { // An unreadable task renders nothing, same as a non-flow task. this.task = null diff --git a/src/components/tabs/CaseTasksTab.vue b/src/components/tabs/CaseTasksTab.vue index 87f89ede5..328d5977b 100644 --- a/src/components/tabs/CaseTasksTab.vue +++ b/src/components/tabs/CaseTasksTab.vue @@ -156,7 +156,7 @@ export default { } this.loading = true try { - const results = await this.objectStore.fetchCollection('task', { + const results = await this.objectStore.fetchCollection('caseTask', { case: this.resolvedCaseId, _limit: 50, }) diff --git a/src/manifest.json b/src/manifest.json index b672883d7..1037bcf96 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -723,7 +723,7 @@ "content": { "source": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "filter": { "assignee": "@me", "isTerminalStatus": false @@ -817,7 +817,7 @@ "content": { "source": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "filter": { "assignee": "@me", "isTerminalStatus": false, @@ -1329,7 +1329,7 @@ "icon": "ClipboardCheckOutline", "content": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "filter": { "case": "@objectId" }, @@ -2124,7 +2124,7 @@ "title": "Tasks", "config": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "allowSavedViews": true, "showViewAction": false, "actions": [ @@ -2158,7 +2158,7 @@ "title": "New task", "config": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "_note": "CREATE-FORM archetype — the task creation form (route /tasks/new, usually pre-seeded with ?caseId=). A not-yet-persisted task has no id, so no children, history, aggregates or content leaves apply: it is just the Data form. No audit sidebar tab either — there is no object to have a history. All richness lives on TaskDetail once the task exists.", "documentationUrl": "https://procest.conduction.nl", "widgets": [ @@ -2197,7 +2197,7 @@ "title": "Task", "config": { "register": "dossiq", - "schema": "task", + "schema": "caseTask", "_note": "ACTION-ITEM archetype — a work item on a case (task.case) with assignee/dueDate/priority/status and an inline checklist[]. The schema declares NO comms linkedTypes and has NO child schemas pointing at it, so it stays deliberately lean: the Data widget carries the who/when/status and the checklist, and lifecycleActions drive the status machine (available → active → completed / terminated). No files/notes/contacts/calendar leaves — a task carries none of that content; those live on the parent case. Audit history (status transitions) is a sidebar tab.", "documentationUrl": "https://procest.conduction.nl", "widgets": [ @@ -4053,7 +4053,7 @@ "source": { "kind": "objectCount", "register": "dossiq", - "schema": "task", + "schema": "caseTask", "groupBy": [ "status" ] @@ -4067,7 +4067,7 @@ "source": { "kind": "objectCount", "register": "dossiq", - "schema": "task", + "schema": "caseTask", "filter": { "deadline": { "lt": "now" diff --git a/src/store/modules/advice.js b/src/store/modules/advice.js index ab5b6202d..e06296b83 100644 --- a/src/store/modules/advice.js +++ b/src/store/modules/advice.js @@ -126,7 +126,7 @@ export const useAdviceStore = defineStore('advice', { // Create task for the adviseur if internal if (requestData.type === 'intern' && requestData.advisor) { - await objectStore.saveObject('task', { + await objectStore.saveObject('caseTask', { case: requestData.case, title: `Advies uitbrengen: ${requestData.subject || 'Adviesaanvraag'}`, description: requestData.questions || '', @@ -205,7 +205,7 @@ export const useAdviceStore = defineStore('advice', { }) // Create task for behandelaar - await objectStore.saveObject('task', { + await objectStore.saveObject('caseTask', { case: request.case, title: `Advies verlopen: ${request.subject || request.advisor}`, description: `Advies van ${request.advisor} is verlopen. Beoordeel of procedure kan doorgaan zonder dit advies.`, diff --git a/src/store/modules/enforcement.js b/src/store/modules/enforcement.js index 801bd3ecb..aa285eb63 100644 --- a/src/store/modules/enforcement.js +++ b/src/store/modules/enforcement.js @@ -290,7 +290,7 @@ export const useEnforcementStore = defineStore('enforcement', { async createBegunstigingTask(caseId, action) { try { const objectStore = useObjectStore() - return await objectStore.saveObject('task', { + return await objectStore.saveObject('caseTask', { case: caseId, title: 'Hercontrole uitvoeren', description: `Begunstigingstermijn van ${action.compliance_period} dagen is verlopen. Voer hercontrole uit voor ${action.intervention}.`, diff --git a/src/store/modules/inspection.js b/src/store/modules/inspection.js index ff5d332cb..0c24babe7 100644 --- a/src/store/modules/inspection.js +++ b/src/store/modules/inspection.js @@ -298,7 +298,7 @@ export const useInspectionStore = defineStore('inspection', { async createFollowUpTask(caseId, failedCount, reportId) { try { const objectStore = useObjectStore() - return await objectStore.saveObject('task', { + return await objectStore.saveObject('caseTask', { case: caseId, title: `Opvolging vereist: ${failedCount} afwijkingen geconstateerd`, description: `Inspectierapport bevat ${failedCount} niet-conforme punten. Beoordeel de afwijkingen en plan opvolging.`, diff --git a/src/store/modules/workflow.js b/src/store/modules/workflow.js index ffb13e686..b6202c359 100644 --- a/src/store/modules/workflow.js +++ b/src/store/modules/workflow.js @@ -781,7 +781,7 @@ export const useWorkflowStore = defineStore('workflow', { */ async dispatchCreateTaskAction(action, caseData) { const objectStore = useObjectStore() - await objectStore.saveObject('task', { + await objectStore.saveObject('caseTask', { title: action.title || t('dossiq', 'New task'), description: action.description || '', case: caseData.id, diff --git a/src/store/store.js b/src/store/store.js index f09d5bd6a..f60debf97 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -19,7 +19,7 @@ export async function initializeStores() { } if (config.register && config.task_schema) { objectStore.registerObjectType( - 'task', + 'caseTask', config.task_schema, config.register, ) diff --git a/src/views/widgets/MyTasksWidget.vue b/src/views/widgets/MyTasksWidget.vue index fc8cd5718..31e2dad39 100644 --- a/src/views/widgets/MyTasksWidget.vue +++ b/src/views/widgets/MyTasksWidget.vue @@ -132,7 +132,7 @@ export default { // 32 tasks while `assignee=rbac-editor` returned the 2 that match. So // this widget was fetching EVERY user's tasks and filtering only by // status, which is not what a widget called "My Tasks" may show. - const results = await this.objectStore.fetchCollection('task', { + const results = await this.objectStore.fetchCollection('caseTask', { assignee: currentUser, isTerminalStatus: false, _limit: 7, diff --git a/src/views/widgets/TaskRemindersWidget.vue b/src/views/widgets/TaskRemindersWidget.vue index 960c44bc1..8aef4d207 100644 --- a/src/views/widgets/TaskRemindersWidget.vue +++ b/src/views/widgets/TaskRemindersWidget.vue @@ -149,7 +149,7 @@ export default { const currentUser = getCurrentUser()?.uid || '' // Bare field names, not `_filters[x]`: that form is inert and this // widget was reading every user's tasks. See MyTasksWidget. - const tasks = await this.objectStore.fetchCollection('task', { + const tasks = await this.objectStore.fetchCollection('caseTask', { assignee: currentUser, isTerminalStatus: false, _limit: 100, diff --git a/tests/Unit/Controller/StoreControllerTest.php b/tests/Unit/Controller/StoreControllerTest.php index 697b1f28c..2465305dd 100644 --- a/tests/Unit/Controller/StoreControllerTest.php +++ b/tests/Unit/Controller/StoreControllerTest.php @@ -336,7 +336,7 @@ public function testAMixedItemInstallsOnlyTheConfigurationHalf(): void { 'slug' => 'mixed-item', 'components' => [ ['schema' => 'caseType', 'object' => ['title' => 'Handhaving']], - ['schema' => 'task', 'object' => ['title' => 'Injected task']], + ['schema' => 'caseTask', 'object' => ['title' => 'Injected task']], ], ] ); diff --git a/tests/Unit/Flow/AskPersonHeartbeatRecoveryTest.php b/tests/Unit/Flow/AskPersonHeartbeatRecoveryTest.php index f36f4d23f..004e98110 100644 --- a/tests/Unit/Flow/AskPersonHeartbeatRecoveryTest.php +++ b/tests/Unit/Flow/AskPersonHeartbeatRecoveryTest.php @@ -311,7 +311,7 @@ public function find( $settings = $this->createMock(SettingsService::class); $settings->method('getObjectService')->willReturn($objectService); $settings->method('getConfigValue')->willReturnCallback( - static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'task') + static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'caseTask') ); $l10n = $this->createMock(IL10N::class); diff --git a/tests/Unit/Flow/CaseFlowDeclarationTest.php b/tests/Unit/Flow/CaseFlowDeclarationTest.php index edd4d65db..0b64b487b 100644 --- a/tests/Unit/Flow/CaseFlowDeclarationTest.php +++ b/tests/Unit/Flow/CaseFlowDeclarationTest.php @@ -679,7 +679,7 @@ public function testTheBlocksCaseCalculationUsesSupportedOperatorsOnly(): void { true ); - $calc = ($register['components']['schemas']['task']['x-openregister-calculations']['blocksCase'] ?? null); + $calc = ($register['components']['schemas']['caseTask']['x-openregister-calculations']['blocksCase'] ?? null); $this->assertIsArray($calc, 'The task must declare when it is blocking a case.'); $this->assertTrue(($calc['materialise'] ?? false), 'It must be materialised, or it cannot be filtered server-side.'); @@ -934,7 +934,7 @@ public function testTheTaskSchemaDeclaresItsRunAndNodeFields(): void { $path = __DIR__ . '/../../../lib/Settings/dossiq_register.json'; $register = json_decode((string)file_get_contents($path), true); - $properties = ($register['components']['schemas']['task']['properties'] ?? []); + $properties = ($register['components']['schemas']['caseTask']['properties'] ?? []); foreach (['flowRun', 'flowNode'] as $field) { $this->assertArrayHasKey( diff --git a/tests/Unit/Flow/DossiqAskPersonNodeTest.php b/tests/Unit/Flow/DossiqAskPersonNodeTest.php index 6a37d45b9..bcb602723 100644 --- a/tests/Unit/Flow/DossiqAskPersonNodeTest.php +++ b/tests/Unit/Flow/DossiqAskPersonNodeTest.php @@ -128,7 +128,7 @@ public function find( $settings = $this->createMock(SettingsService::class); $settings->method('getObjectService')->willReturn($objectService); $settings->method('getConfigValue')->willReturnCallback( - static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'task') + static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'caseTask') ); $l10n = $this->createMock(IL10N::class); @@ -575,7 +575,7 @@ public function saveObject(array $object, string $register, string $schema): mix $settings = $this->createMock(SettingsService::class); $settings->method('getObjectService')->willReturn($objectService); $settings->method('getConfigValue')->willReturnCallback( - static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'task') + static fn (string $key): string => ($key === 'register' ? 'dossiq' : 'caseTask') ); $l10n = $this->createMock(IL10N::class); diff --git a/tests/Unit/Repair/MigrateSchemaApplicationIdTest.php b/tests/Unit/Repair/MigrateSchemaApplicationIdTest.php index 7d3083c2c..6d19b02af 100644 --- a/tests/Unit/Repair/MigrateSchemaApplicationIdTest.php +++ b/tests/Unit/Repair/MigrateSchemaApplicationIdTest.php @@ -75,7 +75,7 @@ function (string $sql, array $params) use (&$written): int { */ public function testMovesASchemaThatHasNoTwin(): void { $written = []; - $db = $this->db([], ['case', 'task'], $written); + $db = $this->db([], ['case', 'caseTask'], $written); $step = new MigrateSchemaApplicationId($db, $this->createMock(LoggerInterface::class)); @@ -85,7 +85,7 @@ public function testMovesASchemaThatHasNoTwin(): void { $step->run($output); self::assertSame( - [['dossiq', 'procest', 'case'], ['dossiq', 'procest', 'task']], + [['dossiq', 'procest', 'case'], ['dossiq', 'procest', 'caseTask']], $written ); @@ -102,7 +102,7 @@ public function testMovesASchemaThatHasNoTwin(): void { */ public function testRefusesASlugThatWouldCollide(): void { $written = []; - $db = $this->db(['case'], ['case', 'task'], $written); + $db = $this->db(['case'], ['case', 'caseTask'], $written); $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::once())->method('warning')->with(self::stringContains('refusing to create a duplicate')); @@ -114,7 +114,7 @@ public function testRefusesASlugThatWouldCollide(): void { $step->run($output); - self::assertSame([['dossiq', 'procest', 'task']], $written); + self::assertSame([['dossiq', 'procest', 'caseTask']], $written); }//end testRefusesASlugThatWouldCollide() diff --git a/tests/Unit/Service/CaseReassignmentServiceTest.php b/tests/Unit/Service/CaseReassignmentServiceTest.php index e62565cb2..f6ee850af 100644 --- a/tests/Unit/Service/CaseReassignmentServiceTest.php +++ b/tests/Unit/Service/CaseReassignmentServiceTest.php @@ -81,7 +81,7 @@ static function (string $key, string $default = ''): string { $map = [ 'register' => 'dossiq', 'case_schema' => 'case', - 'task_schema' => 'task', + 'task_schema' => 'caseTask', 'status_type_schema' => 'statusType', ]; return ($map[$key] ?? $default); @@ -120,7 +120,7 @@ function (string $reg, string $schema, array $filters) { ['id' => 'c2', 'title' => 'Closed', 'assignee' => 'jan', 'status' => 'st-final', 'caseType' => 'vth'], ]; } - if ($schema === 'task') { + if ($schema === 'caseTask') { return [ ['id' => 't1', 'title' => 'Open task', 'assignee' => 'jan', 'status' => 'active', 'case' => 'c1'], ['id' => 't2', 'title' => 'Done task', 'assignee' => 'jan', 'status' => 'completed', 'case' => 'c1'], @@ -155,7 +155,7 @@ function (string $reg, string $schema, array $filters) { ['id' => 'c2', 'assignee' => 'jan', 'status' => 'open', 'caseType' => 'objectionProceeding'], ]; } - if ($schema === 'task') { + if ($schema === 'caseTask') { return [ ['id' => 't1', 'assignee' => 'jan', 'status' => 'active', 'case' => 'c1'], ['id' => 't2', 'assignee' => 'jan', 'status' => 'active', 'case' => 'c2'], @@ -200,7 +200,7 @@ function (string $reg, string $schema, array $filters) { if ($schema === 'case') { return [['id' => 'c1', 'title' => 'Case 1', 'assignee' => 'jan', 'status' => 'open', 'caseType' => 'vth', 'activity' => '[]']]; } - if ($schema === 'task') { + if ($schema === 'caseTask') { return [['id' => 't1', 'title' => 'Task 1', 'assignee' => 'jan', 'status' => 'active', 'case' => 'c1']]; } return []; @@ -257,7 +257,7 @@ function (string $reg, string $schema, array $filters) { ['id' => 'c2', 'assignee' => 'jan', 'status' => 'open', 'caseType' => 'vth', 'activity' => '[]'], ]; } - if ($schema === 'task') { + if ($schema === 'caseTask') { return []; } return []; diff --git a/tests/Unit/Service/KpiAggregationServiceTest.php b/tests/Unit/Service/KpiAggregationServiceTest.php index 2bdd92a1a..cf2ede49c 100644 --- a/tests/Unit/Service/KpiAggregationServiceTest.php +++ b/tests/Unit/Service/KpiAggregationServiceTest.php @@ -53,7 +53,7 @@ final class KpiAggregationServiceTest extends TestCase { * * @var array */ - private const IDS = ['register' => '23', 'case' => '172', 'task' => '173']; + private const IDS = ['register' => '23', 'case' => '172', 'caseTask' => '173']; /** * Today, as the service formats it. @@ -379,7 +379,7 @@ static function (string $app, string $key, string $default = '', bool $lazy = fa return match ($key) { 'register' => self::IDS['register'], 'case_schema' => self::IDS['case'], - 'task_schema' => self::IDS['task'], + 'task_schema' => self::IDS['caseTask'], default => $default, }; } diff --git a/tests/Unit/Service/SelectionReassignmentServiceTest.php b/tests/Unit/Service/SelectionReassignmentServiceTest.php index 6efa465b5..3573ccff5 100644 --- a/tests/Unit/Service/SelectionReassignmentServiceTest.php +++ b/tests/Unit/Service/SelectionReassignmentServiceTest.php @@ -231,7 +231,7 @@ private function selectionService(?object $objectService): SelectionReassignment $this->settingsService->method('getObjectService')->willReturn($objectService); $this->settingsService->method('getConfigValue')->willReturnCallback( static function (string $key, string $default = ''): string { - $map = ['register' => 'dossiq', 'case_schema' => 'case', 'task_schema' => 'task']; + $map = ['register' => 'dossiq', 'case_schema' => 'case', 'task_schema' => 'caseTask']; return ($map[$key] ?? $default); } diff --git a/tests/Unit/Service/Settings/SchemaKeyReconcilerTest.php b/tests/Unit/Service/Settings/SchemaKeyReconcilerTest.php index eacd99962..839b47cd0 100644 --- a/tests/Unit/Service/Settings/SchemaKeyReconcilerTest.php +++ b/tests/Unit/Service/Settings/SchemaKeyReconcilerTest.php @@ -90,8 +90,8 @@ public function testRegisterScopedSlugWinsOverTheFirstGlobalMatch(): void { registerSchemaIds: [165, 166, 172, 173], // The unscoped lookup answers with the FOREIGN row, exactly as the // live instance did. If the fix regresses, this is what gets written. - globalBySlug: ['task' => self::TASK_ROWS['foreign-internetaak']], - scopedBySlug: ['task' => self::TASK_ROWS['dossiq']], + globalBySlug: ['caseTask' => self::TASK_ROWS['foreign-internetaak']], + scopedBySlug: ['caseTask' => self::TASK_ROWS['dossiq']], ); $reconciler->reconcile(); @@ -141,8 +141,8 @@ public function testUnconfiguredRegisterKeepsTheUnscopedBehaviour(): void { $reconciler = $this->reconciler( appConfig: $appConfig, registerSchemaIds: [], - globalBySlug: ['task' => self::TASK_ROWS['foreign-internetaak']], - scopedBySlug: ['task' => self::TASK_ROWS['dossiq']], + globalBySlug: ['caseTask' => self::TASK_ROWS['foreign-internetaak']], + scopedBySlug: ['caseTask' => self::TASK_ROWS['dossiq']], ); $reconciler->reconcile(); @@ -168,7 +168,7 @@ public function testAlreadyCorrectKeyIsNotRewritten(): void { appConfig: $appConfig, registerSchemaIds: [173], globalBySlug: [], - scopedBySlug: ['task' => self::TASK_ROWS['dossiq']], + scopedBySlug: ['caseTask' => self::TASK_ROWS['dossiq']], ); $reconciler->reconcile(); diff --git a/tests/Unit/Service/Settings/SchemaSlugResolverTest.php b/tests/Unit/Service/Settings/SchemaSlugResolverTest.php index 9d65addcc..e2c14f35b 100644 --- a/tests/Unit/Service/Settings/SchemaSlugResolverTest.php +++ b/tests/Unit/Service/Settings/SchemaSlugResolverTest.php @@ -52,11 +52,11 @@ public function testResolvesInsideOurOwnRegister(): void { $resolver = $this->resolver( registerId: '23', registerSchemaIds: [165, 166, 172, 173], - globalBySlug: ['task' => 52], - scopedBySlug: ['task' => 173], + globalBySlug: ['caseTask' => 52], + scopedBySlug: ['caseTask' => 173], ); - $schema = $resolver->resolve($this->schemaMapper, 'task'); + $schema = $resolver->resolve($this->schemaMapper, 'caseTask'); $this->assertNotNull($schema, 'The slug must resolve.'); $this->assertSame( @@ -112,11 +112,11 @@ public function testUnconfiguredRegisterKeepsTheUnscopedAnswer(): void { $resolver = $this->resolver( registerId: '', registerSchemaIds: [], - globalBySlug: ['task' => 52], - scopedBySlug: ['task' => 173], + globalBySlug: ['caseTask' => 52], + scopedBySlug: ['caseTask' => 173], ); - $schema = $resolver->resolve($this->schemaMapper, 'task'); + $schema = $resolver->resolve($this->schemaMapper, 'caseTask'); $this->assertNotNull($schema); $this->assertSame(52, $schema->getId()); diff --git a/tests/Unit/Service/SubstitutionServiceTest.php b/tests/Unit/Service/SubstitutionServiceTest.php index c20d511ee..57311fd21 100644 --- a/tests/Unit/Service/SubstitutionServiceTest.php +++ b/tests/Unit/Service/SubstitutionServiceTest.php @@ -107,7 +107,7 @@ static function (string $key, string $default = ''): string { 'register' => 'dossiq', 'substitution_schema' => 'substitution', 'case_schema' => 'case', - 'task_schema' => 'task', + 'task_schema' => 'caseTask', 'status_type_schema' => 'statusType', ]; return ($map[$key] ?? $default); @@ -302,7 +302,7 @@ function (string $reg, string $schema, array $filters) use ($sub) { ['id' => 'case-c', 'caseType' => 'objectionProceeding', 'assignee' => 'jan', 'status' => 'st-final'], ]; } - if ($schema === 'task') { + if ($schema === 'caseTask') { return []; } return []; diff --git a/tests/Unit/Service/WorkQueueServiceTest.php b/tests/Unit/Service/WorkQueueServiceTest.php index efe12cf9b..c7312ce06 100644 --- a/tests/Unit/Service/WorkQueueServiceTest.php +++ b/tests/Unit/Service/WorkQueueServiceTest.php @@ -57,7 +57,7 @@ static function (string $key): string { return match ($key) { 'register' => 'dossiq', 'case_schema' => 'case', - 'task_schema' => 'task', + 'task_schema' => 'caseTask', 'termijn_instance_schema' => 'deadlineInstance', default => '', }; @@ -267,7 +267,7 @@ public function testComputeQueueExcludesTerminalTasksAndSortsByScore(): void { 'deadline' => '2026-08-01', 'priority' => 'normal', ]); - $this->objects->saveObject('task', [ + $this->objects->saveObject('caseTask', [ 'id' => 'task-1', 'title' => 'Overdue task', 'assignee' => 'jan', @@ -275,7 +275,7 @@ public function testComputeQueueExcludesTerminalTasksAndSortsByScore(): void { 'dueDate' => '2026-07-01', 'priority' => 'normal', ]); - $this->objects->saveObject('task', [ + $this->objects->saveObject('caseTask', [ 'id' => 'task-2', 'title' => 'Completed task', 'assignee' => 'jan', diff --git a/tests/Unit/Settings/ShippedEnumValueConformanceTest.php b/tests/Unit/Settings/ShippedEnumValueConformanceTest.php index 060309173..a31b14243 100644 --- a/tests/Unit/Settings/ShippedEnumValueConformanceTest.php +++ b/tests/Unit/Settings/ShippedEnumValueConformanceTest.php @@ -65,7 +65,7 @@ final class ShippedEnumValueConformanceTest extends TestCase { */ private const COLLECTION_SCHEMA = [ 'cases' => 'case', - 'tasks' => 'task', + 'tasks' => 'caseTask', 'caseTypes' => 'caseType', 'statusTypes' => 'statusType', 'roleTypes' => 'roleType', diff --git a/tests/Unit/Settings/WorkflowEngineSchemaTest.php b/tests/Unit/Settings/WorkflowEngineSchemaTest.php index a61a0ec30..347cb8020 100644 --- a/tests/Unit/Settings/WorkflowEngineSchemaTest.php +++ b/tests/Unit/Settings/WorkflowEngineSchemaTest.php @@ -134,7 +134,7 @@ public function testWorkflowTemplateSchemaHasRequiredProperties(): void { */ public function testCoreSchemasPresentAfterWorkflowEngineMigration(): void { $schemas = $this->registerData['components']['schemas']; - $required = ['case', 'task', 'caseType', 'statusType', 'roleType', 'workflowTemplate']; + $required = ['case', 'caseTask', 'caseType', 'statusType', 'roleType', 'workflowTemplate']; foreach ($required as $schemaName) { $this->assertArrayHasKey( diff --git a/tests/Unit/Settings/WorkflowGuardConformanceTest.php b/tests/Unit/Settings/WorkflowGuardConformanceTest.php index 510993f46..6b3c923ec 100644 --- a/tests/Unit/Settings/WorkflowGuardConformanceTest.php +++ b/tests/Unit/Settings/WorkflowGuardConformanceTest.php @@ -198,7 +198,7 @@ public function searchObjectsBySlug(string $register, string $schema, array $fil $settings->method('getConfigValue')->willReturnCallback( static fn (string $key): string => match ($key) { 'register' => 'dossiq', - 'task_schema' => 'task', + 'task_schema' => 'caseTask', default => 'other', } ); @@ -230,7 +230,7 @@ public function searchObjectsBySlug(string $register, string $schema, array $fil */ public function rowsFor(string $schema): array { return match ($schema) { - 'task' => $this->tasks, + 'caseTask' => $this->tasks, default => [], }; } diff --git a/tests/e2e/case-flow-live-journeys.spec.ts b/tests/e2e/case-flow-live-journeys.spec.ts index 990c35ea6..37cd8dc7d 100644 --- a/tests/e2e/case-flow-live-journeys.spec.ts +++ b/tests/e2e/case-flow-live-journeys.spec.ts @@ -174,9 +174,9 @@ async function updateObject( async function completeTask(api: APIRequestContext, taskId: string): Promise { const current = await getJson(api, `${OR}/objects/dossiq/task/${taskId}`) if (String(current.status ?? '') === 'available') { - await updateObject(api, 'task', taskId, { status: 'active' }) + await updateObject(api, 'caseTask', taskId, { status: 'active' }) } - await updateObject(api, 'task', taskId, { status: 'completed' }) + await updateObject(api, 'caseTask', taskId, { status: 'completed' }) } async function runsForCase(api: APIRequestContext, caseId: string): Promise { diff --git a/tests/e2e/ci-seed.sh b/tests/e2e/ci-seed.sh index dc6368608..b70a78fc0 100755 --- a/tests/e2e/ci-seed.sh +++ b/tests/e2e/ci-seed.sh @@ -198,7 +198,7 @@ required = { # components.schemas..slug — every one of these is exercised by # tests/e2e/helpers/fixtures.ts (createObject / seedCase / seedStateMachine # / ensureCaseType / cleanupRunObjects). - 'schemas': ['case', 'caseType', 'statusType', 'workflowTemplate', 'task', 'complaint'], + 'schemas': ['case', 'caseType', 'statusType', 'workflowTemplate', 'caseTask', 'complaint'], }[kind] with open(path) as fh: raw = fh.read() diff --git a/tests/e2e/demo-caseload.spec.ts b/tests/e2e/demo-caseload.spec.ts index e95c72d8c..d739e722f 100644 --- a/tests/e2e/demo-caseload.spec.ts +++ b/tests/e2e/demo-caseload.spec.ts @@ -108,7 +108,7 @@ test.describe('Demo caseload surfaces', () => { ) } - const open = await createObject(api, token, 'task', { + const open = await createObject(api, token, 'caseTask', { title: OPEN_TASK, case: caseId, assignee: 'admin', @@ -117,7 +117,7 @@ test.describe('Demo caseload surfaces', () => { }) openTaskId = objectId(open) - await createObject(api, token, 'task', { + await createObject(api, token, 'caseTask', { title: DONE_TASK, case: caseId, assignee: 'admin', @@ -131,7 +131,7 @@ test.describe('Demo caseload surfaces', () => { }) test('a completed task reads as terminal, so open-work filters exclude it', async () => { - const tasks = await listObjects(api, 'task') + const tasks = await listObjects(api, 'caseTask') const open = tasks.find((t) => t.title === OPEN_TASK) const done = tasks.find((t) => t.title === DONE_TASK) @@ -152,7 +152,7 @@ test.describe('Demo caseload surfaces', () => { }) test('daysUntilDue is returned when calculations are extended', async () => { - const tasks = await listObjects(api, 'task', { _extend: 'calculations' }) + const tasks = await listObjects(api, 'caseTask', { _extend: 'calculations' }) const open = tasks.find((t) => t.title === OPEN_TASK) expect(open, `seeded task "${OPEN_TASK}" is missing`).toBeTruthy() diff --git a/tests/e2e/helpers/fixtures.ts b/tests/e2e/helpers/fixtures.ts index a4108e732..59703600b 100644 --- a/tests/e2e/helpers/fixtures.ts +++ b/tests/e2e/helpers/fixtures.ts @@ -73,7 +73,7 @@ const TRASH_BASE = '/index.php/apps/openregister/api/deleted' export const FIXTURE_SCHEMAS = [ 'statusRecord', 'caseProperty', - 'task', + 'caseTask', 'consultation', 'objectionProceeding', 'case', diff --git a/tests/l10n/check-l10n.js b/tests/l10n/check-l10n.js index 346f691e4..68790ca17 100644 --- a/tests/l10n/check-l10n.js +++ b/tests/l10n/check-l10n.js @@ -192,7 +192,7 @@ for (const file of files) { // (spdx, change, adr), never rendered, and translating it would be nonsense. // --------------------------------------------------------------------------- const MANIFEST_TEXT_FIELDS = new Set([ - 'title', 'body', 'task', 'label', 'description', + 'title', 'body', 'caseTask', 'label', 'description', 'emptyText', 'placeholder', 'subtitle', 'helpText', ]) diff --git a/tests/vitest/searchableSchemas.spec.js b/tests/vitest/searchableSchemas.spec.js index 7e3974978..a30e9c2ed 100644 --- a/tests/vitest/searchableSchemas.spec.js +++ b/tests/vitest/searchableSchemas.spec.js @@ -21,7 +21,7 @@ const REGISTER_PATH = path.resolve( ) const MANIFEST_PATH = path.resolve(__dirname, '../../src/manifest.json') -const EXPECTED_SEARCHABLE_SLUGS = ['case', 'task', 'objectionProceeding', 'beroep'] +const EXPECTED_SEARCHABLE_SLUGS = ['case', 'caseTask', 'objectionProceeding', 'beroep'] const loadJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))