diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 80e2fac6e..23f35638e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -31,6 +31,57 @@ npm run dev # Development build (watch mode) npm run build # Production build ``` +## Demo data + +The dashboard widgets, the Tasks page and My Work all filter on the current +user, so a fresh instance shows empty panels even when the register imported +cleanly. Seed a working caseload: + +```bash +occ dossiq:demo:seed # 18 cases across the four case types, 32 tasks +occ dossiq:demo:seed --verify-only # report the buckets without creating anything +``` + +Safe to re-run. A case whose title is already present is skipped, and its tasks +with it. + +The dates in `lib/Settings/demo_caseload_seed_data.json` are relative day +offsets, resolved when the seed runs, so the overdue and at-risk buckets stay +correct however long the file sits in the repo. Give a case either +`deadlineInDays` or `startInDays`, never both: `deadline` is a materialised +calculation over `startDate` plus the case type's `processingDeadline`, so the +seed reaches a deadline by backdating the start date. + +`--verify-only` counts what the dashboard will read by querying the register +back, not by counting the seed file. A count taken from the input agrees with +the input whatever the materialiser did. + +## Demo email + +The Nextcloud dashboard's mail widget needs a mail server and an account. The +shared dev environment ships both. + +```bash +docker compose -f .github/docker-compose.yml --profile mail up -d greenmail +bash .github/docker/mail/seed-mail.sh localhost 3025 + +occ app:enable mail +occ mail:account:create admin "Gemeente Demo" admin@test.local \ + conduction-greenmail 3143 none admin@test.local admin@test.local \ + conduction-greenmail 3025 none admin@test.local admin@test.local +occ mail:account:sync 1 +``` + +Then put the widgets on the dashboard: + +```bash +occ user:setting admin dashboard layout \ + "procest_my_tasks_widget,procest_task_reminders_widget,mail-unread,procest_overdue_cases_widget,procest_deadline_alerts_widget,procest_cases_overview_widget" +``` + +The widget ids still carry the `procest_` prefix. Renaming them would drop the +widget out of every layout that already names it, so they stay. + ## Code Quality ```bash diff --git a/appinfo/info.xml b/appinfo/info.xml index 9125260f9..c01f47ed8 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -48,7 +48,7 @@ Vrij en open source onder de EUPL-1.2-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.3.8-unstable.20260831052324 + 0.3.11-unstable.20260901211251 EUPL-1.2 Conduction Dossiq @@ -128,12 +128,23 @@ Vrij en open source onder de EUPL-1.2-licentie. OCA\Dossiq\BackgroundJob\AdviceDeadlineJob - OCA\Dossiq\BackgroundJob\VergaderingDeadlineJob + OCA\Dossiq\BackgroundJob\WOODeadlineCheckJob OCA\Dossiq\BackgroundJob\BezwaarTermijnJob OCA\Dossiq\BackgroundJob\DsoDeadlineJob OCA\Dossiq\BackgroundJob\ResetMonthlyQuotasJob - OCA\Dossiq\BackgroundJob\DailyDeadlineScanJob + OCA\Dossiq\BackgroundJob\InboundEmailJob OCA\Dossiq\BackgroundJob\EmailPdfRetryJob OCA\Dossiq\BackgroundJob\SentimentAnalysisJob @@ -270,6 +281,21 @@ Vrij en open source onder de EUPL-1.2-licentie. an install without it keeps working on its local routes. --> OCA\Dossiq\Repair\MigrateParafeerroutesToDecidiq + + OCA\Dossiq\Repair\RaiseInFlightParaferingenInDecidiq OCA\Dossiq\Repair\RenameDutchDirectionValues OCA\Dossiq\Repair\MigrateArchivalToOpenRegister + + OCA\Dossiq\Repair\MigratePartnersToOrganisations OCA\Dossiq\Repair\SeedKccWerkplekData OCA\Dossiq\Repair\BackfillInformatieobjectMetadata OCA\Dossiq\Repair\LinkInFlightContractDecisionsRepair @@ -333,6 +369,24 @@ Vrij en open source onder de EUPL-1.2-licentie. exists to perform. --> OCA\Dossiq\Repair\RenameDutchValues + + OCA\Dossiq\Repair\RealignLhsActorTypeVocabulary OCA\Dossiq\Repair\BackfillAdviceRequestObjection + + OCA\Dossiq\Repair\ArmTermijnEngineTimers + + @@ -77,6 +85,9 @@ + + + diff --git a/scripts/build-l10n-js.js b/scripts/build-l10n-js.js index 176ef519c..335b08d2c 100644 --- a/scripts/build-l10n-js.js +++ b/scripts/build-l10n-js.js @@ -101,6 +101,9 @@ function renderJs(id, translations, pluralForm) { ].join('\n') } +/** + * + */ function main() { const check = process.argv.includes('--check') const id = appId() diff --git a/scripts/check-integration-parity.js b/scripts/check-integration-parity.js index 0c44a7320..9635810a2 100755 --- a/scripts/check-integration-parity.js +++ b/scripts/check-integration-parity.js @@ -10,11 +10,11 @@ * --------------------------------------- * A leaf has TWO faces (ADR-019 AD-11/AD-13, ADR-066 decisions 4 and 7): * - * * a SERVER face — either a `LeafDescriptor` contributed through + * a SERVER face — either a `LeafDescriptor` contributed through * `RegisterLeafProvidersEvent`, or an `IntegrationProvider` registered on * OpenRegister's `IntegrationRegistry`. This face is what the * `openregister.integrations.leaves` capability advertises. - * * a JS face — a `registerIntegration({ id, … })` call that mounts the + * a JS face — a `registerIntegration({ id, … })` call that mounts the * render pair on `window.OCA.OpenRegister.integrations`. * * The two are correlated ONLY by a shared `id`. Nothing at runtime notices when @@ -132,7 +132,7 @@ function collectFiles(root, test, maxDepth = 10) { let entries try { entries = fs.readdirSync(dir, { withFileTypes: true }) - } catch (e) { + } catch { return } for (const ent of entries) { @@ -289,19 +289,15 @@ function resolvePhp(expr, localConsts, globalConsts) { } m = /^(?:self|static)::([A-Z0-9_]+)$/.exec(e) if (m !== null) { - return Object.prototype.hasOwnProperty.call(localConsts, m[1]) - ? localConsts[m[1]] - : null + return Object.hasOwn(localConsts, m[1]) ? localConsts[m[1]] : null } m = /^([A-Za-z_][A-Za-z0-9_]*)::([A-Z0-9_]+)$/.exec(e) if (m !== null) { const key = `${m[1]}::${m[2]}` - if (Object.prototype.hasOwnProperty.call(FOREIGN_CONSTANTS, key) === true) { + if (Object.hasOwn(FOREIGN_CONSTANTS, key) === true) { return FOREIGN_CONSTANTS[key] } - return Object.prototype.hasOwnProperty.call(globalConsts, key) - ? globalConsts[key] - : null + return Object.hasOwn(globalConsts, key) ? globalConsts[key] : null } if (e.startsWith('[') === true) { const members = splitTopLevel(balanced(e, 0)) @@ -351,8 +347,8 @@ function phpLocalConsts(src) { * * Two shapes count as a server face, because both are how a leaf reaches the * `openregister.integrations.leaves` capability: - * * `new LeafDescriptor(…)` — the ADR-066 collect-event contribution; - * * an `IntegrationProvider` (extends `AbstractIntegrationProvider` or + * `new LeafDescriptor(…)` — the ADR-066 collect-event contribution; + * an `IntegrationProvider` (extends `AbstractIntegrationProvider` or * implements `IntegrationProviderInterface`) whose `getId()` returns a * literal — the `IntegrationRegistry::addProvider()` path. * @@ -371,7 +367,7 @@ function collectServerFaces() { let src try { src = fs.readFileSync(file, 'utf8') - } catch (e) { + } catch { continue } sources.set(file, src) @@ -504,7 +500,7 @@ function resolveJs(expr, locals) { } if ( /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e) === true - && Object.prototype.hasOwnProperty.call(locals, e) === true + && Object.hasOwn(locals, e) === true ) { return locals[e] } @@ -588,7 +584,7 @@ function collectJsRegistrations() { let src try { src = fs.readFileSync(file, 'utf8') - } catch (e) { + } catch { continue } // BOTH SUPPORTED REGISTRATION APIs, NOT ONE. @@ -737,7 +733,7 @@ function collectOrSchemas() { let doc try { doc = JSON.parse(fs.readFileSync(file, 'utf8')) - } catch (e) { + } catch { continue } const declared = doc && doc.components && doc.components.schemas @@ -959,7 +955,7 @@ function main() { continue } counts.R6++ - if (Object.prototype.hasOwnProperty.call(schemas, slug) === false) { + if (Object.hasOwn(schemas, slug) === false) { failures.push( `✗ [R6 offlineConfig] leaf offlineConfig.${key} = "${slug}" (${r.file}) names a ` + `schema this repo does not declare in lib/Settings/** — the leaf would query a ` @@ -996,12 +992,10 @@ function main() { .map(([rule, n]) => `${rule}:${n}`) .join(' ') if (failures.length === 0) { - // eslint-disable-next-line no-console console.log( `✓ integration parity: ${scope} — all rules pass (assertions run per rule: ${perRule})`, ) if (Object.values(counts).every((n) => n === 0) === true) { - // eslint-disable-next-line no-console console.error( '✗ integration parity: every rule had ZERO subject matter, yet gate-24 selected this ' + 'repo as one that registers leaves. That contradiction means this checker failed to ' @@ -1014,15 +1008,14 @@ function main() { // The header carries no `✗` on purpose: gate-24 counts violations by // grepping `^✗` in this log, so every violation — and only a violation — // starts a line with it. - // eslint-disable-next-line no-console + console.error( `integration parity gate FAILED — ${failures.length} violation(s) over ${scope}:`, ) for (const f of failures) { - // eslint-disable-next-line no-console console.error(f) } - // eslint-disable-next-line no-console + console.error(`\nAssertions run per rule: ${perRule}`) process.exit(1) } diff --git a/scripts/check-schema-l10n.js b/scripts/check-schema-l10n.js index 8a860b330..3c4b2626f 100644 --- a/scripts/check-schema-l10n.js +++ b/scripts/check-schema-l10n.js @@ -113,6 +113,9 @@ function collect(node, where, sink) { for (const value of Object.values(node)) collect(value, where, sink) } +/** + * + */ function main() { const update = process.argv.includes('--update') const list = process.argv.includes('--list') diff --git a/scripts/check-single-dexie.js b/scripts/check-single-dexie.js new file mode 100755 index 000000000..5c78d59b5 --- /dev/null +++ b/scripts/check-single-dexie.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node +// SPDX-License-Identifier: EUPL-1.2 +// Copyright (C) 2026 Conduction B.V. +// +// check-single-dexie.js: the Dexie singleton guard. +// +// WHY THIS EXISTS +// +// Dexie refuses to initialise twice in one page: when a second copy loads +// at a different version it throws "Two different versions of Dexie loaded +// in the same app" at module init, before the SPA mounts. Nextcloud loads +// openregister's integration-global script on EVERY page of the instance, +// next to this app's own bundles, so a version drift between any two built +// chunks on the page blanks the SPA. That is not hypothetical: the +// 2026-09-01 acceptance run on a clean rig found dossiq-main.js at dexie +// 4.4.5 beside a shared nc-vue chunk at 4.4.4, and the Cases page rendered +// as bare chrome. Two things have to hold, and this script checks both: +// +// 1. every built chunk that embeds a Dexie copy embeds the SAME version; +// 2. that version is the one package-lock.json resolves, so a stale +// chunk left over from an earlier build (or a dependency that vendors +// its own copy, as @conduction/nextcloud-vue did before its build +// externalised dexie) cannot ship unnoticed. +// +// HOW IT DETECTS A COPY +// +// Dexie's own duplicate check ships in every copy of the library, so a +// built chunk embeds Dexie exactly when it contains the error string +// "Two different versions of Dexie". Inside such a chunk the version +// literal survives minification as `semVer:"x.y.z"` (Dexie.semVer). Both +// markers were verified against the released production bundles. +// +// WHEN IT RUNS +// +// As `postbuild`, so it runs wherever `npm run build` runs: locally, in +// code quality CI, and in the release build that packages js/ into the +// App Store tarball. Standalone via `npm run check:dexie`. When js/ does +// not exist yet it skips loudly instead of failing, matching the +// check-integration-parity.sh convention. +// +// Exit codes: +// 0: zero or one Dexie version across js/, matching the lockfile +// 1: two or more versions, or a version the lockfile does not resolve + +const fs = require('fs') +const path = require('path') + +const repoRoot = path.join(__dirname, '..') +const jsDir = path.join(repoRoot, 'js') + +const SENTINEL = 'Two different versions of Dexie' +const SEMVER_RE = /semVer\s*[:=]\s*["']([0-9][0-9A-Za-z.+-]*)["']/g + +if (!fs.existsSync(jsDir)) { + console.log( + 'i dexie singleton: js/ not built yet, skipping (run npm run build first)', + ) + process.exit(0) +} + +let expected = null +try { + const lock = JSON.parse( + fs.readFileSync(path.join(repoRoot, 'package-lock.json'), 'utf8'), + ) + expected = + (lock.packages + && lock.packages['node_modules/dexie'] + && lock.packages['node_modules/dexie'].version) + || null +} catch (e) { + console.log( + `i dexie singleton: could not read package-lock.json (${e.message}); checking chunk agreement only`, + ) +} + +const findings = [] +for (const name of fs.readdirSync(jsDir).sort()) { + if (!name.endsWith('.js')) { + continue + } + const text = fs.readFileSync(path.join(jsDir, name), 'utf8') + if (!text.includes(SENTINEL)) { + continue + } + const versions = new Set() + for (const m of text.matchAll(SEMVER_RE)) { + versions.add(m[1]) + } + if (versions.size === 0) { + // A chunk carries Dexie's error string but no recognisable version + // literal. The marker contract changed, so the guard can no longer + // see, and a guard that cannot see must say so rather than pass. + console.error( + `x dexie singleton: js/${name} embeds Dexie (sentinel found) but no semVer literal matched; update SEMVER_RE in ${path.basename(__filename)}`, + ) + process.exit(1) + } + for (const v of versions) { + findings.push({ file: name, version: v }) + } +} + +if (findings.length === 0) { + console.log('+ dexie singleton: no built chunk embeds Dexie') + process.exit(0) +} + +const distinct = [...new Set(findings.map((f) => f.version))].sort() + +for (const f of findings) { + console.log(` js/${f.file}: dexie ${f.version}`) +} + +if (distinct.length > 1) { + console.error( + `x dexie singleton: ${distinct.length} different Dexie versions in one chunk set (${distinct.join(', ')}). Loading any two of these chunks in one page throws at module init and the SPA never mounts. Rebuild from a clean js/ with a single resolved dexie.`, + ) + process.exit(1) +} + +if (expected && distinct[0] !== expected) { + console.error( + `x dexie singleton: built chunks carry dexie ${distinct[0]} but package-lock.json resolves ${expected}. A chunk is stale, or a dependency vendors its own copy. Rebuild from a clean js/.`, + ) + process.exit(1) +} + +console.log( + `+ dexie singleton: one Dexie version (${distinct[0]}) across ${findings.length} chunk(s)${expected ? ', matching the lockfile' : ''}`, +) diff --git a/scripts/vue3-compile-sweep.cjs b/scripts/vue3-compile-sweep.cjs index a292da357..e2249d26a 100644 --- a/scripts/vue3-compile-sweep.cjs +++ b/scripts/vue3-compile-sweep.cjs @@ -22,7 +22,7 @@ const path = require('path') let sfc try { sfc = require('@vue/compiler-sfc') -} catch (e) { +} catch { console.error('[vue3-compile-sweep] @vue/compiler-sfc not found — install the Vue 3 toolchain first (npm i -D @vue/compiler-sfc@^3.5).') process.exit(2) } @@ -30,6 +30,11 @@ try { const root = path.resolve(__dirname, '..', 'src') const compat = { compatConfig: { MODE: 2, COMPILER_FILTERS: true } } +/** + * + * @param dir + * @param out + */ function walk(dir, out = []) { for (const e of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, e.name) diff --git a/src/components/bezwaar/BezwaarBeroepOverview.vue b/src/components/bezwaar/BezwaarBeroepOverview.vue deleted file mode 100644 index eca156366..000000000 --- a/src/components/bezwaar/BezwaarBeroepOverview.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - diff --git a/src/components/flow/TaskWaitingCaseSection.vue b/src/components/flow/TaskWaitingCaseSection.vue new file mode 100644 index 000000000..641761ae0 --- /dev/null +++ b/src/components/flow/TaskWaitingCaseSection.vue @@ -0,0 +1,170 @@ + + + + + + diff --git a/src/components/tabs/CaseDecisionsTab.vue b/src/components/tabs/CaseDecisionsTab.vue deleted file mode 100644 index 4b2fcb887..000000000 --- a/src/components/tabs/CaseDecisionsTab.vue +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - - - diff --git a/src/components/tabs/CaseDocumentsTab.vue b/src/components/tabs/CaseDocumentsTab.vue index 4f6ec0bbb..5ece50451 100644 --- a/src/components/tabs/CaseDocumentsTab.vue +++ b/src/components/tabs/CaseDocumentsTab.vue @@ -179,6 +179,16 @@ export default { methods: { formatDate, + /** + * Load the documents belonging to THIS case. + * + * Filters on a bare `case` field name: the `_filters[case]` form this + * used is inert, so the tab was reading every case's documents. + * + * @return {Promise} + * + * @spec openspec/specs/document-zaakdossier/spec.md#requirement-req-zak-001-zaak-objects-must-support-linked-documents-via-zgw-informatieobject-and-zaakinformatieobject + */ async reload() { if (!this.resolvedCaseId) { this.loading = false @@ -189,7 +199,7 @@ export default { const results = await this.objectStore.fetchCollection( 'caseDocument', { - '_filters[case]': this.resolvedCaseId, + case: this.resolvedCaseId, _limit: 100, }, ) diff --git a/src/components/tabs/CaseTasksTab.vue b/src/components/tabs/CaseTasksTab.vue index 88e2eaa61..87f89ede5 100644 --- a/src/components/tabs/CaseTasksTab.vue +++ b/src/components/tabs/CaseTasksTab.vue @@ -139,6 +139,16 @@ export default { }, methods: { + /** + * Load the tasks belonging to THIS case. + * + * Filters on a bare `case` field name: the `_filters[case]` form this + * used is inert, so the tab was reading every case's tasks. + * + * @return {Promise} + * + * @spec openspec/specs/task-management/spec.md#requirement-task-list-must-be-reached-via-mijn-werk-not-a-sibling-top-level-menu + */ async reload() { if (!this.resolvedCaseId) { this.loading = false @@ -147,7 +157,7 @@ export default { this.loading = true try { const results = await this.objectStore.fetchCollection('task', { - '_filters[case]': this.resolvedCaseId, + case: this.resolvedCaseId, _limit: 50, }) this.tasks = results || [] diff --git a/src/customComponents.js b/src/customComponents.js index b329e7ed6..a6e144ef4 100644 --- a/src/customComponents.js +++ b/src/customComponents.js @@ -17,11 +17,13 @@ // - openspec/changes/procest-manifest-v1/design.md // - @conduction/nextcloud-vue → docs/migrating-to-manifest.md -import CaseDecisionsTab from './components/tabs/CaseDecisionsTab.vue' +// --- Surviving custom pages — see design.md "Custom-fallback inventory". --- +import { createApp } from 'vue' import CaseDocumentsTab from './components/tabs/CaseDocumentsTab.vue' // --- Detail-tab custom components (one per cross-schema relation). --- // Stubs for v1 — full implementations follow in `procest-case-relation-tabs`. import CaseTasksTab from './components/tabs/CaseTasksTab.vue' +import ReassignSelectionDialog from './dialogs/ReassignSelectionDialog.vue' // --- Case-email sidebar tab (leaf-first per ADR-022). --- // @spec openspec/changes/case-email-integration/tasks.md#T12 import CaseEmailTab from './views/cases/components/CaseEmailTab.vue' @@ -47,7 +49,6 @@ import DtCaseTypeFilter from './views/doorlooptijd/widgets/DtCaseTypeFilter.vue' import DtChartsWidget from './views/doorlooptijd/widgets/DtChartsWidget.vue' import DtKpiWidget from './views/doorlooptijd/widgets/DtKpiWidget.vue' import DtWooWidget from './views/doorlooptijd/widgets/DtWooWidget.vue' -// --- Surviving custom pages — see design.md "Custom-fallback inventory". --- import MyWorkView from './views/MyWorkCards.vue' import PmBottleneckTableWidget from './views/processMining/PmBottleneckTableWidget.vue' import PmCaseTypeFilter from './views/processMining/PmCaseTypeFilter.vue' @@ -64,6 +65,11 @@ import PublicAppointmentPage from './views/public/PublicAppointmentPage.vue' // Remote-org accept/reject for a federated zaakoverdracht (federated-case-collaboration). import PublicFederatedTransferPage from './views/public/PublicFederatedTransferPage.vue' import PublicStatusPage from './views/public/PublicStatusPage.vue' +// --- Store (ADR-080). A store item is a REMOTE object, so the manifest's +// object-backed index renderer — which resolves a local register+schema — +// cannot address it. Discovery itself is the engine's, not this file's. +// @spec openspec/changes/dossiq-store-surface/specs/dossiq-store-surface/spec.md +import StoreGallery from './views/store/StoreGallery.vue' // --- Termijnbewaking + Tenant dashboards (chain-builds 06/2026). --- // Archief dashboard retired (migrate-archival-to-or, ADR-022): the archivist // views are owned by OpenRegister. @@ -132,9 +138,58 @@ async function voorstelReminder({ actionId, item }) { } } +/** + * Bulk-action handler for the Cases index: reassign the selected cases. + * + * CnIndexPage calls a function-typed `customComponents[handler]` with + * `{ actionId, selectedIds, count }`, so the SELECTION arrives as an argument. + * That matters: a handler that went and re-read the selection itself would be + * one re-render away from acting on a different set than the user saw + * highlighted. + * + * The dialog is mounted here rather than declared in the manifest because the + * library's declarative modal path emits `open-modal` and nothing consumes it. + * + * @param {{actionId: string, selectedIds: Array, count: number}} scope The selection. + * @return {void} + */ +function reassignSelection({ selectedIds }) { + const ids = Array.isArray(selectedIds) ? selectedIds : [] + if (ids.length === 0) { + return + } + + const host = document.createElement('div') + document.body.appendChild(host) + + const app = createApp(ReassignSelectionDialog, { + open: true, + selectedIds: ids, + 'onUpdate:open': (open) => { + if (open === false) { + app.unmount() + host.remove() + } + }, + onReassigned: () => { + // The index has to re-read: the rows the user just moved are no + // longer theirs, and leaving them on screen invites a second + // reassignment of cases that already moved. + window.dispatchEvent(new CustomEvent('dossiq:cases-changed')) + }, + }) + app.mount(host) +} + export default { // --- Genuine exceptions: no abstract analogue. --- + // The Cases page's `reassign` bulk action. A FUNCTION handler, not the + // manifest's declarative `handler: "open-modal"` path: that path emits an + // `open-modal` event and nothing in the library listens for it yet, so + // declaring it would ship a bulk action that does nothing when clicked. + reassignSelection, MyWorkView, // current-user case index (assignee=uid) in card view — CnIndexPage wrapper + StoreGallery, // remote store cards — index renderer cannot address a REMOTE object // CaseMapView removed — see import comment above. // --- Lib gaps: would migrate once lib gains the missing primitive. --- @@ -176,7 +231,9 @@ export default { // --- Detail-tab components (one per case-detail cross-schema relation). --- CaseTasksTab, // tasks where task.case === parent.id - CaseDecisionsTab, // decisions where decision.case === parent.id + // CaseDecisionsTab was retired by dossiq-decisions-to-decidiq: decisions + // are authored in decidiq (besluitvorming leaf); the read-only + // case-decisions widget displays the outcomes stored on the case. CaseDocumentsTab, // documents where document.case === parent.id // --- Deelzaak (sub-case) views (manifest /cases/:id/deelzaken[/...]). --- diff --git a/src/dialogs/ParafeerActieDialog.vue b/src/dialogs/ParafeerActieDialog.vue deleted file mode 100644 index 95660a885..000000000 --- a/src/dialogs/ParafeerActieDialog.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - -