From 637a2fd07307688e50d3c3525a1b6ad85af9e99d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 26 May 2026 00:14:05 +0200 Subject: [PATCH 1/2] retrofit(coverage): reverse-spec + annotate backend to 0 uncovered Covers all 287 backend methods flagged by csc.py spec-coverage report: - 14 reverse-spec capabilities (cronjob-context, aangeboden-gebruik-api, contactpersonen-api, dashboard-views-api, repair-init, settings-admin-controller, settings-service, archimate-export/import, contactpersoon-sync, organization-sync, softwarecatalogue-orchestration, email-delivery, sc-handlers) + 1 extend (progress-tracking#REQ-006) - 286 docblock @spec annotations inserted (script-assisted, comment-only, 0 code-line deletions, all php -l clean) - 2 inline DataDownloadResponse render() passthroughs marked @spec exclude Re: ConductionNL/softwarecatalog#294 --- lib/BackgroundJob/CronjobContextTrait.php | 2 + .../OrganizationContactSyncJob.php | 1 + .../AangebodenGebruikController.php | 8 ++ lib/Controller/ContactpersonenController.php | 12 +++ lib/Controller/DashboardController.php | 2 + lib/Controller/PreferencesController.php | 2 + lib/Controller/SettingsController.php | 57 +++++++++++ lib/Controller/ViewController.php | 1 + lib/Repair/InitializeSettings.php | 1 + lib/Service/AangebodenGebruikService.php | 7 ++ lib/Service/ArchiMateExportService.php | 10 ++ lib/Service/ArchiMateImportService.php | 4 + lib/Service/ArchiMateService.php | 8 ++ lib/Service/ContactpersoonService.php | 8 ++ lib/Service/OrganizationSyncService.php | 11 +++ lib/Service/ProgressTracker.php | 1 + lib/Service/SettingsService.php | 77 +++++++++++++++ .../ContactPersonHandler.php | 22 +++++ .../SoftwareCatalogue/GroupHandler.php | 9 ++ .../SoftwareCatalogue/HierarchyHandler.php | 2 + .../SoftwareCatalogue/OrganizationHandler.php | 6 ++ lib/Service/SoftwareCatalogueService.php | 27 ++++++ lib/Service/SymfonyEmailService.php | 13 +++ lib/Service/ViewService.php | 1 + .../design.md | 3 + .../proposal.md | 28 ++++++ .../specs/aangeboden-gebruik-api/spec.md | 44 +++++++++ .../tasks.md | 5 + .../design.md | 3 + .../proposal.md | 25 +++++ .../specs/archimate-export/spec.md | 32 +++++++ .../tasks.md | 4 + .../design.md | 3 + .../proposal.md | 36 +++++++ .../specs/archimate-import/spec.md | 36 +++++++ .../tasks.md | 4 + .../design.md | 3 + .../proposal.md | 25 +++++ .../specs/contactpersonen-api/spec.md | 44 +++++++++ .../tasks.md | 5 + .../design.md | 3 + .../proposal.md | 24 +++++ .../specs/contactpersoon-sync/spec.md | 36 +++++++ .../tasks.md | 4 + .../design.md | 3 + .../proposal.md | 16 ++++ .../specs/cronjob-context/spec.md | 32 +++++++ .../tasks.md | 4 + .../design.md | 3 + .../proposal.md | 22 +++++ .../specs/dashboard-views-api/spec.md | 44 +++++++++ .../tasks.md | 5 + .../design.md | 3 + .../proposal.md | 33 +++++++ .../specs/email-delivery/spec.md | 36 +++++++ .../tasks.md | 4 + .../design.md | 3 + .../proposal.md | 26 +++++ .../specs/organization-sync/spec.md | 48 ++++++++++ .../tasks.md | 5 + .../design.md | 3 + .../proposal.md | 14 +++ .../specs/progress-tracking/spec.md | 16 ++++ .../tasks.md | 3 + .../retrofit-2026-05-26-repair-init/design.md | 3 + .../proposal.md | 15 +++ .../specs/repair-init/spec.md | 24 +++++ .../retrofit-2026-05-26-repair-init/tasks.md | 3 + .../retrofit-2026-05-26-sc-handlers/design.md | 3 + .../proposal.md | 57 +++++++++++ .../specs/sc-handlers/spec.md | 60 ++++++++++++ .../retrofit-2026-05-26-sc-handlers/tasks.md | 6 ++ .../design.md | 3 + .../proposal.md | 83 ++++++++++++++++ .../specs/settings-admin-controller/spec.md | 72 ++++++++++++++ .../tasks.md | 7 ++ .../design.md | 3 + .../proposal.md | 96 +++++++++++++++++++ .../specs/settings-service/spec.md | 72 ++++++++++++++ .../tasks.md | 7 ++ .../design.md | 3 + .../proposal.md | 46 +++++++++ .../softwarecatalogue-orchestration/spec.md | 60 ++++++++++++ .../tasks.md | 6 ++ 84 files changed, 1611 insertions(+) create mode 100644 openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/specs/aangeboden-gebruik-api/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-export/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-export/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-export/specs/archimate-export/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-import/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-import/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-import/specs/archimate-import/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersonen-api/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersonen-api/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersonen-api/specs/contactpersonen-api/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersoon-sync/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersoon-sync/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersoon-sync/specs/contactpersoon-sync/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-cronjob-context/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-cronjob-context/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-cronjob-context/specs/cronjob-context/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-dashboard-views-api/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-dashboard-views-api/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-dashboard-views-api/specs/dashboard-views-api/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-email-delivery/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-email-delivery/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-email-delivery/specs/email-delivery/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-organization-sync/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-organization-sync/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-organization-sync/specs/organization-sync/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-progress-tracking-2/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-progress-tracking-2/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-progress-tracking-2/specs/progress-tracking/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-progress-tracking-2/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-repair-init/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-repair-init/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-repair-init/specs/repair-init/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-repair-init/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-sc-handlers/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-sc-handlers/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-sc-handlers/specs/sc-handlers/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-admin-controller/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-admin-controller/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-admin-controller/specs/settings-admin-controller/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-service/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-service/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-service/specs/settings-service/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-settings-service/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/design.md create mode 100644 openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/specs/softwarecatalogue-orchestration/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md diff --git a/lib/BackgroundJob/CronjobContextTrait.php b/lib/BackgroundJob/CronjobContextTrait.php index 481efc2e..2267c69b 100644 --- a/lib/BackgroundJob/CronjobContextTrait.php +++ b/lib/BackgroundJob/CronjobContextTrait.php @@ -73,6 +73,7 @@ trait CronjobContextTrait * @param string $jobId The identifier of the cronjob (e.g., 'organization_contact_sync') * * @return bool True if context was successfully set, false otherwise + * @spec openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md#task-1 */ protected function setCronjobContext(string $jobId): bool { @@ -183,6 +184,7 @@ protected function setCronjobContext(string $jobId): bool * @param string $jobId The identifier of the cronjob * * @return void + * @spec openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md#task-1 */ protected function clearCronjobContext(string $jobId): void { diff --git a/lib/BackgroundJob/OrganizationContactSyncJob.php b/lib/BackgroundJob/OrganizationContactSyncJob.php index 79b8db4c..2b3ace0b 100644 --- a/lib/BackgroundJob/OrganizationContactSyncJob.php +++ b/lib/BackgroundJob/OrganizationContactSyncJob.php @@ -78,6 +78,7 @@ public function __construct( * @return void * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @spec openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md#task-2 */ protected function run($argument): void { diff --git a/lib/Controller/AangebodenGebruikController.php b/lib/Controller/AangebodenGebruikController.php index 15068e5d..8482a748 100644 --- a/lib/Controller/AangebodenGebruikController.php +++ b/lib/Controller/AangebodenGebruikController.php @@ -85,6 +85,7 @@ public function __construct( * @NoCSRFRequired * @PublicPage * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getGebruiksWhereAfnemer(): JSONResponse { @@ -160,6 +161,7 @@ public function getGebruiksWhereAfnemer(): JSONResponse * @NoAdminRequired * @NoCSRFRequired * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getKoppelingenGebruikByUuid(string $uuid): JSONResponse { @@ -250,6 +252,7 @@ public function getKoppelingenGebruikByUuid(string $uuid): JSONResponse * @NoAdminRequired * @NoCSRFRequired * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getAllGebruiksForAmbtenaar(): JSONResponse { @@ -357,6 +360,7 @@ public function getAllGebruiksForAmbtenaar(): JSONResponse * @PublicPage * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getSingleGebruikForAmbtenaar(string $gebruikId): JSONResponse { @@ -529,6 +533,7 @@ private function isUserInGroup(string $groupName): bool * @NoCSRFRequired * @PublicPage * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getGebruiksWhereDeelnemers(): JSONResponse { @@ -607,6 +612,7 @@ public function getGebruiksWhereDeelnemers(): JSONResponse * @NoCSRFRequired * @PublicPage * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-2 */ public function setGebruikSelfToActiveOrg(string $gebruikId): JSONResponse { @@ -714,6 +720,7 @@ function ($key) { * @NoCSRFRequired * @PublicPage * @PublicPage + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-2 */ public function deleteGebruikAsAfnemer(string $gebruikId): JSONResponse { @@ -814,6 +821,7 @@ function ($key) { * @PublicPage * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-3 */ public function getApiDocumentation(): JSONResponse { diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index 07fc783f..bbff6428 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -169,6 +169,7 @@ public function __construct( * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function getContactpersonen(string $organisationId): JSONResponse { @@ -264,6 +265,7 @@ function ($group) { * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-2 */ public function convertToUser(string $contactpersoonId): JSONResponse { @@ -489,6 +491,7 @@ public function convertToUser(string $contactpersoonId): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-3 */ public function changePassword(string $username, string $newPassword): JSONResponse { @@ -577,6 +580,7 @@ public function changePassword(string $username, string $newPassword): JSONRespo * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-3 */ public function updateUserGroups(string $username, array $groups=[]): JSONResponse { @@ -700,6 +704,7 @@ function ($group) { * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function getContactPersonsWithUserDetailsForOrganization(string $organizationUuid): JSONResponse { @@ -791,6 +796,7 @@ public function getContactPersonsWithUserDetailsForOrganization(string $organiza * @NoCSRFRequired * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function getUserInfo(string $contactpersoonId): JSONResponse { @@ -917,6 +923,7 @@ public function getUserInfo(string $contactpersoonId): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-3 */ public function getAvailableGroups(): JSONResponse { @@ -981,6 +988,7 @@ public function getAvailableGroups(): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-3 */ public function disableUser(string $contactpersoonId): JSONResponse { @@ -1028,6 +1036,7 @@ public function disableUser(string $contactpersoonId): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-3 */ public function enableUser(string $contactpersoonId): JSONResponse { @@ -1073,6 +1082,7 @@ public function enableUser(string $contactpersoonId): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function testBulkUserInfo(): JSONResponse { @@ -1133,6 +1143,7 @@ public function testBulkUserInfo(): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function getBulkUserInfo(): JSONResponse { @@ -1198,6 +1209,7 @@ public function getBulkUserInfo(): JSONResponse * @NoCSRFRequired * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md#task-1 */ public function getMe(): JSONResponse { diff --git a/lib/Controller/DashboardController.php b/lib/Controller/DashboardController.php index c8946880..56cfe2a3 100644 --- a/lib/Controller/DashboardController.php +++ b/lib/Controller/DashboardController.php @@ -43,6 +43,7 @@ public function __construct($appName, IRequest $request) * @NoCSRFRequired * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-1 */ public function page(?string $getParameter): TemplateResponse { @@ -75,6 +76,7 @@ public function page(?string $getParameter): TemplateResponse * @NoCSRFRequired * * @return JSONResponse The JSON response with empty results + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-1 */ public function index(): JSONResponse { diff --git a/lib/Controller/PreferencesController.php b/lib/Controller/PreferencesController.php index 8fe709c8..182a3a87 100644 --- a/lib/Controller/PreferencesController.php +++ b/lib/Controller/PreferencesController.php @@ -62,6 +62,7 @@ public function __construct( * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-3 */ public function getPreference(string $key): JSONResponse { @@ -101,6 +102,7 @@ public function getPreference(string $key): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-3 */ public function setPreference(string $key, string $value=''): JSONResponse { diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 28545d1c..af041be7 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -103,6 +103,7 @@ public function __construct( * * @return ObjectService|null The OpenRegister service if available, null otherwise. * @throws RuntimeException If the service is not available. + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-1 */ public function getObjectService(): ?ObjectService { @@ -120,6 +121,7 @@ public function getObjectService(): ?ObjectService * * @return ConfigurationService|null The Configuration service if available, null otherwise. * @throws RuntimeException If the service is not available. + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-1 */ public function getConfigurationService(): ?ConfigurationService { @@ -561,6 +563,7 @@ public function status(): JSONResponse * @return JSONResponse JSON response containing the auto-configuration results * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function autoConfigure(): JSONResponse { @@ -662,6 +665,7 @@ public function debug(): JSONResponse * @return JSONResponse * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function sendTestEmail(): JSONResponse { @@ -883,6 +887,7 @@ public function getVersionInfo(): JSONResponse * @return JSONResponse JSON response containing reset results. * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function resetAutoConfig(): JSONResponse { @@ -955,6 +960,7 @@ public function clearCache(): JSONResponse * @return JSONResponse JSON response containing import results. * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function manualImport(): JSONResponse { @@ -1012,6 +1018,7 @@ public function manualImport(): JSONResponse * @return JSONResponse JSON response containing force update results. * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function forceUpdate(): JSONResponse { @@ -1084,6 +1091,7 @@ public function forceUpdate(): JSONResponse * @return JSONResponse JSON response containing consolidated results * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function consolidatedAutoConfigure(): JSONResponse { @@ -1221,6 +1229,8 @@ public function __construct( * Render the SSE stream. * * @return string Empty string (output is streamed directly). + * + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-2 */ public function render(): string { @@ -1309,6 +1319,7 @@ public function render(): string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.Superglobals) + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function importArchiMate(): JSONResponse { @@ -1488,6 +1499,7 @@ public function importArchiMate(): JSONResponse * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function exportArchiMate(): Response { @@ -1549,6 +1561,8 @@ public function __construct(private string $content) * Render the response content. * * @return string The response content. + * + * @spec exclude framework passthrough — inline DataDownloadResponse subclass returning prebuilt content unchanged */ public function render(): string { @@ -1604,6 +1618,7 @@ public function render(): string * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function exportOrgArchiMate(string $organizationUuid): Response { @@ -1660,6 +1675,8 @@ public function __construct(private string $content) * Render the response content. * * @return string The response content. + * + * @spec exclude framework passthrough — inline DataDownloadResponse subclass returning prebuilt content unchanged */ public function render(): string { @@ -1705,6 +1722,7 @@ public function render(): string * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function downloadArchiMate(string $fileName): Response { @@ -1799,6 +1817,7 @@ public function downloadArchiMate(string $fileName): Response * @NoCSRFRequired * * @return JSONResponse Test connection result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function testEmailConnection(): JSONResponse { @@ -1860,6 +1879,7 @@ public function testEmailConnection(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Current email settings + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailSettings(): JSONResponse { @@ -1896,6 +1916,7 @@ public function getEmailSettings(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function updateEmailSettings(): JSONResponse { @@ -1941,6 +1962,7 @@ public function updateEmailSettings(): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailTemplates(): JSONResponse { @@ -1980,6 +2002,7 @@ public function getEmailTemplates(): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailTemplate(string $templateName): JSONResponse { @@ -2019,6 +2042,7 @@ public function getEmailTemplate(string $templateName): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function updateEmailTemplate(string $templateName): JSONResponse { @@ -2078,6 +2102,7 @@ public function updateEmailTemplate(string $templateName): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailTemplateDefault(string $templateName): JSONResponse { @@ -2117,6 +2142,7 @@ public function getEmailTemplateDefault(string $templateName): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailTemplateVariables(string $templateName): JSONResponse { @@ -2158,6 +2184,7 @@ public function getEmailTemplateVariables(string $templateName): JSONResponse * @NoCSRFRequired * * @return JSONResponse Generic user groups + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getGenericUserGroups(): JSONResponse { @@ -2194,6 +2221,7 @@ public function getGenericUserGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function setGenericUserGroups(): JSONResponse { @@ -2236,6 +2264,7 @@ public function setGenericUserGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Organization admin groups + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getOrganizationAdminGroups(): JSONResponse { @@ -2272,6 +2301,7 @@ public function getOrganizationAdminGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function setOrganizationAdminGroups(): JSONResponse { @@ -2314,6 +2344,7 @@ public function setOrganizationAdminGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Super user groups + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getSuperUserGroups(): JSONResponse { @@ -2350,6 +2381,7 @@ public function getSuperUserGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function setSuperUserGroups(): JSONResponse { @@ -2392,6 +2424,7 @@ public function setSuperUserGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse All user groups + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getAllGroups(): JSONResponse { @@ -2432,6 +2465,7 @@ public function getAllGroups(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Clear result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function clearArchiMateImportStatus(): JSONResponse { @@ -2471,6 +2505,7 @@ public function clearArchiMateImportStatus(): JSONResponse * * @NoAdminRequired * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function killArchiMateImport(): JSONResponse { @@ -2509,6 +2544,7 @@ public function killArchiMateImport(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Cancellation result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function cancelArchiMateImport(): JSONResponse { @@ -2550,6 +2586,7 @@ public function cancelArchiMateImport(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Clear result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function clearArchiMateExportStatus(): JSONResponse { @@ -2590,6 +2627,7 @@ public function clearArchiMateExportStatus(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Round-trip test result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function testArchiMateRoundTrip(): JSONResponse { @@ -2640,6 +2678,7 @@ public function testArchiMateRoundTrip(): JSONResponse * @NoCSRFRequired * * @return JSONResponse ArchiMate settings and status + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getArchiMateSettings(): JSONResponse { @@ -2677,6 +2716,7 @@ public function getArchiMateSettings(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Object counts for all registers + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-3 */ public function getObjectCounts(): JSONResponse { @@ -2717,6 +2757,7 @@ public function getObjectCounts(): JSONResponse * @NoCSRFRequired * * @return JSONResponse ArchiMate configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getArchiMateConfig(): JSONResponse { @@ -2747,6 +2788,7 @@ public function getArchiMateConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function updateArchiMateConfig(): JSONResponse { @@ -2779,6 +2821,7 @@ public function updateArchiMateConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Email configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getEmailConfig(): JSONResponse { @@ -2809,6 +2852,7 @@ public function getEmailConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function updateEmailConfig(): JSONResponse { @@ -2841,6 +2885,7 @@ public function updateEmailConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse AMEF configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function getAmefConfig(): JSONResponse { @@ -2871,6 +2916,7 @@ public function getAmefConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-4 */ public function updateAmefConfig(): JSONResponse { @@ -2903,6 +2949,7 @@ public function updateAmefConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Voorzieningen configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-1 */ public function getVoorzieningenConfig(): JSONResponse { @@ -2933,6 +2980,7 @@ public function getVoorzieningenConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-1 */ public function updateVoorzieningenConfig(): JSONResponse { @@ -2965,6 +3013,7 @@ public function updateVoorzieningenConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Object counts + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-3 */ public function getObjectsCounts(): JSONResponse { @@ -2996,6 +3045,7 @@ public function getObjectsCounts(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Object statistics + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-3 */ public function getObjectsStatistics(): JSONResponse { @@ -3027,6 +3077,7 @@ public function getObjectsStatistics(): JSONResponse * @NoCSRFRequired * * @return JSONResponse User groups configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getUserGroupsConfig(): JSONResponse { @@ -3057,6 +3108,7 @@ public function getUserGroupsConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function updateUserGroupsConfig(): JSONResponse { @@ -3213,6 +3265,7 @@ public function syncOrganisations(): JSONResponse * @return JSONResponse Response containing sync results. * * @NoCSRFRequired + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-3 */ public function bulkSyncStandards(): JSONResponse { @@ -3274,6 +3327,7 @@ public function bulkSyncStandards(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Cronjob configurations + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getCronjobConfig(): JSONResponse { @@ -3305,6 +3359,7 @@ public function getCronjobConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse Update result + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function updateCronjobConfig(): JSONResponse { @@ -3344,6 +3399,7 @@ public function updateCronjobConfig(): JSONResponse * @NoCSRFRequired * * @return JSONResponse List of available users + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getCronjobUsers(): JSONResponse { @@ -3377,6 +3433,7 @@ public function getCronjobUsers(): JSONResponse * @NoCSRFRequired * * @return JSONResponse List of available organisations + * @spec openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md#task-5 */ public function getCronjobOrganisations(): JSONResponse { diff --git a/lib/Controller/ViewController.php b/lib/Controller/ViewController.php index 505a362b..c2d9427b 100644 --- a/lib/Controller/ViewController.php +++ b/lib/Controller/ViewController.php @@ -310,6 +310,7 @@ private function parseBooleanParam($value): bool * @return JSONResponse JSON response with API documentation * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-2 */ public function getApiDocumentation(): JSONResponse { diff --git a/lib/Repair/InitializeSettings.php b/lib/Repair/InitializeSettings.php index dcaf61ed..453f8a87 100644 --- a/lib/Repair/InitializeSettings.php +++ b/lib/Repair/InitializeSettings.php @@ -66,6 +66,7 @@ public function getName(): string * @param IOutput $output The output interface for progress reporting * * @return void + * @spec openspec/changes/retrofit-2026-05-26-repair-init/tasks.md#task-1 */ public function run(IOutput $output): void { diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php index 1877d54c..dddf1a40 100644 --- a/lib/Service/AangebodenGebruikService.php +++ b/lib/Service/AangebodenGebruikService.php @@ -91,6 +91,7 @@ public function __construct( * @return array Array with success status, objects data, and metadata. * * @throws Exception When OpenRegister service is not available. + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getGebruiksWhereAfnemer(array $options=[]): array { @@ -321,6 +322,7 @@ public function getGebruiksWhereAfnemer(array $options=[]): array * @throws Exception When OpenRegister service is not available. * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $isAmbtenaar is a simple privilege toggle + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getKoppelingenGebruikByUuid(string $uuid, array $options=[], bool $isAmbtenaar=false): array { @@ -538,6 +540,7 @@ public function getKoppelingenGebruikByUuid(string $uuid, array $options=[], boo * @return array searchObjectsPaginated result with all gebruiks. * * @throws Exception When OpenRegister service is not available. + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getAllGebruiksForAmbtenaar(array $options=[]): array { @@ -638,6 +641,7 @@ public function getAllGebruiksForAmbtenaar(array $options=[]): array * @return array searchObjectsPaginated result with all gebruiks for the suite * * @throws Exception When OpenRegister service is not available + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getSingleGebruikForAmbtenaar(string $suiteId, array $options=[]): array { @@ -756,6 +760,7 @@ public function getSingleGebruikForAmbtenaar(string $suiteId, array $options=[]) * @return array Array with success status, gebruiks data, and metadata * * @throws Exception When OpenRegister service is not available + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-1 */ public function getGebruiksWhereDeelnemers(array $options=[]): array { @@ -878,6 +883,7 @@ public function getGebruiksWhereDeelnemers(array $options=[]): array * @return array Result with success status and updated object data * * @throws Exception When OpenRegister service is not available or operation fails + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-2 */ public function setGebruikSelfToActiveOrg(string $gebruikId, array $options=[]): array { @@ -1669,6 +1675,7 @@ private function addQueryFilters(array $baseQuery, array $options): array * @param array $options Additional options for the operation * * @return array Result array with success status and details + * @spec openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md#task-2 */ public function deleteGebruikAsAfnemer(string $gebruikId, array $options=[]): array { diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index a580e01a..a7ff2494 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -78,6 +78,7 @@ public function __construct( * @param \SimpleXMLElement $xml The XML element to populate. * * @return \SimpleXMLElement The populated XML element. + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function arrayToXml(array $data, \SimpleXMLElement $xml): \SimpleXMLElement { @@ -396,6 +397,7 @@ public function createCleanArchiMateXml(array $modelMetadata): \SimpleXMLElement * @param string $childTagName Tag name for child elements. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function addObjectsToXml( \SimpleXMLElement $xml, @@ -448,6 +450,7 @@ public function addElementsToXml(\SimpleXMLElement $xml, array $elements): void * @param array $relationships The relationships to add. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function addRelationshipsToXml(\SimpleXMLElement $xml, array $relationships): void { @@ -468,6 +471,7 @@ public function addRelationshipsToXml(\SimpleXMLElement $xml, array $relationshi * @param array $views The views to add. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function addViewsToXml(\SimpleXMLElement $xml, array $views): void { @@ -501,6 +505,7 @@ public function addViewsToXml(\SimpleXMLElement $xml, array $views): void * @param array $organizations The organizations to add. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function addOrganizationsToXml(\SimpleXMLElement $xml, array $organizations): void { @@ -765,6 +770,7 @@ private function addObjectToFolder(\SimpleXMLElement $folder, array $object, str * @return array Array of objects from all schemas in the register. * * @throws \RuntimeException If retrieval fails. + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function getObjectsFromDatabase( \OCA\OpenRegister\Service\ObjectService $objectService, @@ -879,6 +885,7 @@ public function getObjectsFromDatabase( * @param array $propertyDefinitions The property definitions. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function addPropertyDefinitionsToXml(\SimpleXMLElement $xml, array $propertyDefinitions): void { @@ -907,6 +914,7 @@ public function addPropertyDefinitionsToXml(\SimpleXMLElement $xml, array $prope * @param string|null $organization Organization filter. * * @return string Generated XML. + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function exportArchiMateXml( \OCA\OpenRegister\Service\ObjectService $objectService, @@ -1948,6 +1956,7 @@ private function addDataToXmlNode(\SimpleXMLElement $node, array $data): void * @param array $schemaIdMap Mapping of schema IDs to schema types. * * @return array ArchiMate data structure. + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-1 */ public function convertFromOpenRegisterObjects(array $objects, array $schemaIdMap): array { @@ -2378,6 +2387,7 @@ private function validateTextContentNormalized(\SimpleXMLElement $xml): void * @param array $options Export options. * * @return string Generated XML. + * @spec openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md#task-2 */ public function exportOrganizationArchiMateXml( \OCA\OpenRegister\Service\ObjectService $objectService, diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index 6218d982..ba50975a 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -191,6 +191,7 @@ public function __construct( * @param \SimpleXMLElement $xml The XML element to convert. * * @return array The normalized associative array. + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-1 */ public function xmlToArray(\SimpleXMLElement $xml): array { @@ -303,6 +304,7 @@ private function isAssoc(mixed $value): bool * @param array $options Import options including file_path, fileName, etc. * * @return array Import results with detailed status + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-1 */ public function importArchiMateFileFromPathOptimized(array $options=[]): array { @@ -1917,6 +1919,7 @@ private function getCurrentOrganisation(): string * Get AMEF configuration from app config * * @return array AMEF configuration + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-1 */ public function getAmefConfig(): array { @@ -2191,6 +2194,7 @@ private function extractPropertyDefinitionMap(array $data): array * @param array $propDefMap The original property definition map * * @return array Mapping of original names to camelCase names + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-1 */ public function getPropertyNameMapping(array $propDefMap): array { diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index 12bf25b6..a476100b 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -192,6 +192,7 @@ public function __construct( * @param array $options Import options including file_path, fileName, etc. * * @return array Import results with detailed status + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function importArchiMateFileFromPathOptimized(array $options=[]): array { @@ -299,6 +300,7 @@ public function exportToArchiMate(?string $organization=null): array * @param array $options Optional export options. * * @return array Export results with 'success', 'xml', 'file_name' + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function exportOrgArchiMate(string $organizationUuid, array $options=[]): array { @@ -1481,6 +1483,7 @@ private function getSchemaIdForSection(string $section): int * Test round-trip functionality * * @return array Test results + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function testRoundTrip(): array { @@ -1584,6 +1587,7 @@ private function createTempFile(string $content): string * Get AMEF configuration from app config * * @return array AMEF configuration + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function getAmefConfig(): array { @@ -1686,6 +1690,7 @@ private function getVoorzieningenConfig(): array * Get the current status of ArchiMate operations * * @return array Status information including import/export status and object counts + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function getArchiMateStatus(): array { @@ -2006,6 +2011,7 @@ private function getObjectsWithPagination(string $schemaType, array $query=[]): * Check if import is in progress * * @return bool True if import is in progress + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function isImportInProgress(): bool { @@ -2017,6 +2023,7 @@ public function isImportInProgress(): bool * Check if export is in progress * * @return bool True if export is in progress + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function isExportInProgress(): bool { @@ -2758,6 +2765,7 @@ private function convertToCamelCase(string $propertyName): string * @param array $propDefMap The original property definition map * * @return array Mapping of original names to camelCase names + * @spec openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md#task-2 */ public function getPropertyNameMapping(array $propDefMap): array { diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index 261b8259..c17d4b4e 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -386,6 +386,7 @@ public function processContactpersoon(object $contactpersoonObject, bool $isUpda * @param string $username The username to update groups for * * @return void + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-1 */ public function updateUserGroups(object $contactpersoonObject, string $username): void { @@ -412,6 +413,7 @@ public function updateUserGroups(object $contactpersoonObject, string $username) * @param string $username The username being processed * * @return void + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-1 */ public function ensureOrganizationBeheerder(object $contactpersoonObject, string $username): void { @@ -706,6 +708,7 @@ private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService * @param object $contactObject The contact object being deleted * * @return void + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-1 */ public function handleContactDeletion(object $contactObject): void { @@ -775,6 +778,7 @@ public function handleContactDeletion(object $contactObject): void * @param string $organizationUuid The organization UUID * * @return array Array of contact person objects + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-2 */ public function getContactPersonsForOrganization(string $organizationUuid): array { @@ -834,6 +838,7 @@ public function getContactPersonsForOrganization(string $organizationUuid): arra * @return array Array of contact person objects with user details spliced in * * @throws \Exception If contact person retrieval fails + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-2 */ public function getContactPersonsWithUserDetailsForOrganization(string $organizationUuid): array { @@ -989,6 +994,7 @@ static function ($cp) { * @return array Array of user information keyed by contact person ID * * @throws \Exception If bulk user info retrieval fails + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-2 */ public function getBulkUserInfo(array $contactpersoonIds): array { @@ -1262,6 +1268,7 @@ private function updateContactpersoonObjectOwner(object $contactObject, string $ * @return void * * @throws \Exception If enabling fails. + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-2 */ public function enableUserForContactpersoon(string $contactpersoonId): void { @@ -1333,6 +1340,7 @@ public function enableUserForContactpersoon(string $contactpersoonId): void * @return void * * @throws \Exception If disabling fails. + * @spec openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md#task-2 */ public function disableUserForContactpersoon(string $contactpersoonId): void { diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 6460c0a2..939f24a4 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -344,6 +344,7 @@ public function performOrganizationsSync(int $batchSize=50, int $maxExecutionSec * @param int $maxExecutionSeconds The maximum execution time in seconds. * * @return array The sync statistics. + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-1 */ public function performContactSync(int $batchSize=100, int $maxExecutionSeconds=30): array { @@ -472,6 +473,7 @@ public function performContactSync(int $batchSize=100, int $maxExecutionSeconds= * @return array The sync statistics. * * @psalm-suppress UndefinedClass + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-1 */ public function performUserSync(): array { @@ -811,6 +813,7 @@ private function processOrganisatieObject( * @return object|null The organisation entity or null on failure. * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $sendEmails is a simple notification toggle + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-2 */ public function ensureOrganisationEntityPublic(object $organisatieObject, array &$stats, bool $sendEmails=true): ?object { @@ -1378,6 +1381,7 @@ private function getAdminUsers(): array * @param int $minutesBack Number of minutes to look back for prediction (default: 10 for scheduled sync) * * @return array Status information about sync requirements including processing predictions + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-3 */ public function getSyncStatus(int $minutesBack=10): array { @@ -1541,6 +1545,7 @@ private function formatNumber(int $number): string * Records the last sync time * * @return void + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-3 */ public function recordSyncTime(): void { @@ -1557,6 +1562,7 @@ public function recordSyncTime(): void * @param \OCA\OpenRegister\Db\ObjectEntity $organizationObject The organization object to process * * @return array Processing results + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-2 */ public function processSpecificOrganization($organizationObject): array { @@ -2463,6 +2469,7 @@ private function createOrUpdateContactPersonObject( * @param \OCA\OpenRegister\Db\ObjectEntity $contactObject The contact person object to process * * @return array Processing results + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-2 */ public function processSpecificContactPerson($contactObject): array { @@ -2701,6 +2708,7 @@ public function processSpecificContactPerson($contactObject): array * @param int $batchSize Number of items to process per batch (default: 100) * * @return array Comprehensive synchronization results + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-1 */ public function performOptimizedManualSync(int $maxRounds=10, int $batchSize=100): array { @@ -2799,6 +2807,7 @@ public function performOptimizedManualSync(int $maxRounds=10, int $batchSize=100 * @param int $minutesBack Number of minutes to look back for changes (default: 0 = full sync) * * @return array Synchronization results with detailed logging information + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-1 */ public function performScheduledSync(int $minutesBack=0): array { @@ -2896,6 +2905,7 @@ public function performScheduledSync(int $minutesBack=0): array * @param int $minutesBack Number of minutes to look back for changes (default: 0 for full sync) * * @return array Synchronization results formatted for API response + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-1 */ public function performManualSync(int $minutesBack=0): array { @@ -2965,6 +2975,7 @@ public function performManualSync(int $minutesBack=0): array * @param int $minutesBack The number of minutes to look back. * * @return array Status information with error handling. + * @spec openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md#task-3 */ public function getSyncStatusWithErrorHandling(int $minutesBack=10): array { diff --git a/lib/Service/ProgressTracker.php b/lib/Service/ProgressTracker.php index de7389e2..4ab7683d 100644 --- a/lib/Service/ProgressTracker.php +++ b/lib/Service/ProgressTracker.php @@ -431,6 +431,7 @@ private function saveProgress(): void * @param int $maxAge Maximum age in seconds (default: 1 hour) * * @return void + * @spec openspec/changes/retrofit-2026-05-26-progress-tracking-2/tasks.md#task-1 */ public function cleanupOldProgress(int $maxAge=3600): void { diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index b1c82298..99e4030c 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -124,6 +124,7 @@ public function __construct( * @param string|null $minVersion Minimum required version * * @return bool True if OpenRegister is installed and meets version requirements + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function isOpenRegisterInstalled(?string $minVersion=self::MIN_OPENREGISTER_VERSION): bool { @@ -155,6 +156,7 @@ public function isOpenRegisterEnabled(): bool * @return \OCA\OpenRegister\Service\ObjectService|null The OpenRegister service if available * * @throws \RuntimeException If the service is not available + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { @@ -169,6 +171,7 @@ public function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService * Get the OpenRegister RegisterService. * * @return \OCA\OpenRegister\Service\RegisterService|null The RegisterService instance or null if not available. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getRegisterService(): ?\OCA\OpenRegister\Service\RegisterService { @@ -185,6 +188,7 @@ public function getRegisterService(): ?\OCA\OpenRegister\Service\RegisterService * @return \OCA\OpenRegister\Service\ConfigurationService|null The Configuration service if available * * @throws \RuntimeException If the service is not available + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getConfigurationService(): ?\OCA\OpenRegister\Service\ConfigurationService { @@ -506,6 +510,7 @@ public function autoConfigure(bool $force=false): array * @return array The updated configuration * * @throws \RuntimeException If auto-configuration fails + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function autoConfigureAfterImport(): array { @@ -612,6 +617,7 @@ public function autoConfigureAfterImport(): array * schema and register IDs for pages, menus, and themes from the publication register. * * @return array Configuration result with success status and configured settings + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function configureOpenCatalogi(): array { @@ -751,6 +757,7 @@ private function tableHasData(string $tableName): bool * @param string $objectType The object type (organization, contact, gebruiker, contactpersoon) * * @return int|null The schema ID or null if not configured + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getSchemaIdForObjectType(string $objectType): ?int { @@ -908,6 +915,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int * @param string $objectType The object type (organization, contact, gebruiker) * * @return int|null The register ID or null if not configured + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getRegisterIdForObjectType(string $objectType): ?int { @@ -974,6 +982,7 @@ public function getRegisterIdForObjectType(string $objectType): ?int * cached values don't become stale. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function clearConfigurationCache(): void { @@ -995,6 +1004,7 @@ public function clearConfigurationCache(): void * Gets the configured register ID for the voorzieningen register * * @return int|null The register ID or null if not configured + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-1 */ public function getVoorzieningenRegisterId(): ?int { @@ -1109,6 +1119,7 @@ public function getVoorzieningenRegisterId(): ?int * Checks if all required object types are configured * * @return bool True if all object types have schemas configured + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function isFullyConfigured(): bool { @@ -1129,6 +1140,7 @@ public function isFullyConfigured(): bool * Gets configuration status for each object type * * @return array Configuration status information + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getConfigurationStatus(): array { @@ -1163,6 +1175,7 @@ public function getConfigurationStatus(): array * @param string|null $minOpenRegisterVersion Minimum required OpenRegister version * * @return array The initialization results + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function initialize(?string $minOpenRegisterVersion=self::MIN_OPENREGISTER_VERSION): array { @@ -1347,6 +1360,7 @@ public function initialize(?string $minOpenRegisterVersion=self::MIN_OPENREGISTE * @throws \RuntimeException If settings loading fails * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $force is a simple re-import toggle + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function loadSettings(bool $force=false): array { @@ -1432,6 +1446,7 @@ public function loadSettings(bool $force=false): array * Gets the list of generic user groups from configuration * * @return array Array of generic user groups + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getGenericUserGroups(): array { @@ -1458,6 +1473,7 @@ public function getGenericUserGroups(): array * @param array $groups Array of generic user groups * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function setGenericUserGroups(array $groups): void { @@ -1476,6 +1492,7 @@ public function setGenericUserGroups(array $groups): void * Gets the list of organization admin groups from configuration * * @return array Array of organization admin groups + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getOrganizationAdminGroups(): array { @@ -1491,6 +1508,7 @@ public function getOrganizationAdminGroups(): array * @param array $groups Array of organization admin groups * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function setOrganizationAdminGroups(array $groups): void { @@ -1509,6 +1527,7 @@ public function setOrganizationAdminGroups(array $groups): void * Gets the list of super user groups from configuration * * @return array Array of super user groups + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getSuperUserGroups(): array { @@ -1535,6 +1554,7 @@ public function getSuperUserGroups(): array * @param array $groups Array of super user groups * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function setSuperUserGroups(array $groups): void { @@ -1555,6 +1575,7 @@ public function setSuperUserGroups(array $groups): void * @param array $groups Array of group names to validate * * @return array Array with validation results + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function validateGroups(array $groups): array { @@ -1590,6 +1611,7 @@ public function validateGroups(array $groups): array * This is the public method that creates user groups and returns status information. * * @return array Results of user group creation and configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function createAndConfigureUserGroups(): array { @@ -1814,6 +1836,7 @@ private function createRequiredUserGroups(): void * Gets all available groups with their information * * @return array Array of group information + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getAllGroups(): array { @@ -1845,6 +1868,7 @@ public function getAllGroups(): array * Gets email configuration settings * * @return array Email settings configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getEmailSettings(): array { @@ -2019,6 +2043,7 @@ public function getEmailSettings(): array * @param array $emailSettings Email settings to update * * @return array Updated email settings + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function updateEmailSettings(array $emailSettings): array { @@ -2096,6 +2121,7 @@ public function updateEmailSettings(array $emailSettings): array * @param string $templateName The template name (organization_registration, organization_activation, user_creation) * * @return string The template content + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getEmailTemplate(string $templateName): string { @@ -2112,6 +2138,7 @@ public function getEmailTemplate(string $templateName): string * @param string $templateContent The template content * * @return bool True if update was successful + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function updateEmailTemplate(string $templateName, string $templateContent): bool { @@ -2144,6 +2171,7 @@ public function updateEmailTemplate(string $templateName, string $templateConten * @param string $templateName The template name * * @return string Default template content + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getDefaultEmailTemplate(string $templateName): string { @@ -2215,6 +2243,7 @@ public function getDefaultEmailTemplate(string $templateName): string * @param string $templateName The template name * * @return array Available template variables + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getEmailTemplateVariables(string $templateName): array { @@ -2253,6 +2282,7 @@ public function getEmailTemplateVariables(string $templateName): array * Gets debug information for settings * * @return array Debug information + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getDebugInfo(): array { @@ -2336,6 +2366,7 @@ public function getDebugInfo(): array * @param array $emailSettings The email settings to use * * @return array Result of the test email + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function sendTestEmail(string $email, array $emailSettings=[]): array { @@ -2482,6 +2513,7 @@ public function sendTestEmail(string $email, array $emailSettings=[]): array * @param array $emailSettings The email settings to test * * @return array Result of the connection test + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function testEmailConnection(array $emailSettings=[]): array { @@ -2919,6 +2951,7 @@ private function shouldLoadSettings(): bool * * @return array Version information with app and configuration versions. * @throws \RuntimeException If version retrieval fails. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getVersionInfo(): array { @@ -3003,6 +3036,7 @@ public function getVersionInfo(): array * flags and configurations, then performs import and auto-configuration. * * @return array The force update results + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function forceUpdate(): array { @@ -3089,6 +3123,7 @@ public function forceUpdate(): array * @return array The reset results * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $resetConfiguration is a simple scope toggle + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function resetAutoConfiguration(bool $resetConfiguration=false): array { @@ -3162,6 +3197,7 @@ public function resetAutoConfiguration(bool $resetConfiguration=false): array * @return array The import results with success/error information. * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $forceImport is a simple re-import toggle + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function manualImport(bool $forceImport=false): array { @@ -3300,6 +3336,7 @@ public function manualImport(bool $forceImport=false): array * @return array Consolidated configuration results * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $force is a simple re-import toggle + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function performConsolidatedAutoConfiguration(bool $force=false): array { @@ -3874,6 +3911,7 @@ private function addStepResult(array &$results, array $stepResult, string $stepN * Get consolidated configuration as JSON objects * * @return array The consolidated configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getConsolidatedConfiguration(): array { @@ -3907,6 +3945,7 @@ public function getConsolidatedConfiguration(): array * Get Voorzieningen configuration as JSON object * * @return array The voorzieningen configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getVoorzieningenConfig(): array { @@ -3945,6 +3984,7 @@ public function getVoorzieningenConfig(): array * @param array $config The voorzieningen configuration. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function setVoorzieningenConfig(array $config): void { @@ -4019,6 +4059,7 @@ private function normalizeVoorzieningenConfig(array $input): array * to ensure consistency and avoid code duplication. * * @return array The AMEF configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getAmefConfig(): array { @@ -4091,6 +4132,7 @@ public function getAmefConfig(): array * @param array $config The AMEF configuration. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function setAmefConfig(array $config): void { @@ -4113,6 +4155,7 @@ public function setAmefConfig(array $config): void * Get Email configuration as JSON object * * @return array The email configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getEmailConfig(): array { @@ -4145,6 +4188,7 @@ public function getEmailConfig(): array * @param array $config The email configuration. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function setEmailConfig(array $config): void { @@ -4162,6 +4206,7 @@ public function setEmailConfig(array $config): void * and ensure consistency in ArchiMate status management. * * @return array The ArchiMate status with object counts + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getArchiMateStatus(): array { @@ -4399,6 +4444,7 @@ private function getAmefObjectCounts(): array * @param array $status The import status. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function setArchiMateImportStatus(array $status): void { @@ -4431,6 +4477,7 @@ public function setArchiMateImportStatus(array $status): void * @param array $status The export status. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function setArchiMateExportStatus(array $status): void { @@ -4461,6 +4508,7 @@ public function setArchiMateExportStatus(array $status): void * and ensure consistency in ArchiMate status management. * * @return array Clear operation result + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function clearArchiMateImportStatus(): array { @@ -4499,6 +4547,7 @@ public function clearArchiMateImportStatus(): array * * @return array Kill operation result * @deprecated Use cancelArchiMateImport() instead + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function killArchiMateImport(): array { @@ -4537,6 +4586,7 @@ public function killArchiMateImport(): array * import cancellation. It delegates to ArchiMateService for the actual work. * * @return array Cancellation result with detailed status + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function cancelArchiMateImport(): array { @@ -4576,6 +4626,7 @@ public function cancelArchiMateImport(): array * and ensure consistency in ArchiMate status management. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function clearArchiMateExportStatus(): void { @@ -4603,6 +4654,7 @@ public function clearArchiMateExportStatus(): void * This method reorganizes all the scattered config values into organized JSON objects * * @return array Compaction results + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function compactToJsonConfiguration(): array { @@ -4872,6 +4924,7 @@ public function compactToJsonConfiguration(): array * This method removes the old scattered config values after successful compaction * * @return array Cleanup results + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-3 */ public function cleanupOldConfiguration(): array { @@ -5000,6 +5053,7 @@ public function cleanupOldConfiguration(): array * This aggregates all settings data for the main settings endpoint * * @return array Complete settings data + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getAllSettings(): array { @@ -5054,6 +5108,7 @@ public function getAllSettings(): array * Get object counts statistics for all configured registers * * @return array Statistics for all registers with object counts + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getObjectCountsStatistics(): array { @@ -5150,6 +5205,7 @@ public function getObjectCountsStatistics(): array * This handles template iteration and individual failures * * @return array All email templates + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getAllEmailTemplates(): array { @@ -5180,6 +5236,7 @@ public function getAllEmailTemplates(): array * @param array $groups Groups to set. * * @return array Update result with validation. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function updateGenericUserGroups(array $groups): array { @@ -5221,6 +5278,7 @@ public function updateGenericUserGroups(array $groups): array * @param array $groups Groups to set. * * @return array Update result with validation. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function updateOrganizationAdminGroups(array $groups): array { @@ -5262,6 +5320,7 @@ public function updateOrganizationAdminGroups(array $groups): array * @param array $groups Groups to set. * * @return array Update result with validation. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function updateSuperUserGroups(array $groups): array { @@ -5305,6 +5364,7 @@ public function updateSuperUserGroups(array $groups): array * Get ArchiMate configuration only * * @return array ArchiMate configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getArchiMateConfig(): array { @@ -5338,6 +5398,7 @@ public function getArchiMateConfig(): array * @param array $config ArchiMate configuration data * * @return array Result of the update operation + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function updateArchiMateConfig(array $config): array { @@ -5368,6 +5429,7 @@ public function updateArchiMateConfig(array $config): array * Get email configuration only * * @return array Email configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function getEmailConfigFocused(): array { @@ -5401,6 +5463,7 @@ public function getEmailConfigFocused(): array * @param array $config Email configuration data * * @return array Result of the update operation + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-4 */ public function updateEmailConfig(array $config): array { @@ -5436,6 +5499,7 @@ public function updateEmailConfig(array $config): array * Get AMEF configuration only * * @return array AMEF configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getAmefConfigFocused(): array { @@ -5467,6 +5531,7 @@ public function getAmefConfigFocused(): array * @param array $config AMEF configuration data * * @return array Result of the update operation + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function updateAmefConfig(array $config): array { @@ -5599,6 +5664,7 @@ public function updateAmefConfig(array $config): array * Get Voorzieningen configuration only * * @return array Voorzieningen configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function getVoorzieningenConfigFocused(): array { @@ -5630,6 +5696,7 @@ public function getVoorzieningenConfigFocused(): array * @param array $config Voorzieningen configuration data * * @return array Result of the update operation + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-2 */ public function updateVoorzieningenConfig(array $config): array { @@ -5660,6 +5727,7 @@ public function updateVoorzieningenConfig(array $config): array * Get object counts only (lightweight) * * @return array Object counts for all registers + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getObjectsCounts(): array { @@ -5692,6 +5760,7 @@ public function getObjectsCounts(): array * Get object statistics (full statistics with configuration) * * @return array Full object statistics + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getObjectsStatistics(): array { @@ -5720,6 +5789,7 @@ public function getObjectsStatistics(): array * Get user groups configuration only * * @return array User groups configuration + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getUserGroupsConfig(): array { @@ -5756,6 +5826,7 @@ public function getUserGroupsConfig(): array * @param array $config User groups configuration data * * @return array Result of the update operation + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function updateUserGroupsConfig(array $config): array { @@ -5843,6 +5914,7 @@ public function setCatalogLocation(string $location): void * - dry_run: Only check what would be created (default: false) * * @return array Sync results with performance metrics + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function syncOrganisationsToVoorzieningenOptimized(array $options=[]): array { @@ -6164,6 +6236,7 @@ private function determineOrganisationType(\OCA\OpenRegister\Db\Organisation $or * Will be removed in a future version. * * @return array The cronjob configurations indexed by job name + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getCronjobConfig(): array { @@ -6241,6 +6314,7 @@ private function getAvailableCronjobs(): array * * @deprecated Cronjob context is no longer needed since sync operations use _rbac: false. * Will be removed in a future version. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function updateCronjobConfig(array $data): array { @@ -6322,6 +6396,7 @@ public function updateCronjobConfig(array $data): array * * @deprecated Cronjob context is no longer needed since sync operations use _rbac: false. * Will be removed in a future version. + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getCronjobContext(string $jobId): ?array { @@ -6364,6 +6439,7 @@ public function getCronjobContext(string $jobId): ?array * Will be removed in a future version. * * @return array List of users with id and display name + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getAvailableUsersForCronjobs(): array { @@ -6430,6 +6506,7 @@ public function getAvailableUsersForCronjobs(): array * Will be removed in a future version. * * @return array List of organisations with uuid and name + * @spec openspec/changes/retrofit-2026-05-26-settings-service/tasks.md#task-5 */ public function getAvailableOrganisationsForCronjobs(): array { diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index f1840ee6..4c7865bd 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -105,6 +105,7 @@ private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService * @param array $contactData The contact data array * * @return string Generated username + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function generateUsernameFromContactData(array $contactData): string { @@ -200,6 +201,7 @@ private function isValidUsername(string $username): bool * @param string $email The email address to sanitize. * * @return string The sanitized email suitable for use as a username. + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function sanitizeEmailForUsername(string $email): string { @@ -230,6 +232,7 @@ public function sanitizeEmailForUsername(string $email): string * @param string $email The email address to validate. * * @return string|null Null if valid, error message if invalid. + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function validateEmailForUsername(string $email): ?string { @@ -301,6 +304,7 @@ private function ensureUniqueUsername(string $username): string * @return \OCP\IUser|null The created user or null if failed. * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $isFirstContact is a simple role-assignment toggle + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function createUserAccount(object $contactpersoonObject, bool $isFirstContact=false): ?\OCP\IUser { @@ -921,6 +925,7 @@ private function addUserToGroupWithCheck(\OCP\IUser $user, string $groupName, st * @param array $contactData The updated contact person data * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function updateUserGroupsFromContactData(\OCP\IUser $user, array $contactData): void { @@ -1002,6 +1007,7 @@ public function updateUserGroupsFromContactData(\OCP\IUser $user, array $contact * * @return void * @deprecated Use updateUserGroupsFromContactData instead + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function updateUserGroupsFromRoles(\OCP\IUser $user, array $newRoles, array $oldRoles=[]): void { @@ -1144,6 +1150,7 @@ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup * @param array $objectData The contact data * * @return bool True if this is the first contact for the organization + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function isFirstContactForOrganization(object $contactObject, array $objectData): bool { @@ -1267,6 +1274,7 @@ private function getDisplayNameFromContactData(array $contactData): string * @param array $contactData The contact data containing name fields * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function storeContactNameFields(\OCP\IUser $user, array $contactData): void { @@ -1370,6 +1378,7 @@ public function storeContactNameFields(\OCP\IUser $user, array $contactData): vo * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function handleNewContact(object $contactObject): void { @@ -1400,6 +1409,7 @@ public function handleNewContact(object $contactObject): void * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function handleContactUpdate(object $contactObject): void { @@ -1430,6 +1440,7 @@ public function handleContactUpdate(object $contactObject): void * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function handleContactDeletion(object $contactObject): void { @@ -1484,6 +1495,7 @@ public function handleContactDeletion(object $contactObject): void * @param string $organizationUuid The organization UUID * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function assignBeheerderRole(object $contactpersoonObject, string $username, string $organizationUuid): void { @@ -1556,6 +1568,7 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna * @param string $managerUsername The manager's username * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function setUserManager(string $username, string $managerUsername): void { @@ -1610,6 +1623,7 @@ public function setUserManager(string $username, string $managerUsername): void * @param string $username The username * * @return string|null The manager's username or null if not set + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function getUserManager(string $username): ?string { @@ -1858,6 +1872,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi * @throws \Exception If processing fails * * @SuppressWarnings(PHPMD.BooleanArgumentFlag) $isUpdate is a simple create-vs-update toggle + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function processContactpersoon(object $contactpersoonObject, bool $isUpdate=false): bool { @@ -1999,6 +2014,7 @@ public function processContactpersoon(object $contactpersoonObject, bool $isUpda * @param string $username The username to set as inactive * * @return bool True if successful + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function setUserInactive(string $username): bool { @@ -2045,6 +2061,7 @@ public function setUserInactive(string $username): bool * @param string $username The username to set as active * * @return bool True if successful + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function setUserActive(string $username): bool { @@ -2092,6 +2109,7 @@ public function setUserActive(string $username): bool * @param object $oldContactpersoonObject The previous contactpersoon object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function handleContactpersoonUpdate(object $contactpersoonObject, object $oldContactpersoonObject): void { @@ -2207,6 +2225,7 @@ private function sendAccountSuspensionEmail(\OCP\IUser $user, array $objectData) * @param object $contactpersoonObject The contactpersoon object * * @return bool True if the user should be added to the organization + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function shouldAddContactpersoonToOrganization(object $contactpersoonObject): bool { @@ -2282,6 +2301,7 @@ public function shouldAddContactpersoonToOrganization(object $contactpersoonObje * @param object $contactpersoonObject The contactpersoon object * * @return bool True if the user was successfully added + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function addContactpersoonToOrganization(object $contactpersoonObject): bool { @@ -2392,6 +2412,7 @@ public function addContactpersoonToOrganization(object $contactpersoonObject): b * @param object $contactpersoonObject The contactpersoon object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function ensureContactpersoonInOrganization(object $contactpersoonObject): void { @@ -2460,6 +2481,7 @@ public function ensureContactpersoonInOrganization(object $contactpersoonObject) * (useful when organisatie field was removed from object data) * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-1 */ public function addUserToOrganizationEntity( object $contactpersoonObject, diff --git a/lib/Service/SoftwareCatalogue/GroupHandler.php b/lib/Service/SoftwareCatalogue/GroupHandler.php index 31322fc8..b0ded386 100644 --- a/lib/Service/SoftwareCatalogue/GroupHandler.php +++ b/lib/Service/SoftwareCatalogue/GroupHandler.php @@ -93,6 +93,7 @@ private function getObjectService(): ?ObjectService * Gets the list of generic user groups from configuration * * @return array Array of generic user groups + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function getGenericUserGroups(): array { @@ -120,6 +121,7 @@ public function getGenericUserGroups(): array * @param array $groups Array of generic user groups * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function setGenericUserGroups(array $groups): void { @@ -138,6 +140,7 @@ public function setGenericUserGroups(array $groups): void * Ensures that all generic user groups exist in the system * * @return array Array of groups that were created + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function ensureGenericUserGroupsExist(): array { @@ -193,6 +196,7 @@ public function ensureGenericUserGroupsExist(): array * @param string $groupName The group name to create * * @return IGroup|null The created or existing group + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function createGroupIfNotExists(string $groupName): ?IGroup { @@ -277,6 +281,7 @@ public function updateUserGroups(object $contactpersoonObject, string $username) * @param array $objectData The contactpersoon data * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function updateRoleBasedGroups(IUser $user, array $objectData): void { @@ -339,6 +344,7 @@ public function updateRoleBasedGroups(IUser $user, array $objectData): void * @param array $objectData The contactpersoon data * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function updateOrganizationGroups(IUser $user, array $objectData): void { @@ -411,6 +417,7 @@ public function updateOrganizationGroups(IUser $user, array $objectData): void * @param array $objectData The contactpersoon data * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function updateGemeenteGroups(IUser $user, array $objectData): void { @@ -467,6 +474,7 @@ public function updateGemeenteGroups(IUser $user, array $objectData): void * Gets all available groups with their information * * @return array Array of group information + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function getAllGroups(): array { @@ -491,6 +499,7 @@ public function getAllGroups(): array * @param array $groups Array of group names to validate * * @return array Array with validation results + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-2 */ public function validateGroups(array $groups): array { diff --git a/lib/Service/SoftwareCatalogue/HierarchyHandler.php b/lib/Service/SoftwareCatalogue/HierarchyHandler.php index 36edc151..1983a15d 100644 --- a/lib/Service/SoftwareCatalogue/HierarchyHandler.php +++ b/lib/Service/SoftwareCatalogue/HierarchyHandler.php @@ -69,6 +69,7 @@ public function __construct( * @param string $username The username being processed * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-3 */ public function ensureOrganizationBeheerder(object $contactgegevensObject, string $username): void { @@ -120,6 +121,7 @@ public function ensureOrganizationBeheerder(object $contactgegevensObject, strin * @param string $organizationUuid The organization UUID * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-3 */ public function setupManagerRelationships( string $username, diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/SoftwareCatalogue/OrganizationHandler.php index 1eca68c8..55ff3262 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/SoftwareCatalogue/OrganizationHandler.php @@ -160,6 +160,7 @@ public function processOrganization(object $organizationObject): bool * @param array $objectData The organization data * * @return string|null The group ID or null if failed + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function ensureOrganizationGroup(object $organizationObject, array &$objectData): ?string { @@ -253,6 +254,7 @@ private function ensureUniqueGroupName(string $baseName): string * @param string $groupName The group name to create * * @return IGroup|null The created or existing group + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function createGroupIfNotExists(string $groupName): ?IGroup { @@ -288,6 +290,7 @@ public function createGroupIfNotExists(string $groupName): ?IGroup * @param string $name The name to sanitize * * @return string The sanitized group name + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function sanitizeGroupName(string $name): string { @@ -616,6 +619,7 @@ private function mapFunctieToRoles(string $functie, bool $isFirstContact=false): * @param object $organizationObject The organization object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function handleNewOrganization(object $organizationObject): void { @@ -664,6 +668,7 @@ public function handleNewOrganization(object $organizationObject): void * @param string $organizationUuid The organization UUID * * @return array Array of usernames who are beheerders in this organization + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function getOrganizationBeheerders(string $organizationUuid): array { @@ -739,6 +744,7 @@ function ($a, $b) { * @param string $organizationUuid The organization UUID * * @return bool True if user belongs to organization + * @spec openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md#task-4 */ public function userBelongsToOrganization(IUser $user, string $organizationUuid): bool { diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 76de4807..24753b8f 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -388,6 +388,7 @@ public function processOrganization(object $organizationObject): bool * @param string $username The username to update groups for * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function updateUserGroups(object $contactpersoonObject, string $username): void { @@ -408,6 +409,7 @@ public function updateUserGroups(object $contactpersoonObject, string $username) * @param string $username The username being processed * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function ensureOrganizationBeheerder(object $contactpersoonObject, string $username): void { @@ -421,6 +423,7 @@ public function ensureOrganizationBeheerder(object $contactpersoonObject, string * @param string $username The username * * @return string|null The manager's username or null if not set + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function getUserManager(string $username): ?string { @@ -434,6 +437,7 @@ public function getUserManager(string $username): ?string * @param object $organizationObject The new organization object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function handleNewOrganization(object $organizationObject): void { @@ -621,6 +625,7 @@ public function handleNewOrganization(object $organizationObject): void * @param object $oldOrganizationObject The previous organization object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function handleOrganizationUpdate(object $organizationObject, object $oldOrganizationObject): void { @@ -790,6 +795,7 @@ private function activateContactpersonenForOrganization(string $organizationId): * @return void * * @deprecated This method is disabled to prevent organization duplication. + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function sendOrganizationWelcomeEmail(object $organizationObject): void { @@ -812,6 +818,7 @@ public function sendOrganizationWelcomeEmail(object $organizationObject): void * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function handleNewContact(object $contactObject): void { @@ -825,6 +832,7 @@ public function handleNewContact(object $contactObject): void * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function createUserForContactIfNotExists(object $contactObject): void { @@ -843,6 +851,7 @@ public function createUserForContactIfNotExists(object $contactObject): void * @param object $gebruikerObject The gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function handleNewGebruiker(object $gebruikerObject): void { @@ -861,6 +870,7 @@ public function handleNewGebruiker(object $gebruikerObject): void * @param object $gebruikerObject The gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function sendGebruikerWelcomeEmail(object $gebruikerObject): void { @@ -879,6 +889,7 @@ public function sendGebruikerWelcomeEmail(object $gebruikerObject): void * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function handleContactUpdate(object $contactObject): void { @@ -893,6 +904,7 @@ public function handleContactUpdate(object $contactObject): void * @param object $oldGebruikerObject The old gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function handleGebruikerUpdate(object $gebruikerObject, object $oldGebruikerObject): void { @@ -911,6 +923,7 @@ public function handleGebruikerUpdate(object $gebruikerObject, object $oldGebrui * @param object $contactObject The contact object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function handleContactDeletion(object $contactObject): void { @@ -924,6 +937,7 @@ public function handleContactDeletion(object $contactObject): void * @param object $gebruikerObject The gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function blockUserForGebruiker(object $gebruikerObject): void { @@ -942,6 +956,7 @@ public function blockUserForGebruiker(object $gebruikerObject): void * @param object $gebruikerObject The gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function temporarilyBlockUserForGebruiker(object $gebruikerObject): void { @@ -960,6 +975,7 @@ public function temporarilyBlockUserForGebruiker(object $gebruikerObject): void * @param object $gebruikerObject The gebruiker object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function restoreUserAccessForGebruiker(object $gebruikerObject): void { @@ -979,6 +995,7 @@ public function restoreUserAccessForGebruiker(object $gebruikerObject): void * @param mixed $revertPoint The revert point * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function syncUserWithRevertedContact(object $contactObject, mixed $revertPoint): void { @@ -998,6 +1015,7 @@ public function syncUserWithRevertedContact(object $contactObject, mixed $revert * @param mixed $revertPoint The revert point * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-3 */ public function updateUserFromRevertedGebruiker(object $gebruikerObject, mixed $revertPoint): void { @@ -1036,6 +1054,7 @@ public function setGenericUserGroups(array $groups): void * Ensures all generic user groups exist * * @return array Array of created/existing groups + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-4 */ public function ensureGenericUserGroupsExist(): array { @@ -1073,6 +1092,7 @@ public function getOrganizationStructure(string $organizationUuid): array * @param object $oldContactpersoonObject The previous contactpersoon object (optional) * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function handleContactpersoonUpdate(object $contactpersoonObject, object $oldContactpersoonObject=null): void { @@ -1240,6 +1260,7 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object * @param object $organizationObject The organization object being deleted * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function handleOrganizationDeletion(object $organizationObject): void { @@ -1284,6 +1305,7 @@ public function handleOrganizationDeletion(object $organizationObject): void * @param object $organizationObject The organization object to sync * * @return bool True if sync was successful + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function syncOrganizationWithOpenRegister(object $organizationObject): bool { @@ -1429,6 +1451,7 @@ public function syncOrganizationWithOpenRegister(object $organizationObject): bo * @param array $objectData The organization object data * * @return object|null The created organisation entity or null on failure + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-2 */ public function createOrganisationInOpenRegister(array $objectData): ?object { @@ -2705,6 +2728,7 @@ private function addAdminGroupUsersToOrganization(string $organizationUuid): voi * @param object $contactpersoonObject The contactpersoon object * * @return bool True if the user should be added to the organization + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function shouldAddContactpersoonToOrganization(object $contactpersoonObject): bool { @@ -2780,6 +2804,7 @@ public function shouldAddContactpersoonToOrganization(object $contactpersoonObje * @param object $contactpersoonObject The contactpersoon object * * @return bool True if the user was successfully added + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function addContactpersoonToOrganization(object $contactpersoonObject): bool { @@ -3150,6 +3175,7 @@ private function handleOwnershipAssignment(object $organizationObject): void * @param string $organizationUuid The UUID of the organization * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function syncContactPersonUsernamesWithOrganization(string $organizationUuid): void { @@ -3291,6 +3317,7 @@ public function syncContactPersonUsernamesWithOrganization(string $organizationU * @param object $contactPersonObject The contact person object * * @return void + * @spec openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md#task-1 */ public function ensureContactPersonInOrganization(object $contactPersonObject): void { diff --git a/lib/Service/SymfonyEmailService.php b/lib/Service/SymfonyEmailService.php index 547cb685..02e3d783 100644 --- a/lib/Service/SymfonyEmailService.php +++ b/lib/Service/SymfonyEmailService.php @@ -599,6 +599,7 @@ public function sendOrganizationRegistrationEmail(array $organization): bool * @return bool True if email was sent successfully, false otherwise. * * @throws \Exception If email sending fails. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-1 */ public function sendOrganizationActivationEmail(array $organization): bool { @@ -706,6 +707,7 @@ public function sendOrganizationActivationEmail(array $organization): bool * @return bool True if email was sent successfully, false otherwise. * * @throws \Exception If email sending fails. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-1 */ public function sendUserCreationEmail(array $user, array $organization=[]): bool { @@ -820,6 +822,7 @@ public function sendUserCreationEmail(array $user, array $organization=[]): bool * @return bool True if email was sent successfully, false otherwise. * * @throws \Exception If email sending fails. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-1 */ public function sendUserUpdateEmail(array $user, array $organization=[]): bool { @@ -935,6 +938,7 @@ public function sendUserUpdateEmail(array $user, array $organization=[]): bool * @return bool True if email was sent successfully, false otherwise. * * @throws \Exception If email sending fails. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-1 */ public function sendUserPasswordEmail(array $user, string $password, array $organization=[]): bool { @@ -1302,6 +1306,7 @@ private function validateEmail(string $email): bool * Gets the configured sender email address. * * @return string The sender email address. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function getSenderEmail(): string { @@ -1313,6 +1318,7 @@ public function getSenderEmail(): string * Gets the configured sender name. * * @return string The sender name. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function getSenderName(): string { @@ -1324,6 +1330,7 @@ public function getSenderName(): string * Gets all email settings including transport configuration. * * @return array The email settings. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function getEmailSettings(): array { @@ -1352,6 +1359,7 @@ public function getAvailableTransports(): array * @param string $email The sender email address. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function setSenderEmail(string $email): void { @@ -1370,6 +1378,7 @@ public function setSenderEmail(string $email): void * @param string $name The sender name. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function setSenderName(string $name): void { @@ -1385,6 +1394,7 @@ public function setSenderName(string $name): void * @param array $transportConfig Transport-specific configuration. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function setTransportConfiguration(string $transportType, array $transportConfig=[]): void { @@ -1404,6 +1414,7 @@ public function setTransportConfiguration(string $transportType, array $transpor * @param string $testEmail The email address to send test email to. * * @return bool True if test email was sent successfully. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-1 */ public function sendTestEmail(string $testEmail): bool { @@ -1471,6 +1482,7 @@ public function setEnabled(bool $enabled): void * @param string $email The test receiver override email address. * * @return void + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function setTestReceiverOverride(string $email): void { @@ -1533,6 +1545,7 @@ public function setUserPasswordEnabled(bool $enabled): void * Checks if the email system is fully configured with credentials and templates. * * @return array Configuration status with details. + * @spec openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md#task-2 */ public function isEmailSystemConfigured(): array { diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 73ead964..cbd8d584 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -161,6 +161,7 @@ public function getAllViews(array $options=[]): array * @param array $options Query options including enrichment flags. * * @return array View object with optional enrichments or error response. + * @spec openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md#task-2 */ public function getView(string $viewId, array $options=[]): array { diff --git a/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/design.md b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/design.md new file mode 100644 index 00000000..f86e95f4 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/design.md @@ -0,0 +1,3 @@ +# Design — aangeboden-gebruik-api + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 15 listed methods already exist and implement the 3 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/proposal.md b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/proposal.md new file mode 100644 index 00000000..3b947772 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/proposal.md @@ -0,0 +1,28 @@ +# Retrofit — aangeboden-gebruik-api + +Describes observed behavior of 15 methods as 3 REQ(s) under the `aangeboden-gebruik-api` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Controller/AangebodenGebruikController.php::getGebruiksWhereAfnemer +- lib/Controller/AangebodenGebruikController.php::getAllGebruiksForAmbtenaar +- lib/Controller/AangebodenGebruikController.php::getGebruiksWhereDeelnemers +- lib/Controller/AangebodenGebruikController.php::getSingleGebruikForAmbtenaar +- lib/Controller/AangebodenGebruikController.php::getKoppelingenGebruikByUuid +- lib/Controller/AangebodenGebruikController.php::setGebruikSelfToActiveOrg +- lib/Controller/AangebodenGebruikController.php::deleteGebruikAsAfnemer +- lib/Controller/AangebodenGebruikController.php::getApiDocumentation +- lib/Service/AangebodenGebruikService.php::getGebruiksWhereAfnemer +- lib/Service/AangebodenGebruikService.php::getAllGebruiksForAmbtenaar +- lib/Service/AangebodenGebruikService.php::getGebruiksWhereDeelnemers +- lib/Service/AangebodenGebruikService.php::getSingleGebruikForAmbtenaar +- lib/Service/AangebodenGebruikService.php::getKoppelingenGebruikByUuid +- lib/Service/AangebodenGebruikService.php::setGebruikSelfToActiveOrg +- lib/Service/AangebodenGebruikService.php::deleteGebruikAsAfnemer + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/specs/aangeboden-gebruik-api/spec.md b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/specs/aangeboden-gebruik-api/spec.md new file mode 100644 index 00000000..5284d5dc --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/specs/aangeboden-gebruik-api/spec.md @@ -0,0 +1,44 @@ +--- +status: draft +retrofit: true +--- + +# Aangeboden Gebruik Api Specification + +## Purpose + +Captures observed behavior of the 'aangeboden gebruik' (offered usage) read/write API — the controller endpoints and their backing service that expose usage records from the perspective of afnemer (consumer), ambtenaar (civil servant), and deelnemer (participant). + +## ADDED Requirements + +### REQ-001: The system SHALL list offered-usage records scoped to the caller's role + +The controller exposes role-scoped listings: `getGebruiksWhereAfnemer`, `getAllGebruiksForAmbtenaar`, `getGebruiksWhereDeelnemers`, and `getSingleGebruikForAmbtenaar`/`getKoppelingenGebruikByUuid` for single records. Each delegates to the matching `AangebodenGebruikService` method, which queries OpenRegister with the caller's active-organisation context and returns the usage records (and their koppelingen/links) as a JSON response. + +#### Scenario: REQ-001 case 1 +- WHEN `getGebruiksWhereAfnemer()` is called by an authenticated user +- THEN it MUST return the usage records where the active organisation is the afnemer + +#### Scenario: REQ-001 case 2 +- WHEN `getKoppelingenGebruikByUuid(uuid)` is called +- THEN it MUST return the koppelingen for that usage record + +### REQ-002: The system SHALL allow assigning and removing offered-usage for the caller's active organisation + +`setGebruikSelfToActiveOrg(gebruikId)` MUST attach the identified usage to the caller's active organisation; `deleteGebruikAsAfnemer(gebruikId)` MUST remove the caller's consumer link to that usage. Both delegate to the matching service method which performs the OpenRegister mutation and returns the result. + +#### Scenario: REQ-002 case 1 +- WHEN `setGebruikSelfToActiveOrg(id)` is called +- THEN the usage MUST be linked to the caller's active organisation + +#### Scenario: REQ-002 case 2 +- WHEN `deleteGebruikAsAfnemer(id)` is called +- THEN the caller's afnemer link to the usage MUST be removed + +### REQ-003: The system SHALL publish API documentation for the offered-usage endpoints + +`getApiDocumentation()` MUST return a machine-readable description of the offered-usage API surface. + +#### Scenario: REQ-003 case 1 +- WHEN `getApiDocumentation()` is called +- THEN it MUST return the offered-usage API documentation as a JSON response diff --git a/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md new file mode 100644 index 00000000..3cf8fde3 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-aangeboden-gebruik-api/tasks.md @@ -0,0 +1,5 @@ +# Tasks + +- [x] task-1: aangeboden-gebruik-api#REQ-001 — The system SHALL list offered-usage records scoped to the caller's role (retroactive annotation) +- [x] task-2: aangeboden-gebruik-api#REQ-002 — The system SHALL allow assigning and removing offered-usage for the caller's active organisation (retroactive annotation) +- [x] task-3: aangeboden-gebruik-api#REQ-003 — The system SHALL publish API documentation for the offered-usage endpoints (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-archimate-export/design.md b/openspec/changes/retrofit-2026-05-26-archimate-export/design.md new file mode 100644 index 00000000..03c889bc --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-export/design.md @@ -0,0 +1,3 @@ +# Design — archimate-export + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 12 listed methods already exist and implement the 2 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-archimate-export/proposal.md b/openspec/changes/retrofit-2026-05-26-archimate-export/proposal.md new file mode 100644 index 00000000..cb73edee --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-export/proposal.md @@ -0,0 +1,25 @@ +# Retrofit — archimate-export + +Describes observed behavior of 12 methods as 2 REQ(s) under the `archimate-export` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/ArchiMateExportService.php::exportArchiMateXml +- lib/Service/ArchiMateExportService.php::createCleanArchiMateXml +- lib/Service/ArchiMateExportService.php::getObjectsFromDatabase +- lib/Service/ArchiMateExportService.php::convertFromOpenRegisterObjects +- lib/Service/ArchiMateExportService.php::addObjectsToXml +- lib/Service/ArchiMateExportService.php::addElementsToXml +- lib/Service/ArchiMateExportService.php::addRelationshipsToXml +- lib/Service/ArchiMateExportService.php::addViewsToXml +- lib/Service/ArchiMateExportService.php::addOrganizationsToXml +- lib/Service/ArchiMateExportService.php::addPropertyDefinitionsToXml +- lib/Service/ArchiMateExportService.php::arrayToXml +- lib/Service/ArchiMateExportService.php::exportOrganizationArchiMateXml + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-archimate-export/specs/archimate-export/spec.md b/openspec/changes/retrofit-2026-05-26-archimate-export/specs/archimate-export/spec.md new file mode 100644 index 00000000..d42bb37e --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-export/specs/archimate-export/spec.md @@ -0,0 +1,32 @@ +--- +status: draft +retrofit: true +--- + +# Archimate Export Specification + +## Purpose + +Captures observed behavior of ArchiMateExportService — converting OpenRegister catalog objects into an ArchiMate Model Exchange Format (AMEF) XML document, including per-organisation scoped exports. + +## ADDED Requirements + +### REQ-001: The system SHALL build an ArchiMate AMEF XML document from catalog objects + +`exportArchiMateXml(...)` MUST produce a complete AMEF XML document. It builds the document via `createCleanArchiMateXml(modelMetadata)`, reads source objects with `getObjectsFromDatabase(...)`, converts them through `convertFromOpenRegisterObjects(objects,schemaIdMap)`, and appends each section with `addObjectsToXml`, `addElementsToXml`, `addRelationshipsToXml`, `addViewsToXml`, `addOrganizationsToXml`, and `addPropertyDefinitionsToXml`. `arrayToXml(data,xml)` recursively serialises arbitrary array data into the XML node. + +#### Scenario: REQ-001 case 1 +- WHEN `exportArchiMateXml(...)` is called with catalog objects present +- THEN it MUST return a well-formed AMEF XML document containing the elements, relationships and views + +#### Scenario: REQ-001 case 2 +- WHEN `createCleanArchiMateXml(metadata)` is called +- THEN it MUST return an XML element seeded with the model metadata + +### REQ-002: The system SHALL scope an ArchiMate export to a single organisation + +`exportOrganizationArchiMateXml(...)` MUST produce an AMEF XML document containing only the objects belonging to the requested organisation. + +#### Scenario: REQ-002 case 1 +- WHEN `exportOrganizationArchiMateXml(orgUuid, ...)` is called +- THEN the returned XML MUST contain only that organisation's objects diff --git a/openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md b/openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md new file mode 100644 index 00000000..77742be1 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-export/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: archimate-export#REQ-001 — The system SHALL build an ArchiMate AMEF XML document from catalog objects (retroactive annotation) +- [x] task-2: archimate-export#REQ-002 — The system SHALL scope an ArchiMate export to a single organisation (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-archimate-import/design.md b/openspec/changes/retrofit-2026-05-26-archimate-import/design.md new file mode 100644 index 00000000..5a8fc5a0 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-import/design.md @@ -0,0 +1,3 @@ +# Design — archimate-import + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 23 listed methods already exist and implement the 2 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-archimate-import/proposal.md b/openspec/changes/retrofit-2026-05-26-archimate-import/proposal.md new file mode 100644 index 00000000..29d88954 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-import/proposal.md @@ -0,0 +1,36 @@ +# Retrofit — archimate-import + +Describes observed behavior of 23 methods as 2 REQ(s) under the `archimate-import` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/ArchiMateImportService.php::importArchiMateFileFromPath +- lib/Service/ArchiMateImportService.php::importArchiMateFileFromPathOptimized +- lib/Service/ArchiMateImportService.php::xmlToArray +- lib/Service/ArchiMateImportService.php::getPropertyNameMapping +- lib/Service/ArchiMateImportService.php::getAmefConfig +- lib/Service/ArchiMateService.php::importArchiMateFileFromPath +- lib/Service/ArchiMateService.php::importArchiMateFileFromPathOptimized +- lib/Service/ArchiMateService.php::exportToArchiMate +- lib/Service/ArchiMateService.php::exportOrgArchiMate +- lib/Service/ArchiMateService.php::testRoundTrip +- lib/Service/ArchiMateService.php::getAmefConfig +- lib/Service/ArchiMateService.php::getArchiMateStatus +- lib/Service/ArchiMateService.php::getElementObjects +- lib/Service/ArchiMateService.php::getOrganizationObjects +- lib/Service/ArchiMateService.php::getViewObjects +- lib/Service/ArchiMateService.php::getRelationshipObjects +- lib/Service/ArchiMateService.php::getModelObjects +- lib/Service/ArchiMateService.php::getPropertyObjects +- lib/Service/ArchiMateService.php::getPropertyDefinitionObjects +- lib/Service/ArchiMateService.php::isImportInProgress +- lib/Service/ArchiMateService.php::isExportInProgress +- lib/Service/ArchiMateService.php::isOperationInProgress +- lib/Service/ArchiMateService.php::getPropertyNameMapping + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-archimate-import/specs/archimate-import/spec.md b/openspec/changes/retrofit-2026-05-26-archimate-import/specs/archimate-import/spec.md new file mode 100644 index 00000000..537c56f1 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-import/specs/archimate-import/spec.md @@ -0,0 +1,36 @@ +--- +status: draft +retrofit: true +--- + +# Archimate Import Specification + +## Purpose + +Captures observed behavior of ArchiMateImportService and the ArchiMateService facade — parsing AMEF XML into catalog objects, the optimised + plain import paths, round-trip testing, operation-status reporting, and the typed object queries. + +## ADDED Requirements + +### REQ-001: The system SHALL parse an ArchiMate AMEF file into catalog objects + +`importArchiMateFileFromPath(options)` and `importArchiMateFileFromPathOptimized(options)` MUST read an AMEF XML file (via `xmlToArray(xml)`) and persist the parsed elements/relationships/views/organisations as OpenRegister objects, returning an import-result summary. `getPropertyNameMapping(propDefMap)` MUST resolve property-definition ids to their human names; `getAmefConfig()` MUST return the AMEF register/schema configuration used by the import. + +#### Scenario: REQ-001 case 1 +- WHEN `importArchiMateFileFromPath(options)` is called with a valid AMEF file +- THEN the parsed objects MUST be persisted and a result summary returned + +#### Scenario: REQ-001 case 2 +- WHEN `xmlToArray(xml)` is called +- THEN it MUST return the array representation of the AMEF XML + +### REQ-002: The system SHALL expose ArchiMate import/export, round-trip and operation status via a facade + +`ArchiMateService` is the facade: `exportToArchiMate(organization)` and `exportOrgArchiMate(orgUuid,options)` produce exports; `testRoundTrip()` exports-then-imports to validate fidelity; `getArchiMateStatus()` reports the current operation status; `isImportInProgress()`/`isExportInProgress()`/`isOperationInProgress()` report liveness. The typed query helpers (`getElementObjects`, `getOrganizationObjects`, `getViewObjects`, `getRelationshipObjects`, `getModelObjects`, `getPropertyObjects`, `getPropertyDefinitionObjects`) MUST return the matching catalog objects for the supplied query. + +#### Scenario: REQ-002 case 1 +- WHEN `testRoundTrip()` is called +- THEN it MUST export then re-import and report whether the model survived intact + +#### Scenario: REQ-002 case 2 +- WHEN `isOperationInProgress()` is called during an import +- THEN it MUST return true diff --git a/openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md b/openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md new file mode 100644 index 00000000..08b901a4 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-archimate-import/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: archimate-import#REQ-001 — The system SHALL parse an ArchiMate AMEF file into catalog objects (retroactive annotation) +- [x] task-2: archimate-import#REQ-002 — The system SHALL expose ArchiMate import/export, round-trip and operation status via a facade (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-contactpersonen-api/design.md b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/design.md new file mode 100644 index 00000000..0c31e9e6 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/design.md @@ -0,0 +1,3 @@ +# Design — contactpersonen-api + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 12 listed methods already exist and implement the 3 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-contactpersonen-api/proposal.md b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/proposal.md new file mode 100644 index 00000000..8cd3044e --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/proposal.md @@ -0,0 +1,25 @@ +# Retrofit — contactpersonen-api + +Describes observed behavior of 12 methods as 3 REQ(s) under the `contactpersonen-api` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Controller/ContactpersonenController.php::getContactpersonen +- lib/Controller/ContactpersonenController.php::getContactPersonsWithUserDetailsForOrganization +- lib/Controller/ContactpersonenController.php::getUserInfo +- lib/Controller/ContactpersonenController.php::getBulkUserInfo +- lib/Controller/ContactpersonenController.php::testBulkUserInfo +- lib/Controller/ContactpersonenController.php::getMe +- lib/Controller/ContactpersonenController.php::convertToUser +- lib/Controller/ContactpersonenController.php::changePassword +- lib/Controller/ContactpersonenController.php::updateUserGroups +- lib/Controller/ContactpersonenController.php::disableUser +- lib/Controller/ContactpersonenController.php::enableUser +- lib/Controller/ContactpersonenController.php::getAvailableGroups + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-contactpersonen-api/specs/contactpersonen-api/spec.md b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/specs/contactpersonen-api/spec.md new file mode 100644 index 00000000..8282de48 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/specs/contactpersonen-api/spec.md @@ -0,0 +1,44 @@ +--- +status: draft +retrofit: true +--- + +# Contactpersonen Api Specification + +## Purpose + +Captures observed behavior of the contactpersonen (contact persons) management API — listing an organisation's contacts, converting a contact into a Nextcloud user, and the user-account lifecycle (password, groups, enable/disable, info lookups). + +## ADDED Requirements + +### REQ-001: The system SHALL list an organisation's contact persons with their linked user details + +`getContactpersonen(organisationId)`, `getContactPersonsWithUserDetailsForOrganization(organizationUuid)`, `getUserInfo(contactpersoonId)`, `getBulkUserInfo()`/`testBulkUserInfo()`, and `getMe()` MUST return contact-person records — optionally enriched with the linked Nextcloud user's account details — as JSON responses. + +#### Scenario: REQ-001 case 1 +- WHEN `getContactpersonen(orgId)` is called +- THEN it MUST return the organisation's contact persons + +#### Scenario: REQ-001 case 2 +- WHEN `getMe()` is called +- THEN it MUST return the current user's contact-person profile + +### REQ-002: The system SHALL convert a contact person into a Nextcloud user account + +`convertToUser(contactpersoonId)` MUST create a Nextcloud user for the contact person (if one does not already exist) and link it back to the contact record, returning the result as a JSON response. + +#### Scenario: REQ-002 case 1 +- WHEN `convertToUser(id)` is called for a contact without a user +- THEN a Nextcloud user MUST be created and linked to the contact + +### REQ-003: The system SHALL manage the lifecycle of a contact person's linked user account + +`changePassword(username,newPassword)`, `updateUserGroups(username,groups)`, `disableUser(contactpersoonId)`, `enableUser(contactpersoonId)`, and `getAvailableGroups()` MUST apply the requested account mutation (password reset, group membership change, enable/disable) or return the available groups, each as a JSON response. + +#### Scenario: REQ-003 case 1 +- WHEN `disableUser(id)` is called +- THEN the linked user account MUST be disabled + +#### Scenario: REQ-003 case 2 +- WHEN `updateUserGroups(username, groups)` is called +- THEN the user's group membership MUST be set to the supplied groups diff --git a/openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md new file mode 100644 index 00000000..c46869e6 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersonen-api/tasks.md @@ -0,0 +1,5 @@ +# Tasks + +- [x] task-1: contactpersonen-api#REQ-001 — The system SHALL list an organisation's contact persons with their linked user details (retroactive annotation) +- [x] task-2: contactpersonen-api#REQ-002 — The system SHALL convert a contact person into a Nextcloud user account (retroactive annotation) +- [x] task-3: contactpersonen-api#REQ-003 — The system SHALL manage the lifecycle of a contact person's linked user account (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/design.md b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/design.md new file mode 100644 index 00000000..0b35cee3 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/design.md @@ -0,0 +1,3 @@ +# Design — contactpersoon-sync + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 11 listed methods already exist and implement the 2 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/proposal.md b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/proposal.md new file mode 100644 index 00000000..93108408 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/proposal.md @@ -0,0 +1,24 @@ +# Retrofit — contactpersoon-sync + +Describes observed behavior of 11 methods as 2 REQ(s) under the `contactpersoon-sync` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/ContactpersoonService.php::processContactpersoon +- lib/Service/ContactpersoonService.php::handleContactpersoonUpdate +- lib/Service/ContactpersoonService.php::handleContactDeletion +- lib/Service/ContactpersoonService.php::updateUserGroups +- lib/Service/ContactpersoonService.php::ensureOrganizationBeheerder +- lib/Service/ContactpersoonService.php::getUserManager +- lib/Service/ContactpersoonService.php::getContactPersonsForOrganization +- lib/Service/ContactpersoonService.php::getContactPersonsWithUserDetailsForOrganization +- lib/Service/ContactpersoonService.php::getBulkUserInfo +- lib/Service/ContactpersoonService.php::enableUserForContactpersoon +- lib/Service/ContactpersoonService.php::disableUserForContactpersoon + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/specs/contactpersoon-sync/spec.md b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/specs/contactpersoon-sync/spec.md new file mode 100644 index 00000000..78a6e161 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/specs/contactpersoon-sync/spec.md @@ -0,0 +1,36 @@ +--- +status: draft +retrofit: true +--- + +# Contactpersoon Sync Specification + +## Purpose + +Captures observed behavior of ContactpersoonService — synchronising SoftwareCatalog contact-person records to Nextcloud user accounts: processing creates/updates, assigning beheerder roles and groups, resolving the user's manager, and enabling/disabling/deleting the linked account. + +## ADDED Requirements + +### REQ-001: The system SHALL process a contact person into a managed Nextcloud user + +`processContactpersoon(contactpersoonObject,isUpdate)` MUST create or update the linked Nextcloud user from the contact record; `handleContactpersoonUpdate(new,old)` MUST reconcile changes; `handleContactDeletion(contactObject)` MUST handle removal. `updateUserGroups(contact,username)` and `ensureOrganizationBeheerder(contact,username)` MUST set group membership / beheerder role. `getUserManager(username)` MUST resolve the user's configured manager. + +#### Scenario: REQ-001 case 1 +- WHEN `processContactpersoon(obj, false)` is called for a new contact +- THEN a linked user MUST be created and configured + +#### Scenario: REQ-001 case 2 +- WHEN `ensureOrganizationBeheerder(obj, username)` is called +- THEN the user MUST be granted the organisation beheerder role + +### REQ-002: The system SHALL list and look up contact persons' user details and toggle their accounts + +`getContactPersonsForOrganization(orgUuid)` and `getContactPersonsWithUserDetailsForOrganization(orgUuid)` MUST list an organisation's contacts (the latter enriched with account details); `getBulkUserInfo(ids)` MUST batch-resolve account details; `enableUserForContactpersoon(id)` / `disableUserForContactpersoon(id)` MUST toggle the linked account's enabled state. + +#### Scenario: REQ-002 case 1 +- WHEN `getContactPersonsWithUserDetailsForOrganization(orgUuid)` is called +- THEN each contact MUST be returned with its linked account details + +#### Scenario: REQ-002 case 2 +- WHEN `disableUserForContactpersoon(id)` is called +- THEN the linked account MUST be disabled diff --git a/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md new file mode 100644 index 00000000..445bc4e8 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-contactpersoon-sync/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: contactpersoon-sync#REQ-001 — The system SHALL process a contact person into a managed Nextcloud user (retroactive annotation) +- [x] task-2: contactpersoon-sync#REQ-002 — The system SHALL list and look up contact persons' user details and toggle their accounts (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-cronjob-context/design.md b/openspec/changes/retrofit-2026-05-26-cronjob-context/design.md new file mode 100644 index 00000000..a0f1b349 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-cronjob-context/design.md @@ -0,0 +1,3 @@ +# Design — cronjob-context + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 3 listed methods already exist and implement the 2 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-cronjob-context/proposal.md b/openspec/changes/retrofit-2026-05-26-cronjob-context/proposal.md new file mode 100644 index 00000000..ea21ae06 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-cronjob-context/proposal.md @@ -0,0 +1,16 @@ +# Retrofit — cronjob-context + +Describes observed behavior of 3 methods as 2 REQ(s) under the `cronjob-context` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/BackgroundJob/CronjobContextTrait.php::setCronjobContext +- lib/BackgroundJob/CronjobContextTrait.php::clearCronjobContext +- lib/BackgroundJob/OrganizationContactSyncJob.php::run + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-cronjob-context/specs/cronjob-context/spec.md b/openspec/changes/retrofit-2026-05-26-cronjob-context/specs/cronjob-context/spec.md new file mode 100644 index 00000000..40efd4d9 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-cronjob-context/specs/cronjob-context/spec.md @@ -0,0 +1,32 @@ +--- +status: draft +retrofit: true +--- + +# Cronjob Context Specification + +## Purpose + +Captures observed behavior of the cronjob user/organisation context trait and the scheduled organisation-contact sync background job. + +## ADDED Requirements + +### REQ-001: The system SHALL set and clear a per-job user/organisation context for cronjob execution + +`CronjobContextTrait::setCronjobContext(jobId)` MUST resolve the configured cronjob user/organisation from app config and establish it for the duration of the job, returning whether the context was successfully set. `clearCronjobContext(jobId)` MUST tear that context down afterwards. The trait is documented as deprecated (sync now runs with `_rbac:false`) but the methods remain wired. + +#### Scenario: REQ-001 case 1 +- WHEN `setCronjobContext('org-sync')` is called with a configured user +- THEN it MUST return a boolean indicating whether context was established + +#### Scenario: REQ-001 case 2 +- WHEN `clearCronjobContext('org-sync')` is called after a job +- THEN it MUST reset the previously-set user/organisation context + +### REQ-002: The system SHALL run the scheduled organisation-contact synchronisation as a background job + +`OrganizationContactSyncJob::run(argument)` MUST execute the organisation + contact synchronisation pipeline on its timed interval, operating as a system-level (non-RBAC) sync. + +#### Scenario: REQ-002 case 1 +- WHEN the background job interval elapses +- THEN `run()` MUST trigger the organisation/contact sync pipeline diff --git a/openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md b/openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md new file mode 100644 index 00000000..8b0196dd --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-cronjob-context/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: cronjob-context#REQ-001 — The system SHALL set and clear a per-job user/organisation context for cronjob execution (retroactive annotation) +- [x] task-2: cronjob-context#REQ-002 — The system SHALL run the scheduled organisation-contact synchronisation as a background job (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-dashboard-views-api/design.md b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/design.md new file mode 100644 index 00000000..cccf69bd --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/design.md @@ -0,0 +1,3 @@ +# Design — dashboard-views-api + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 9 listed methods already exist and implement the 3 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-dashboard-views-api/proposal.md b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/proposal.md new file mode 100644 index 00000000..5bfbcb5d --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/proposal.md @@ -0,0 +1,22 @@ +# Retrofit — dashboard-views-api + +Describes observed behavior of 9 methods as 3 REQ(s) under the `dashboard-views-api` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Controller/DashboardController.php::page +- lib/Controller/DashboardController.php::index +- lib/Controller/ViewController.php::getAllViews +- lib/Controller/ViewController.php::getView +- lib/Controller/ViewController.php::getApiDocumentation +- lib/Service/ViewService.php::getAllViews +- lib/Service/ViewService.php::getView +- lib/Controller/PreferencesController.php::getPreference +- lib/Controller/PreferencesController.php::setPreference + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-dashboard-views-api/specs/dashboard-views-api/spec.md b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/specs/dashboard-views-api/spec.md new file mode 100644 index 00000000..d816b9c5 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/specs/dashboard-views-api/spec.md @@ -0,0 +1,44 @@ +--- +status: draft +retrofit: true +--- + +# Dashboard Views Api Specification + +## Purpose + +Captures observed behavior of the dashboard page/index endpoints, the saved-views read API, and the generic per-user preferences endpoint. + +## ADDED Requirements + +### REQ-001: The system SHALL render the app page and expose dashboard data + +`DashboardController::page(getParameter)` MUST return the app's main TemplateResponse; `DashboardController::index()` MUST return the dashboard's aggregate data as a JSON response. + +#### Scenario: REQ-001 case 1 +- WHEN `page()` is requested +- THEN it MUST return the app shell TemplateResponse + +#### Scenario: REQ-001 case 2 +- WHEN `index()` is called +- THEN it MUST return dashboard data as JSON + +### REQ-002: The system SHALL expose saved views over the API + +`ViewController::getAllViews()` / `ViewService::getAllViews(options)` MUST list saved views; `ViewController::getView(viewId)` / `ViewService::getView(viewId,options)` MUST return a single view; `ViewController::getApiDocumentation()` MUST return the views API documentation. + +#### Scenario: REQ-002 case 1 +- WHEN `getAllViews()` is called +- THEN it MUST return the list of saved views + +#### Scenario: REQ-002 case 2 +- WHEN `getView(viewId)` is called with an existing id +- THEN it MUST return that view + +### REQ-003: The system SHALL store and retrieve generic per-user preferences by key + +`PreferencesController::getPreference(key)` MUST return the current user's stored value for the key; `setPreference(key,value)` MUST persist a value for the current user. Values are scoped per user. + +#### Scenario: REQ-003 case 1 +- WHEN `setPreference('listColumns', json)` then `getPreference('listColumns')` is called +- THEN the stored value MUST be returned for the same user diff --git a/openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md new file mode 100644 index 00000000..fb7e395c --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-dashboard-views-api/tasks.md @@ -0,0 +1,5 @@ +# Tasks + +- [x] task-1: dashboard-views-api#REQ-001 — The system SHALL render the app page and expose dashboard data (retroactive annotation) +- [x] task-2: dashboard-views-api#REQ-002 — The system SHALL expose saved views over the API (retroactive annotation) +- [x] task-3: dashboard-views-api#REQ-003 — The system SHALL store and retrieve generic per-user preferences by key (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-email-delivery/design.md b/openspec/changes/retrofit-2026-05-26-email-delivery/design.md new file mode 100644 index 00000000..f2a20d2e --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-email-delivery/design.md @@ -0,0 +1,3 @@ +# Design — email-delivery + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 20 listed methods already exist and implement the 2 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-email-delivery/proposal.md b/openspec/changes/retrofit-2026-05-26-email-delivery/proposal.md new file mode 100644 index 00000000..42dfe52d --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-email-delivery/proposal.md @@ -0,0 +1,33 @@ +# Retrofit — email-delivery + +Describes observed behavior of 20 methods as 2 REQ(s) under the `email-delivery` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/SymfonyEmailService.php::sendOrganizationRegistrationEmail +- lib/Service/SymfonyEmailService.php::sendOrganizationActivationEmail +- lib/Service/SymfonyEmailService.php::sendUserCreationEmail +- lib/Service/SymfonyEmailService.php::sendUserUpdateEmail +- lib/Service/SymfonyEmailService.php::sendUserPasswordEmail +- lib/Service/SymfonyEmailService.php::sendTestEmail +- lib/Service/SymfonyEmailService.php::getSenderEmail +- lib/Service/SymfonyEmailService.php::getSenderName +- lib/Service/SymfonyEmailService.php::getEmailSettings +- lib/Service/SymfonyEmailService.php::getAvailableTransports +- lib/Service/SymfonyEmailService.php::setSenderEmail +- lib/Service/SymfonyEmailService.php::setSenderName +- lib/Service/SymfonyEmailService.php::setTransportConfiguration +- lib/Service/SymfonyEmailService.php::setEnabled +- lib/Service/SymfonyEmailService.php::setTestReceiverOverride +- lib/Service/SymfonyEmailService.php::setOrganizationRegistrationEnabled +- lib/Service/SymfonyEmailService.php::setOrganizationActivationEnabled +- lib/Service/SymfonyEmailService.php::setUserCreationEnabled +- lib/Service/SymfonyEmailService.php::setUserPasswordEnabled +- lib/Service/SymfonyEmailService.php::isEmailSystemConfigured + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-email-delivery/specs/email-delivery/spec.md b/openspec/changes/retrofit-2026-05-26-email-delivery/specs/email-delivery/spec.md new file mode 100644 index 00000000..0bc39253 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-email-delivery/specs/email-delivery/spec.md @@ -0,0 +1,36 @@ +--- +status: draft +retrofit: true +--- + +# Email Delivery Specification + +## Purpose + +Captures observed behavior of SymfonyEmailService — composing and sending the app's transactional notifications (organisation registration/activation, user creation/update/password) over a configurable Symfony Mailer transport, plus the transport-configuration getters/setters and test-send. + +## ADDED Requirements + +### REQ-001: The system SHALL send the app's transactional notification emails + +`sendOrganizationRegistrationEmail`, `sendOrganizationActivationEmail`, `sendUserCreationEmail`, `sendUserUpdateEmail`, `sendUserPasswordEmail`, and `sendTestEmail` MUST render the corresponding template and dispatch the message via the configured transport, returning whether the send succeeded. Sends are skipped when the corresponding notification type is disabled. + +#### Scenario: REQ-001 case 1 +- WHEN `sendUserPasswordEmail(user, password, org)` is called and the type is enabled +- THEN the password email MUST be dispatched and true returned + +#### Scenario: REQ-001 case 2 +- WHEN a notification type is disabled and its send method is called +- THEN the email MUST NOT be sent + +### REQ-002: The system SHALL expose and persist the mail transport configuration + +`getSenderEmail`/`setSenderEmail`, `getSenderName`/`setSenderName`, `getEmailSettings`, `getAvailableTransports`, `setTransportConfiguration`, `setEnabled`, `setTestReceiverOverride`, the per-type enable toggles (`setOrganizationRegistrationEnabled`, `setOrganizationActivationEnabled`, `setUserCreationEnabled`, `setUserPasswordEnabled`), and `isEmailSystemConfigured()` MUST read/persist the transport + sender + per-notification configuration and report whether the system is fully configured. + +#### Scenario: REQ-002 case 1 +- WHEN `setTransportConfiguration('smtp', cfg)` is called +- THEN the SMTP transport configuration MUST be persisted + +#### Scenario: REQ-002 case 2 +- WHEN `isEmailSystemConfigured()` is called with a sender and transport set +- THEN it MUST report configured diff --git a/openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md b/openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md new file mode 100644 index 00000000..76139077 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-email-delivery/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: email-delivery#REQ-001 — The system SHALL send the app's transactional notification emails (retroactive annotation) +- [x] task-2: email-delivery#REQ-002 — The system SHALL expose and persist the mail transport configuration (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-organization-sync/design.md b/openspec/changes/retrofit-2026-05-26-organization-sync/design.md new file mode 100644 index 00000000..c91bf068 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-organization-sync/design.md @@ -0,0 +1,3 @@ +# Design — organization-sync + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 13 listed methods already exist and implement the 3 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-organization-sync/proposal.md b/openspec/changes/retrofit-2026-05-26-organization-sync/proposal.md new file mode 100644 index 00000000..c284bac4 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-organization-sync/proposal.md @@ -0,0 +1,26 @@ +# Retrofit — organization-sync + +Describes observed behavior of 13 methods as 3 REQ(s) under the `organization-sync` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/OrganizationSyncService.php::performOrganizationsSync +- lib/Service/OrganizationSyncService.php::performContactSync +- lib/Service/OrganizationSyncService.php::performUserSync +- lib/Service/OrganizationSyncService.php::performFullSync +- lib/Service/OrganizationSyncService.php::performScheduledSync +- lib/Service/OrganizationSyncService.php::performManualSync +- lib/Service/OrganizationSyncService.php::performOptimizedManualSync +- lib/Service/OrganizationSyncService.php::processSpecificOrganization +- lib/Service/OrganizationSyncService.php::processSpecificContactPerson +- lib/Service/OrganizationSyncService.php::ensureOrganisationEntityPublic +- lib/Service/OrganizationSyncService.php::getSyncStatus +- lib/Service/OrganizationSyncService.php::getSyncStatusWithErrorHandling +- lib/Service/OrganizationSyncService.php::recordSyncTime + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-organization-sync/specs/organization-sync/spec.md b/openspec/changes/retrofit-2026-05-26-organization-sync/specs/organization-sync/spec.md new file mode 100644 index 00000000..38acb35b --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-organization-sync/specs/organization-sync/spec.md @@ -0,0 +1,48 @@ +--- +status: draft +retrofit: true +--- + +# Organization Sync Specification + +## Purpose + +Captures observed behavior of OrganizationSyncService — the batch synchronisation pipeline that mirrors SoftwareCatalog organisations, contacts and users into OpenRegister, including scheduled/manual/optimised variants, single-record processing, status reporting, and sync-time bookkeeping. + +## ADDED Requirements + +### REQ-001: The system SHALL run batched organisation/contact/user synchronisation in multiple modes + +`performOrganizationsSync`, `performContactSync`, `performUserSync`, and `performFullSync(minutesBack)` MUST sync the respective entity type from SoftwareCatalog into OpenRegister in batches with an execution-time budget, returning sync statistics. The orchestration variants `performScheduledSync`, `performManualSync`, and `performOptimizedManualSync` MUST drive the full pipeline for cron / on-demand / multi-round optimised execution. + +#### Scenario: REQ-001 case 1 +- WHEN `performFullSync(10)` is called +- THEN organisations, contacts and users changed in the last 10 minutes MUST be synced and stats returned + +#### Scenario: REQ-001 case 2 +- WHEN `performOptimizedManualSync(maxRounds, batchSize)` is called +- THEN it MUST run repeated rounds until no further work remains or the round cap is reached + +### REQ-002: The system SHALL process individual organisation/contact records and ensure their OpenRegister entity + +`processSpecificOrganization(obj)` and `processSpecificContactPerson(obj)` MUST sync a single record; `ensureOrganisationEntityPublic(obj,&stats,sendEmails)` MUST guarantee an OpenRegister organisation entity exists for the SoftwareCatalog organisation (optionally sending notification emails) and update the running stats. + +#### Scenario: REQ-002 case 1 +- WHEN `processSpecificOrganization(obj)` is called +- THEN that one organisation MUST be synced to OpenRegister + +#### Scenario: REQ-002 case 2 +- WHEN `ensureOrganisationEntityPublic(obj, stats, true)` is called for a new organisation +- THEN the OR entity MUST be created and notification emails sent + +### REQ-003: The system SHALL report synchronisation status and record sync timestamps + +`getSyncStatus(minutesBack)` and `getSyncStatusWithErrorHandling(minutesBack)` MUST report what would/did change in the window (the latter never throwing); `recordSyncTime()` MUST persist the last-sync timestamp used to scope incremental syncs. + +#### Scenario: REQ-003 case 1 +- WHEN `getSyncStatus(10)` is called +- THEN it MUST report the records changed in the last 10 minutes + +#### Scenario: REQ-003 case 2 +- WHEN `recordSyncTime()` is called after a sync +- THEN the last-sync timestamp MUST be persisted diff --git a/openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md b/openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md new file mode 100644 index 00000000..f89c6e3b --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-organization-sync/tasks.md @@ -0,0 +1,5 @@ +# Tasks + +- [x] task-1: organization-sync#REQ-001 — The system SHALL run batched organisation/contact/user synchronisation in multiple modes (retroactive annotation) +- [x] task-2: organization-sync#REQ-002 — The system SHALL process individual organisation/contact records and ensure their OpenRegister entity (retroactive annotation) +- [x] task-3: organization-sync#REQ-003 — The system SHALL report synchronisation status and record sync timestamps (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-progress-tracking-2/design.md b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/design.md new file mode 100644 index 00000000..3dc98034 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/design.md @@ -0,0 +1,3 @@ +# Design — progress-tracking-2 + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 1 listed methods already exist and implement the 1 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-progress-tracking-2/proposal.md b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/proposal.md new file mode 100644 index 00000000..8a5702c5 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/proposal.md @@ -0,0 +1,14 @@ +# Retrofit — progress-tracking-2 + +Describes observed behavior of 1 methods as 1 REQ(s) under the `progress-tracking` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/ProgressTracker.php::cleanupOldProgress + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-progress-tracking-2/specs/progress-tracking/spec.md b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/specs/progress-tracking/spec.md new file mode 100644 index 00000000..bdb1a565 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/specs/progress-tracking/spec.md @@ -0,0 +1,16 @@ +--- +retrofit_extensions: + - REQ-006 +--- + +# Progress Tracking Specification + +## ADDED Requirements + +### REQ-006: The system SHALL clean up stale progress-tracking records + +`ProgressTracker::cleanupOldProgress(maxAge)` MUST remove progress records older than `maxAge` seconds (default 3600), preventing unbounded accumulation of completed/abandoned operation state. + +#### Scenario: REQ-006 case 1 +- WHEN `cleanupOldProgress(3600)` is called +- THEN progress records older than one hour MUST be removed diff --git a/openspec/changes/retrofit-2026-05-26-progress-tracking-2/tasks.md b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/tasks.md new file mode 100644 index 00000000..3839d291 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-progress-tracking-2/tasks.md @@ -0,0 +1,3 @@ +# Tasks + +- [x] task-1: progress-tracking#REQ-006 — The system SHALL clean up stale progress-tracking records (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-repair-init/design.md b/openspec/changes/retrofit-2026-05-26-repair-init/design.md new file mode 100644 index 00000000..064242be --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-repair-init/design.md @@ -0,0 +1,3 @@ +# Design — repair-init + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 2 listed methods already exist and implement the 1 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-repair-init/proposal.md b/openspec/changes/retrofit-2026-05-26-repair-init/proposal.md new file mode 100644 index 00000000..e1af3ca4 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-repair-init/proposal.md @@ -0,0 +1,15 @@ +# Retrofit — repair-init + +Describes observed behavior of 2 methods as 1 REQ(s) under the `repair-init` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Repair/InitializeSettings.php::run +- lib/Repair/InitializeSettings.php::getName + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-repair-init/specs/repair-init/spec.md b/openspec/changes/retrofit-2026-05-26-repair-init/specs/repair-init/spec.md new file mode 100644 index 00000000..34d06627 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-repair-init/specs/repair-init/spec.md @@ -0,0 +1,24 @@ +--- +status: draft +retrofit: true +--- + +# Repair Init Specification + +## Purpose + +Captures observed behavior of the install/upgrade repair step that initialises default settings. + +## ADDED Requirements + +### REQ-001: The system SHALL initialise default settings during a repair step + +`InitializeSettings::run(output)` MUST seed/initialise the app's default settings on install/upgrade; `getName()` MUST return the human-readable step name shown during the repair run. + +#### Scenario: REQ-001 case 1 +- WHEN the repair step executes +- THEN `run()` MUST initialise the default settings + +#### Scenario: REQ-001 case 2 +- WHEN the repair framework lists steps +- THEN `getName()` MUST return the step's display name diff --git a/openspec/changes/retrofit-2026-05-26-repair-init/tasks.md b/openspec/changes/retrofit-2026-05-26-repair-init/tasks.md new file mode 100644 index 00000000..77c23b09 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-repair-init/tasks.md @@ -0,0 +1,3 @@ +# Tasks + +- [x] task-1: repair-init#REQ-001 — The system SHALL initialise default settings during a repair step (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-sc-handlers/design.md b/openspec/changes/retrofit-2026-05-26-sc-handlers/design.md new file mode 100644 index 00000000..5e727faf --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-sc-handlers/design.md @@ -0,0 +1,3 @@ +# Design — sc-handlers + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 44 listed methods already exist and implement the 4 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-sc-handlers/proposal.md b/openspec/changes/retrofit-2026-05-26-sc-handlers/proposal.md new file mode 100644 index 00000000..eaf488ab --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-sc-handlers/proposal.md @@ -0,0 +1,57 @@ +# Retrofit — sc-handlers + +Describes observed behavior of 44 methods as 4 REQ(s) under the `sc-handlers` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::generateUsernameFromContactData +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::sanitizeEmailForUsername +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::validateEmailForUsername +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::createUserAccount +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::storeContactNameFields +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::setUserManager +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::getUserManager +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::setUserActive +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::setUserInactive +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::handleNewContact +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::handleContactUpdate +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::handleContactDeletion +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::handleContactpersoonUpdate +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::processContactpersoon +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::isFirstContactForOrganization +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::assignBeheerderRole +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::shouldAddContactpersoonToOrganization +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::addContactpersoonToOrganization +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::ensureContactpersoonInOrganization +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::addUserToOrganizationEntity +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::updateUserGroupsFromContactData +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php::updateUserGroupsFromRoles +- lib/Service/SoftwareCatalogue/GroupHandler.php::getGenericUserGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::setGenericUserGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::ensureGenericUserGroupsExist +- lib/Service/SoftwareCatalogue/GroupHandler.php::createGroupIfNotExists +- lib/Service/SoftwareCatalogue/GroupHandler.php::updateUserGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::updateRoleBasedGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::updateOrganizationGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::updateGemeenteGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::getAllGroups +- lib/Service/SoftwareCatalogue/GroupHandler.php::validateGroups +- lib/Service/SoftwareCatalogue/HierarchyHandler.php::ensureOrganizationBeheerder +- lib/Service/SoftwareCatalogue/HierarchyHandler.php::setupManagerRelationships +- lib/Service/SoftwareCatalogue/HierarchyHandler.php::getUserHierarchy +- lib/Service/SoftwareCatalogue/HierarchyHandler.php::getOrganizationStructure +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::processOrganization +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::ensureOrganizationGroup +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::createGroupIfNotExists +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::sanitizeGroupName +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::processContactpersonen +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::handleNewOrganization +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::getOrganizationBeheerders +- lib/Service/SoftwareCatalogue/OrganizationHandler.php::userBelongsToOrganization + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-sc-handlers/specs/sc-handlers/spec.md b/openspec/changes/retrofit-2026-05-26-sc-handlers/specs/sc-handlers/spec.md new file mode 100644 index 00000000..73178232 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-sc-handlers/specs/sc-handlers/spec.md @@ -0,0 +1,60 @@ +--- +status: draft +retrofit: true +--- + +# Sc Handlers Specification + +## Purpose + +Captures observed behavior of the focused SoftwareCatalogue sub-handlers — ContactPersonHandler (user-account provisioning), GroupHandler (group provisioning + role/org/gemeente group mapping), HierarchyHandler (manager relationships + org structure), and OrganizationHandler (organisation group + beheerder management). These decompose SoftwareCatalogueService into single-responsibility units. + +## ADDED Requirements + +### REQ-001: The system SHALL provision and maintain a contact person's Nextcloud user account + +ContactPersonHandler MUST derive a username (`generateUsernameFromContactData`, `sanitizeEmailForUsername`, `validateEmailForUsername`), create the account (`createUserAccount`), store profile fields (`storeContactNameFields`), set/get the manager (`setUserManager`/`getUserManager`), toggle active state (`setUserActive`/`setUserInactive`), and react to contact lifecycle events (`handleNewContact`, `handleContactUpdate`, `handleContactDeletion`, `handleContactpersoonUpdate`, `processContactpersoon`). `isFirstContactForOrganization`, `assignBeheerderRole`, `shouldAddContactpersoonToOrganization`, `addContactpersoonToOrganization`, `ensureContactpersoonInOrganization`, and `addUserToOrganizationEntity` MUST manage organisation linkage + beheerder assignment, and `updateUserGroupsFromContactData`/`updateUserGroupsFromRoles` MUST reconcile group membership. + +#### Scenario: REQ-001 case 1 +- WHEN `createUserAccount(obj, true)` is called for the first contact of an organisation +- THEN a Nextcloud user MUST be created and assigned the beheerder role + +#### Scenario: REQ-001 case 2 +- WHEN `generateUsernameFromContactData(data)` is called +- THEN it MUST return a sanitised, unique username + +### REQ-002: The system SHALL provision groups and map roles/organisation/gemeente to group membership + +GroupHandler MUST read/write the generic user groups (`getGenericUserGroups`/`setGenericUserGroups`/`ensureGenericUserGroupsExist`), create groups on demand (`createGroupIfNotExists`), list/validate groups (`getAllGroups`/`validateGroups`), and reconcile a user's group membership from their roles, organisation and gemeente (`updateUserGroups`, `updateRoleBasedGroups`, `updateOrganizationGroups`, `updateGemeenteGroups`). + +#### Scenario: REQ-002 case 1 +- WHEN `updateRoleBasedGroups(user, objectData)` is called +- THEN the user's role-derived group membership MUST be set + +#### Scenario: REQ-002 case 2 +- WHEN `createGroupIfNotExists('org-X')` is called for a missing group +- THEN the group MUST be created and returned + +### REQ-003: The system SHALL maintain user-manager hierarchy and report organisation structure + +HierarchyHandler MUST ensure an organisation has a beheerder (`ensureOrganizationBeheerder`), wire manager relationships (`setupManagerRelationships`), and report the manager chain for a user (`getUserHierarchy`) and the full structure of an organisation (`getOrganizationStructure`). + +#### Scenario: REQ-003 case 1 +- WHEN `setupManagerRelationships(...)` is called +- THEN each contact's Nextcloud manager MUST be set to its organisation beheerder + +#### Scenario: REQ-003 case 2 +- WHEN `getUserHierarchy(username)` is called +- THEN it MUST return the user's manager chain + +### REQ-004: The system SHALL provision an organisation's group and manage its beheerders and membership + +OrganizationHandler MUST process an organisation (`processOrganization`, `handleNewOrganization`), ensure/create its group with a sanitised name (`ensureOrganizationGroup`, `createGroupIfNotExists`, `sanitizeGroupName`), process its contact persons (`processContactpersonen`), list its beheerders (`getOrganizationBeheerders`), and test organisation membership (`userBelongsToOrganization`). + +#### Scenario: REQ-004 case 1 +- WHEN `processOrganization(obj)` is called for a new organisation +- THEN its group MUST be created with a sanitised name + +#### Scenario: REQ-004 case 2 +- WHEN `userBelongsToOrganization(user, orgUuid)` is called +- THEN it MUST return whether the user is a member of that organisation diff --git a/openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md b/openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md new file mode 100644 index 00000000..111fb8d5 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-sc-handlers/tasks.md @@ -0,0 +1,6 @@ +# Tasks + +- [x] task-1: sc-handlers#REQ-001 — The system SHALL provision and maintain a contact person's Nextcloud user account (retroactive annotation) +- [x] task-2: sc-handlers#REQ-002 — The system SHALL provision groups and map roles/organisation/gemeente to group membership (retroactive annotation) +- [x] task-3: sc-handlers#REQ-003 — The system SHALL maintain user-manager hierarchy and report organisation structure (retroactive annotation) +- [x] task-4: sc-handlers#REQ-004 — The system SHALL provision an organisation's group and manage its beheerders and membership (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-settings-admin-controller/design.md b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/design.md new file mode 100644 index 00000000..9ed2d6dc --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/design.md @@ -0,0 +1,3 @@ +# Design — settings-admin-controller + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 70 listed methods already exist and implement the 5 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-settings-admin-controller/proposal.md b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/proposal.md new file mode 100644 index 00000000..16c5387c --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/proposal.md @@ -0,0 +1,83 @@ +# Retrofit — settings-admin-controller + +Describes observed behavior of 70 methods as 5 REQ(s) under the `settings-admin-controller` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Controller/SettingsController.php::getObjectService +- lib/Controller/SettingsController.php::getConfigurationService +- lib/Controller/SettingsController.php::index +- lib/Controller/SettingsController.php::create +- lib/Controller/SettingsController.php::getGeneralConfig +- lib/Controller/SettingsController.php::updateGeneralConfig +- lib/Controller/SettingsController.php::getSyncConfig +- lib/Controller/SettingsController.php::updateSyncConfig +- lib/Controller/SettingsController.php::getVoorzieningenConfig +- lib/Controller/SettingsController.php::updateVoorzieningenConfig +- lib/Controller/SettingsController.php::load +- lib/Controller/SettingsController.php::status +- lib/Controller/SettingsController.php::initialize +- lib/Controller/SettingsController.php::autoConfigure +- lib/Controller/SettingsController.php::consolidatedAutoConfigure +- lib/Controller/SettingsController.php::manualImport +- lib/Controller/SettingsController.php::forceUpdate +- lib/Controller/SettingsController.php::resetAutoConfig +- lib/Controller/SettingsController.php::clearCache +- lib/Controller/SettingsController.php::getProgress +- lib/Controller/SettingsController.php::streamProgress +- lib/Controller/SettingsController.php::getVersionInfo +- lib/Controller/SettingsController.php::getSyncStatus +- lib/Controller/SettingsController.php::performSync +- lib/Controller/SettingsController.php::syncOrganisations +- lib/Controller/SettingsController.php::bulkSyncStandards +- lib/Controller/SettingsController.php::heartbeat +- lib/Controller/SettingsController.php::stats +- lib/Controller/SettingsController.php::debug +- lib/Controller/SettingsController.php::getObjectCounts +- lib/Controller/SettingsController.php::getObjectsCounts +- lib/Controller/SettingsController.php::getObjectsStatistics +- lib/Controller/SettingsController.php::getEmailSettings +- lib/Controller/SettingsController.php::updateEmailSettings +- lib/Controller/SettingsController.php::getEmailConfig +- lib/Controller/SettingsController.php::updateEmailConfig +- lib/Controller/SettingsController.php::getAmefConfig +- lib/Controller/SettingsController.php::updateAmefConfig +- lib/Controller/SettingsController.php::sendTestEmail +- lib/Controller/SettingsController.php::testEmailConnection +- lib/Controller/SettingsController.php::getEmailTemplates +- lib/Controller/SettingsController.php::getEmailTemplate +- lib/Controller/SettingsController.php::updateEmailTemplate +- lib/Controller/SettingsController.php::getEmailTemplateDefault +- lib/Controller/SettingsController.php::getEmailTemplateVariables +- lib/Controller/SettingsController.php::getGenericUserGroups +- lib/Controller/SettingsController.php::setGenericUserGroups +- lib/Controller/SettingsController.php::getOrganizationAdminGroups +- lib/Controller/SettingsController.php::setOrganizationAdminGroups +- lib/Controller/SettingsController.php::getSuperUserGroups +- lib/Controller/SettingsController.php::setSuperUserGroups +- lib/Controller/SettingsController.php::getAllGroups +- lib/Controller/SettingsController.php::getUserGroupsConfig +- lib/Controller/SettingsController.php::updateUserGroupsConfig +- lib/Controller/SettingsController.php::importArchiMate +- lib/Controller/SettingsController.php::exportArchiMate +- lib/Controller/SettingsController.php::exportOrgArchiMate +- lib/Controller/SettingsController.php::downloadArchiMate +- lib/Controller/SettingsController.php::getArchiMateSettings +- lib/Controller/SettingsController.php::getArchiMateConfig +- lib/Controller/SettingsController.php::updateArchiMateConfig +- lib/Controller/SettingsController.php::clearArchiMateImportStatus +- lib/Controller/SettingsController.php::killArchiMateImport +- lib/Controller/SettingsController.php::cancelArchiMateImport +- lib/Controller/SettingsController.php::clearArchiMateExportStatus +- lib/Controller/SettingsController.php::testArchiMateRoundTrip +- lib/Controller/SettingsController.php::getCronjobConfig +- lib/Controller/SettingsController.php::updateCronjobConfig +- lib/Controller/SettingsController.php::getCronjobUsers +- lib/Controller/SettingsController.php::getCronjobOrganisations + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-settings-admin-controller/specs/settings-admin-controller/spec.md b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/specs/settings-admin-controller/spec.md new file mode 100644 index 00000000..02216a61 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/specs/settings-admin-controller/spec.md @@ -0,0 +1,72 @@ +--- +status: draft +retrofit: true +--- + +# Settings Admin Controller Specification + +## Purpose + +Captures observed behavior of the SettingsController — the admin-facing HTTP surface for configuration, status, synchronisation, email, user-groups, ArchiMate, and statistics. Each endpoint validates admin context and delegates to SettingsService / domain services, returning JSONResponse (or a streaming/file Response for progress and exports). + +## ADDED Requirements + +### REQ-001: The system SHALL expose read/write endpoints for app configuration + +The controller exposes paired get/update endpoints for each configuration domain — general, sync, voorzieningen, AMEF, email, ArchiMate, user-groups, cronjob — plus the OpenRegister object-service/configuration-service accessors, `index`/`create`/`load`, and the catalog autoconfigure flows. Each delegates to SettingsService and returns the current or updated config as JSON. + +#### Scenario: REQ-001 case 1 +- WHEN `getGeneralConfig()` is called by an admin +- THEN it MUST return the current general configuration as JSON + +#### Scenario: REQ-001 case 2 +- WHEN `updateVoorzieningenConfig()` is called with a body +- THEN SettingsService MUST persist it and the endpoint MUST return the updated config + +### REQ-002: The system SHALL expose configuration status, auto-configuration and import endpoints + +`status`, `initialize`, `autoConfigure`, `consolidatedAutoConfigure`, `manualImport`, `forceUpdate`, `resetAutoConfig`, `clearCache`, `getProgress`, `streamProgress`, and `getVersionInfo` MUST run the corresponding SettingsService configuration/diagnostic operation and report the result (JSON, or an SSE stream for `streamProgress`). + +#### Scenario: REQ-002 case 1 +- WHEN `autoConfigure()` is called +- THEN the consolidated auto-configuration MUST run and its result MUST be returned + +#### Scenario: REQ-002 case 2 +- WHEN `streamProgress(operationId)` is called +- THEN it MUST stream progress events for the operation + +### REQ-003: The system SHALL expose synchronisation, heartbeat and statistics endpoints + +`getSyncStatus`, `getSyncConfig`/`updateSyncConfig`, `performSync`, `syncOrganisations`, `bulkSyncStandards`, `heartbeat`, `stats`, `debug`, `getObjectCounts`, `getObjectsCounts`, and `getObjectsStatistics` MUST run the matching sync/diagnostic/statistics routine and return the result as JSON. + +#### Scenario: REQ-003 case 1 +- WHEN `performSync(minutesBack)` is called +- THEN the synchronisation MUST run for that window and stats MUST be returned + +#### Scenario: REQ-003 case 2 +- WHEN `heartbeat()` is called +- THEN it MUST return the current liveness/heartbeat payload + +### REQ-004: The system SHALL expose email-configuration and template endpoints + +`getEmailSettings`/`updateEmailSettings`, `getEmailConfig`/`updateEmailConfig`, `getAmefConfig`/`updateAmefConfig`, `sendTestEmail`, `testEmailConnection`, `getEmailTemplates`, `getEmailTemplate`, `updateEmailTemplate`, `getEmailTemplateDefault`, and `getEmailTemplateVariables` MUST manage the email transport configuration and templates, returning the settings/template/test result as JSON. + +#### Scenario: REQ-004 case 1 +- WHEN `sendTestEmail()` is called with a recipient +- THEN a test email MUST be dispatched and the outcome returned + +#### Scenario: REQ-004 case 2 +- WHEN `updateEmailTemplate(name)` is called +- THEN the named template MUST be persisted + +### REQ-005: The system SHALL expose user-group and ArchiMate management endpoints + +The user-group endpoints (`getGenericUserGroups`/`setGenericUserGroups`, `getOrganizationAdminGroups`/`setOrganizationAdminGroups`, `getSuperUserGroups`/`setSuperUserGroups`, `getAllGroups`, `getUserGroupsConfig`/`updateUserGroupsConfig`) and the ArchiMate endpoints (`importArchiMate`, `exportArchiMate`, `exportOrgArchiMate`, `downloadArchiMate`, `getArchiMateSettings`, `getArchiMateConfig`/`updateArchiMateConfig`, `clearArchiMateImportStatus`, `killArchiMateImport`, `cancelArchiMateImport`, `clearArchiMateExportStatus`, `testArchiMateRoundTrip`) plus cronjob endpoints (`getCronjobConfig`/`updateCronjobConfig`, `getCronjobUsers`, `getCronjobOrganisations`) MUST apply the requested mutation or return the requested data — exports returning a downloadable Response. + +#### Scenario: REQ-005 case 1 +- WHEN `exportArchiMate()` is called +- THEN it MUST return the ArchiMate model as a downloadable Response + +#### Scenario: REQ-005 case 2 +- WHEN `setSuperUserGroups()` is called with a body +- THEN the super-user group list MUST be persisted diff --git a/openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md new file mode 100644 index 00000000..3f55ab17 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-admin-controller/tasks.md @@ -0,0 +1,7 @@ +# Tasks + +- [x] task-1: settings-admin-controller#REQ-001 — The system SHALL expose read/write endpoints for app configuration (retroactive annotation) +- [x] task-2: settings-admin-controller#REQ-002 — The system SHALL expose configuration status, auto-configuration and import endpoints (retroactive annotation) +- [x] task-3: settings-admin-controller#REQ-003 — The system SHALL expose synchronisation, heartbeat and statistics endpoints (retroactive annotation) +- [x] task-4: settings-admin-controller#REQ-004 — The system SHALL expose email-configuration and template endpoints (retroactive annotation) +- [x] task-5: settings-admin-controller#REQ-005 — The system SHALL expose user-group and ArchiMate management endpoints (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-settings-service/design.md b/openspec/changes/retrofit-2026-05-26-settings-service/design.md new file mode 100644 index 00000000..99d46df9 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-service/design.md @@ -0,0 +1,3 @@ +# Design — settings-service + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 83 listed methods already exist and implement the 5 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-settings-service/proposal.md b/openspec/changes/retrofit-2026-05-26-settings-service/proposal.md new file mode 100644 index 00000000..b23aeaec --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-service/proposal.md @@ -0,0 +1,96 @@ +# Retrofit — settings-service + +Describes observed behavior of 83 methods as 5 REQ(s) under the `settings-service` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/SettingsService.php::isOpenRegisterInstalled +- lib/Service/SettingsService.php::isOpenRegisterEnabled +- lib/Service/SettingsService.php::getObjectService +- lib/Service/SettingsService.php::getRegisterService +- lib/Service/SettingsService.php::getConfigurationService +- lib/Service/SettingsService.php::getSchemaIdForObjectType +- lib/Service/SettingsService.php::getRegisterIdForObjectType +- lib/Service/SettingsService.php::getVoorzieningenRegisterId +- lib/Service/SettingsService.php::getSettings +- lib/Service/SettingsService.php::updateSettings +- lib/Service/SettingsService.php::isFullyConfigured +- lib/Service/SettingsService.php::getConfigurationStatus +- lib/Service/SettingsService.php::getConsolidatedConfiguration +- lib/Service/SettingsService.php::getAllSettings +- lib/Service/SettingsService.php::getVoorzieningenConfig +- lib/Service/SettingsService.php::setVoorzieningenConfig +- lib/Service/SettingsService.php::getAmefConfig +- lib/Service/SettingsService.php::setAmefConfig +- lib/Service/SettingsService.php::getVoorzieningenConfigFocused +- lib/Service/SettingsService.php::updateVoorzieningenConfig +- lib/Service/SettingsService.php::getAmefConfigFocused +- lib/Service/SettingsService.php::updateAmefConfig +- lib/Service/SettingsService.php::getCatalogLocation +- lib/Service/SettingsService.php::setCatalogLocation +- lib/Service/SettingsService.php::getArchiMateConfig +- lib/Service/SettingsService.php::updateArchiMateConfig +- lib/Service/SettingsService.php::autoConfigure +- lib/Service/SettingsService.php::autoConfigureAfterImport +- lib/Service/SettingsService.php::configureOpenCatalogi +- lib/Service/SettingsService.php::initialize +- lib/Service/SettingsService.php::loadSettings +- lib/Service/SettingsService.php::performConsolidatedAutoConfiguration +- lib/Service/SettingsService.php::manualImport +- lib/Service/SettingsService.php::forceUpdate +- lib/Service/SettingsService.php::resetAutoConfiguration +- lib/Service/SettingsService.php::compactToJsonConfiguration +- lib/Service/SettingsService.php::cleanupOldConfiguration +- lib/Service/SettingsService.php::clearConfigurationCache +- lib/Service/SettingsService.php::getEmailSettings +- lib/Service/SettingsService.php::updateEmailSettings +- lib/Service/SettingsService.php::getEmailConfig +- lib/Service/SettingsService.php::setEmailConfig +- lib/Service/SettingsService.php::getEmailConfigFocused +- lib/Service/SettingsService.php::updateEmailConfig +- lib/Service/SettingsService.php::getEmailTemplate +- lib/Service/SettingsService.php::updateEmailTemplate +- lib/Service/SettingsService.php::getDefaultEmailTemplate +- lib/Service/SettingsService.php::getAllEmailTemplates +- lib/Service/SettingsService.php::getEmailTemplateVariables +- lib/Service/SettingsService.php::sendTestEmail +- lib/Service/SettingsService.php::testEmailConnection +- lib/Service/SettingsService.php::getGenericUserGroups +- lib/Service/SettingsService.php::setGenericUserGroups +- lib/Service/SettingsService.php::getOrganizationAdminGroups +- lib/Service/SettingsService.php::setOrganizationAdminGroups +- lib/Service/SettingsService.php::getSuperUserGroups +- lib/Service/SettingsService.php::setSuperUserGroups +- lib/Service/SettingsService.php::validateGroups +- lib/Service/SettingsService.php::createAndConfigureUserGroups +- lib/Service/SettingsService.php::getAllGroups +- lib/Service/SettingsService.php::updateGenericUserGroups +- lib/Service/SettingsService.php::updateOrganizationAdminGroups +- lib/Service/SettingsService.php::updateSuperUserGroups +- lib/Service/SettingsService.php::getUserGroupsConfig +- lib/Service/SettingsService.php::updateUserGroupsConfig +- lib/Service/SettingsService.php::getArchiMateStatus +- lib/Service/SettingsService.php::setArchiMateImportStatus +- lib/Service/SettingsService.php::setArchiMateExportStatus +- lib/Service/SettingsService.php::clearArchiMateImportStatus +- lib/Service/SettingsService.php::killArchiMateImport +- lib/Service/SettingsService.php::cancelArchiMateImport +- lib/Service/SettingsService.php::clearArchiMateExportStatus +- lib/Service/SettingsService.php::getObjectCountsStatistics +- lib/Service/SettingsService.php::getObjectsCounts +- lib/Service/SettingsService.php::getObjectsStatistics +- lib/Service/SettingsService.php::getDebugInfo +- lib/Service/SettingsService.php::getVersionInfo +- lib/Service/SettingsService.php::getCronjobConfig +- lib/Service/SettingsService.php::updateCronjobConfig +- lib/Service/SettingsService.php::getCronjobContext +- lib/Service/SettingsService.php::getAvailableUsersForCronjobs +- lib/Service/SettingsService.php::getAvailableOrganisationsForCronjobs +- lib/Service/SettingsService.php::syncOrganisationsToVoorzieningenOptimized + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-settings-service/specs/settings-service/spec.md b/openspec/changes/retrofit-2026-05-26-settings-service/specs/settings-service/spec.md new file mode 100644 index 00000000..7c10530c --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-service/specs/settings-service/spec.md @@ -0,0 +1,72 @@ +--- +status: draft +retrofit: true +--- + +# Settings Service Specification + +## Purpose + +Captures observed behavior of SettingsService — the central configuration service backing the admin settings API. It detects/uses OpenRegister, reads and persists every configuration domain (voorzieningen, AMEF, email, ArchiMate, user-groups, cronjob, catalog location), runs auto-configuration and import, computes statistics, and orchestrates organisation synchronisation. + +## ADDED Requirements + +### REQ-001: The system SHALL detect and resolve OpenRegister availability and services + +`isOpenRegisterInstalled(minVersion)`, `isOpenRegisterEnabled()`, `getObjectService()`, `getRegisterService()`, `getConfigurationService()`, `getSchemaIdForObjectType(type)`, `getRegisterIdForObjectType(type)`, and `getVoorzieningenRegisterId()` MUST resolve OpenRegister availability and its service handles / register-schema ids, returning null when OpenRegister is absent. + +#### Scenario: REQ-001 case 1 +- WHEN `isOpenRegisterEnabled()` is called with OpenRegister disabled +- THEN it MUST return false + +#### Scenario: REQ-001 case 2 +- WHEN `getObjectService()` is called with OpenRegister enabled +- THEN it MUST return the OpenRegister ObjectService + +### REQ-002: The system SHALL read and persist every configuration domain + +The service exposes get/set (and focused get/update) pairs for voorzieningen, AMEF, email, ArchiMate, user-groups (generic/org-admin/super), cronjob, and catalog location, plus aggregate readers (`getSettings`, `getAllSettings`, `getConsolidatedConfiguration`, `getConfigurationStatus`, `isFullyConfigured`) and writers (`updateSettings`). Each reads from / writes to app config and returns the current or updated values. + +#### Scenario: REQ-002 case 1 +- WHEN `setVoorzieningenConfig(config)` then `getVoorzieningenConfig()` is called +- THEN the persisted config MUST be returned + +#### Scenario: REQ-002 case 2 +- WHEN `isFullyConfigured()` is called with all required config present +- THEN it MUST return true + +### REQ-003: The system SHALL run auto-configuration, import and configuration maintenance + +`autoConfigure`, `autoConfigureAfterImport`, `configureOpenCatalogi`, `initialize`, `loadSettings`, `performConsolidatedAutoConfiguration`, `manualImport`, `forceUpdate`, `resetAutoConfiguration`, `compactToJsonConfiguration`, `cleanupOldConfiguration`, and `clearConfigurationCache` MUST create/repair the register-schema configuration in OpenRegister, import seed data, and maintain the cached configuration, returning a result summary. + +#### Scenario: REQ-003 case 1 +- WHEN `autoConfigure(force=true)` is called +- THEN the registers/schemas MUST be (re)configured and a result summary returned + +#### Scenario: REQ-003 case 2 +- WHEN `clearConfigurationCache()` is called +- THEN the cached configuration MUST be invalidated + +### REQ-004: The system SHALL manage email settings, templates and connectivity tests + +`getEmailSettings`/`updateEmailSettings`, `getEmailConfig`/`setEmailConfig`/`getEmailConfigFocused`/`updateEmailConfig`, `getEmailTemplate`/`updateEmailTemplate`/`getDefaultEmailTemplate`/`getAllEmailTemplates`/`getEmailTemplateVariables`, `sendTestEmail`, and `testEmailConnection` MUST manage the email transport configuration + templates and run connectivity/test-send diagnostics. + +#### Scenario: REQ-004 case 1 +- WHEN `sendTestEmail(email)` is called +- THEN a test message MUST be dispatched and the result returned + +#### Scenario: REQ-004 case 2 +- WHEN `getDefaultEmailTemplate(name)` is called +- THEN the built-in default template body MUST be returned + +### REQ-005: The system SHALL manage user groups, ArchiMate operation status, statistics and organisation sync + +User-group helpers (`getGenericUserGroups`/`set...`/`update...`, org-admin, super-user, `getAllGroups`, `validateGroups`, `createAndConfigureUserGroups`, `getUserGroupsConfig`/`updateUserGroupsConfig`), ArchiMate-status helpers (`getArchiMateStatus`, `set/clear ArchiMateImportStatus`, `set/clear ArchiMateExportStatus`, `killArchiMateImport`, `cancelArchiMateImport`, `getArchiMateConfig`/`updateArchiMateConfig`), statistics (`getObjectCountsStatistics`, `getObjectsCounts`, `getObjectsStatistics`, `getDebugInfo`, `getVersionInfo`), cronjob config + context (`getCronjobConfig`/`updateCronjobConfig`, `getCronjobContext`, `getAvailableUsersForCronjobs`, `getAvailableOrganisationsForCronjobs`), and `syncOrganisationsToVoorzieningenOptimized` MUST each apply or report the requested state. + +#### Scenario: REQ-005 case 1 +- WHEN `validateGroups(groups)` is called +- THEN it MUST return which groups exist/are valid + +#### Scenario: REQ-005 case 2 +- WHEN `getArchiMateStatus()` is called during an import +- THEN it MUST report the in-progress import status diff --git a/openspec/changes/retrofit-2026-05-26-settings-service/tasks.md b/openspec/changes/retrofit-2026-05-26-settings-service/tasks.md new file mode 100644 index 00000000..12cd1941 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-settings-service/tasks.md @@ -0,0 +1,7 @@ +# Tasks + +- [x] task-1: settings-service#REQ-001 — The system SHALL detect and resolve OpenRegister availability and services (retroactive annotation) +- [x] task-2: settings-service#REQ-002 — The system SHALL read and persist every configuration domain (retroactive annotation) +- [x] task-3: settings-service#REQ-003 — The system SHALL run auto-configuration, import and configuration maintenance (retroactive annotation) +- [x] task-4: settings-service#REQ-004 — The system SHALL manage email settings, templates and connectivity tests (retroactive annotation) +- [x] task-5: settings-service#REQ-005 — The system SHALL manage user groups, ArchiMate operation status, statistics and organisation sync (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/design.md b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/design.md new file mode 100644 index 00000000..734a828e --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/design.md @@ -0,0 +1,3 @@ +# Design — softwarecatalogue-orchestration + +Retrofit change. Tasks describe retroactive annotation, not new implementation work. The 33 listed methods already exist and implement the 4 REQ(s) below; this change records their observed behavior as a spec and annotates each method with an `@spec` tag pointing at its task. diff --git a/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/proposal.md b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/proposal.md new file mode 100644 index 00000000..33069b79 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/proposal.md @@ -0,0 +1,46 @@ +# Retrofit — softwarecatalogue-orchestration + +Describes observed behavior of 33 methods as 4 REQ(s) under the `softwarecatalogue-orchestration` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every method gets a spec). + +## Affected code units + +- lib/Service/SoftwareCatalogueService.php::processContactpersoon +- lib/Service/SoftwareCatalogueService.php::handleNewContact +- lib/Service/SoftwareCatalogueService.php::handleContactUpdate +- lib/Service/SoftwareCatalogueService.php::handleContactpersoonUpdate +- lib/Service/SoftwareCatalogueService.php::handleContactDeletion +- lib/Service/SoftwareCatalogueService.php::createUserForContactIfNotExists +- lib/Service/SoftwareCatalogueService.php::updateUserGroups +- lib/Service/SoftwareCatalogueService.php::ensureOrganizationBeheerder +- lib/Service/SoftwareCatalogueService.php::getUserManager +- lib/Service/SoftwareCatalogueService.php::syncContactPersonUsernamesWithOrganization +- lib/Service/SoftwareCatalogueService.php::ensureContactPersonInOrganization +- lib/Service/SoftwareCatalogueService.php::shouldAddContactpersoonToOrganization +- lib/Service/SoftwareCatalogueService.php::addContactpersoonToOrganization +- lib/Service/SoftwareCatalogueService.php::processOrganization +- lib/Service/SoftwareCatalogueService.php::handleNewOrganization +- lib/Service/SoftwareCatalogueService.php::handleOrganizationUpdate +- lib/Service/SoftwareCatalogueService.php::handleOrganizationDeletion +- lib/Service/SoftwareCatalogueService.php::syncOrganizationWithOpenRegister +- lib/Service/SoftwareCatalogueService.php::createOrganisationInOpenRegister +- lib/Service/SoftwareCatalogueService.php::sendOrganizationWelcomeEmail +- lib/Service/SoftwareCatalogueService.php::handleNewGebruiker +- lib/Service/SoftwareCatalogueService.php::handleGebruikerUpdate +- lib/Service/SoftwareCatalogueService.php::sendGebruikerWelcomeEmail +- lib/Service/SoftwareCatalogueService.php::blockUserForGebruiker +- lib/Service/SoftwareCatalogueService.php::temporarilyBlockUserForGebruiker +- lib/Service/SoftwareCatalogueService.php::restoreUserAccessForGebruiker +- lib/Service/SoftwareCatalogueService.php::syncUserWithRevertedContact +- lib/Service/SoftwareCatalogueService.php::updateUserFromRevertedGebruiker +- lib/Service/SoftwareCatalogueService.php::getGenericUserGroups +- lib/Service/SoftwareCatalogueService.php::setGenericUserGroups +- lib/Service/SoftwareCatalogueService.php::ensureGenericUserGroupsExist +- lib/Service/SoftwareCatalogueService.php::getUserHierarchy +- lib/Service/SoftwareCatalogueService.php::getOrganizationStructure + +## Approach + +- Describe observed inputs/outputs/side-effects of each method. +- Group methods implementing the same observable behavior under one REQ. + +Source: openspec/coverage-report (csc.py --mode report) generated 2026-05-26. Umbrella: ConductionNL/softwarecatalog#294. diff --git a/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/specs/softwarecatalogue-orchestration/spec.md b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/specs/softwarecatalogue-orchestration/spec.md new file mode 100644 index 00000000..4b05bbe1 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/specs/softwarecatalogue-orchestration/spec.md @@ -0,0 +1,60 @@ +--- +status: draft +retrofit: true +--- + +# Softwarecatalogue Orchestration Specification + +## Purpose + +Captures observed behavior of SoftwareCatalogueService — the high-level orchestrator that reacts to SoftwareCatalog object create/update/delete events for contacts, organisations and gebruikers, delegating to the focused handlers while managing user accounts, groups, hierarchy, welcome emails, and OpenRegister organisation sync. + +## ADDED Requirements + +### REQ-001: The system SHALL react to contact-person create/update/delete events + +`processContactpersoon`, `handleNewContact`, `handleContactUpdate`, `handleContactpersoonUpdate`, `handleContactDeletion`, `createUserForContactIfNotExists`, `updateUserGroups`, `ensureOrganizationBeheerder`, `getUserManager`, `syncContactPersonUsernamesWithOrganization`, `ensureContactPersonInOrganization`, `shouldAddContactpersoonToOrganization`, and `addContactpersoonToOrganization` MUST create/maintain the contact's Nextcloud user, group membership, beheerder role, and organisation linkage in response to contact lifecycle events. + +#### Scenario: REQ-001 case 1 +- WHEN `handleNewContact(obj)` fires +- THEN the contact's user account MUST be created if absent and linked to its organisation + +#### Scenario: REQ-001 case 2 +- WHEN `shouldAddContactpersoonToOrganization(obj)` returns true and `addContactpersoonToOrganization(obj)` is called +- THEN the contact MUST be added to the organisation entity + +### REQ-002: The system SHALL react to organisation create/update/delete events and sync to OpenRegister + +`processOrganization`, `handleNewOrganization`, `handleOrganizationUpdate`, `handleOrganizationDeletion`, `syncOrganizationWithOpenRegister`, `createOrganisationInOpenRegister`, and `sendOrganizationWelcomeEmail` MUST provision the organisation's group, mirror the organisation into OpenRegister, and send the welcome email on creation. + +#### Scenario: REQ-002 case 1 +- WHEN `handleNewOrganization(obj)` fires +- THEN the organisation MUST be created in OpenRegister and a welcome email sent + +#### Scenario: REQ-002 case 2 +- WHEN `handleOrganizationDeletion(obj)` fires +- THEN the organisation's downstream artefacts MUST be cleaned up + +### REQ-003: The system SHALL react to gebruiker lifecycle events and manage account access + +`handleNewGebruiker`, `handleGebruikerUpdate`, `sendGebruikerWelcomeEmail`, `blockUserForGebruiker`, `temporarilyBlockUserForGebruiker`, `restoreUserAccessForGebruiker`, `syncUserWithRevertedContact`, and `updateUserFromRevertedGebruiker` MUST manage the gebruiker's account access (block/restore/revert) and welcome notification in response to gebruiker events. + +#### Scenario: REQ-003 case 1 +- WHEN `handleNewGebruiker(obj)` fires +- THEN the gebruiker's account access MUST be provisioned and a welcome email sent + +#### Scenario: REQ-003 case 2 +- WHEN `blockUserForGebruiker(obj)` is called +- THEN the linked account MUST be blocked + +### REQ-004: The system SHALL expose user-group, hierarchy and structure helpers + +`getGenericUserGroups`/`setGenericUserGroups`/`ensureGenericUserGroupsExist`, `getUserHierarchy(username)`, and `getOrganizationStructure(orgUuid)` MUST read/maintain the generic user groups and report the user-manager hierarchy and organisation structure. + +#### Scenario: REQ-004 case 1 +- WHEN `ensureGenericUserGroupsExist()` is called +- THEN the generic user groups MUST be created if missing + +#### Scenario: REQ-004 case 2 +- WHEN `getOrganizationStructure(orgUuid)` is called +- THEN the organisation's structure MUST be returned diff --git a/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md new file mode 100644 index 00000000..d066932c --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-softwarecatalogue-orchestration/tasks.md @@ -0,0 +1,6 @@ +# Tasks + +- [x] task-1: softwarecatalogue-orchestration#REQ-001 — The system SHALL react to contact-person create/update/delete events (retroactive annotation) +- [x] task-2: softwarecatalogue-orchestration#REQ-002 — The system SHALL react to organisation create/update/delete events and sync to OpenRegister (retroactive annotation) +- [x] task-3: softwarecatalogue-orchestration#REQ-003 — The system SHALL react to gebruiker lifecycle events and manage account access (retroactive annotation) +- [x] task-4: softwarecatalogue-orchestration#REQ-004 — The system SHALL expose user-group, hierarchy and structure helpers (retroactive annotation) From c8e1d5115a7673e364bfd4bff125bece553a835c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 26 May 2026 00:54:56 +0200 Subject: [PATCH 2/2] retrofit(coverage): reverse-spec + annotate frontend to 0 uncovered Closes spec coverage to zero uncovered methods for the frontend layer. Annotates 575 Vue/JS methods with @spec references and adds 5 reverse-spec openspec changes covering the frontend capabilities: - fe-object-modals: object lifecycle + bulk-operation modals (8 REQ) - fe-organizations: org & contact-person UI (6 REQ) - fe-settings-ui: admin settings sections (10 REQ) - fe-shell-navigation: dashboard, sidebars, reusable components (4 REQ) - fe-stores: Pinia stores, object-ops plugin, theme, heartbeat (7 REQ) Genuine framework plumbing (SPA shell hooks, Pinia plugin installer/$patch passthrough, DI getters, i18n wrapper, class constructor) marked @spec exclude with reasons. Comment-only additive changes; all JS/Vue script blocks parse. --- .../proposal.md | 31 ++ .../specs/fe-object-modals/spec.md | 100 ++++++ .../tasks.md | 10 + .../proposal.md | 22 ++ .../specs/fe-organizations/spec.md | 76 +++++ .../tasks.md | 8 + .../proposal.md | 27 ++ .../specs/fe-settings-ui/spec.md | 116 +++++++ .../tasks.md | 12 + .../proposal.md | 23 ++ .../specs/fe-shell-navigation/spec.md | 56 ++++ .../tasks.md | 6 + .../retrofit-2026-05-26-fe-stores/proposal.md | 23 ++ .../specs/fe-stores/spec.md | 94 ++++++ .../retrofit-2026-05-26-fe-stores/tasks.md | 9 + src/App.vue | 10 + src/components/AddContactpersoonModal.vue | 18 ++ src/components/AlwaysVisibleSection.vue | 2 + src/components/CollapsibleSection.vue | 3 + src/components/ContactpersonenList.vue | 68 ++++ src/components/PaginationComponent.vue | 4 + src/components/PublishedIcon.vue | 3 + src/components/SelectedObjectsList.vue | 5 + src/components/cards/OrganisatieCard.vue | 9 + src/dialogs/Dialogs.vue | 3 + src/modals/Modals.vue | 4 + src/modals/OrganisationModal.vue | 28 ++ .../object/ChangeOrganisatieStatusDialog.vue | 3 + src/modals/object/DeleteObject.vue | 6 + src/modals/object/DownloadObject.vue | 9 + src/modals/object/LockObject.vue | 6 + src/modals/object/MassDeleteObject.vue | 17 + src/modals/object/MassDepublishObjects.vue | 14 + src/modals/object/MassLockObjects.vue | 14 + src/modals/object/MassPublishObjects.vue | 14 + src/modals/object/MassUnlockObjects.vue | 14 + src/modals/object/MassValidateObjects.vue | 14 + src/modals/object/MergeObject.vue | 63 ++++ src/modals/object/MigrationObject.vue | 57 ++++ src/modals/object/ObjectModal.vue | 57 ++++ src/modals/object/UploadObject.vue | 24 ++ src/modals/object/ViewObject.vue | 298 ++++++++++++++++++ src/navigation/Configuration.vue | 6 + src/services/getTheme.js | 3 + src/sidebars/directory/DirectorySideBar.vue | 33 ++ src/sidebars/search/SearchSideBar.vue | 9 + src/store/modules/catalog.js | 2 + src/store/modules/navigation.js | 15 + src/store/modules/organisatie.js | 12 + src/store/modules/settings.js | 41 +++ src/store/plugins/softwarecatalogPlugin.js | 61 ++++ src/utils/heartbeat.js | 8 + src/views/Dashboard.vue | 13 + src/views/settings/Settings.vue | 15 + .../settings/SoftwareCatalogSettings.vue | 14 + .../sections/ArchiMateImportExport.vue | 19 ++ .../sections/CronjobConfiguration.vue | 14 + .../settings/sections/EmailConfiguration.vue | 27 ++ .../sections/OpenRegisterIntegration.vue | 61 ++++ .../sections/OrganizationSynchronization.vue | 26 ++ .../settings/sections/StatisticsOverview.vue | 29 ++ .../sections/UserGroupsConfiguration.vue | 38 +++ .../settings/sections/VersionInformation.vue | 14 + .../widgets/ConceptOrganisatiesWidget.vue | 5 + 64 files changed, 1845 insertions(+) create mode 100644 openspec/changes/retrofit-2026-05-26-fe-object-modals/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-object-modals/specs/fe-object-modals/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-organizations/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-organizations/specs/fe-organizations/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-settings-ui/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-settings-ui/specs/fe-settings-ui/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-shell-navigation/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-shell-navigation/specs/fe-shell-navigation/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-stores/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-stores/specs/fe-stores/spec.md create mode 100644 openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md diff --git a/openspec/changes/retrofit-2026-05-26-fe-object-modals/proposal.md b/openspec/changes/retrofit-2026-05-26-fe-object-modals/proposal.md new file mode 100644 index 00000000..471af89d --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-object-modals/proposal.md @@ -0,0 +1,31 @@ +# Retrofit — fe-object-modals + +Describes observed behavior of the frontend object-lifecycle modals and dialogs as REQ(s) under the `fe-object-modals` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every non-trivial frontend method gets a spec). + +## Affected code units + +- src/modals/object/ViewObject.vue +- src/modals/object/ObjectModal.vue +- src/modals/object/MergeObject.vue +- src/modals/object/MigrationObject.vue +- src/modals/object/UploadObject.vue +- src/modals/object/DownloadObject.vue +- src/modals/object/DeleteObject.vue +- src/modals/object/LockObject.vue +- src/modals/object/MassDeleteObject.vue +- src/modals/object/MassDepublishObjects.vue +- src/modals/object/MassLockObjects.vue +- src/modals/object/MassPublishObjects.vue +- src/modals/object/MassUnlockObjects.vue +- src/modals/object/MassValidateObjects.vue +- src/components/SelectedObjectsList.vue + +## Approach + +- Describe the observed user-facing behavior of each modal/dialog (open/close, validation, store dispatch, result feedback). +- Group methods implementing the same observable behavior under one REQ. +- Annotate each method with `@spec` pointing at the matching task. + +## Impact + +- Documentation only. No runtime behavior change. diff --git a/openspec/changes/retrofit-2026-05-26-fe-object-modals/specs/fe-object-modals/spec.md b/openspec/changes/retrofit-2026-05-26-fe-object-modals/specs/fe-object-modals/spec.md new file mode 100644 index 00000000..6ea0b64f --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-object-modals/specs/fe-object-modals/spec.md @@ -0,0 +1,100 @@ +--- +status: draft +retrofit: true +--- + +# Frontend Object Modals Specification + +## Purpose + +Captures the observed behavior of the object-lifecycle modals and dialogs that operate on register objects (view, create/edit, merge, migrate, file transfer, delete/lock, bulk operations) and the selected-objects list. + +## ADDED Requirements + +### Requirement: View object detail modal (REQ-FE-101) + +The object detail modal SHALL render a read-only view of the active object, exposing its properties, related data, files, audit trail and available actions, and MUST allow the user to close the modal and trigger object actions. + +`ViewObject.vue` loads the active object from the store, formats its properties for display, lazily fetches related/linked data and files, and dispatches lifecycle actions (publish, lock, delete, edit) back to the store. + +#### Scenario: View an object +- WHEN the detail modal opens for an active object +- THEN it MUST display the object's properties and available actions + +#### Scenario: Close the modal +- WHEN the user closes the modal +- THEN the active object MUST be cleared from navigation state + +### Requirement: Create/edit object modal (REQ-FE-102) + +The object modal SHALL allow creating a new object or editing an existing one, validating the form and dispatching a save to the store. + +`ObjectModal.vue` builds the form from the schema, validates input, and on submit dispatches a create or update action and surfaces success/error feedback. + +#### Scenario: Save a valid object +- WHEN the user submits a valid form +- THEN the modal MUST dispatch a save action and report the result + +### Requirement: Merge objects (REQ-FE-103) + +The merge modal SHALL let the user pick a target object and merge the active object into it, previewing the field-level resolution before dispatching the merge. + +`MergeObject.vue` lists merge candidates, lets the user resolve conflicting fields, and dispatches the merge action. + +#### Scenario: Merge two objects +- WHEN the user confirms a merge with a chosen target and field resolution +- THEN the modal MUST dispatch the merge and report the result + +### Requirement: Migrate object between register/schema (REQ-FE-104) + +The migration modal SHALL let the user move an object to a different register/schema, mapping source properties to target properties before dispatching the migration. + +`MigrationObject.vue` loads target register/schema options, builds a property mapping, validates it, and dispatches the migration. + +#### Scenario: Migrate an object +- WHEN the user confirms a property mapping to a target schema +- THEN the modal MUST dispatch the migration and report the result + +### Requirement: Upload/download object files (REQ-FE-105) + +The file modals SHALL let the user upload files to an object and download object files/exports. + +`UploadObject.vue` validates and uploads selected files to the active object; `DownloadObject.vue` triggers a download/export of the object in the chosen format. + +#### Scenario: Upload a file +- WHEN the user selects and confirms files to upload +- THEN the modal MUST dispatch the upload and report the result + +#### Scenario: Download an object +- WHEN the user confirms a download/export format +- THEN the modal MUST trigger the corresponding download + +### Requirement: Delete/lock single object (REQ-FE-106) + +The delete and lock dialogs SHALL confirm the action with the user and dispatch the delete or lock/unlock action for the active object. + +`DeleteObject.vue` confirms and dispatches deletion; `LockObject.vue` confirms and dispatches lock/unlock. + +#### Scenario: Confirm a delete +- WHEN the user confirms deletion +- THEN the dialog MUST dispatch the delete action and report the result + +### Requirement: Bulk object operations (REQ-FE-107) + +The bulk-operation modals SHALL apply an operation (delete, depublish, publish, lock, unlock, validate) to all currently selected objects, reporting per-object progress and the aggregate result. + +`MassDeleteObject.vue`, `MassDepublishObjects.vue`, `MassPublishObjects.vue`, `MassLockObjects.vue`, `MassUnlockObjects.vue` and `MassValidateObjects.vue` each confirm the operation, iterate the selection through the store's mass-operation action, and surface progress and outcome. + +#### Scenario: Bulk publish selected objects +- WHEN the user confirms a bulk publish over the current selection +- THEN the modal MUST dispatch the operation for each selected object and report the aggregate result + +### Requirement: Selected-objects list management (REQ-FE-108) + +The selected-objects list SHALL display the current selection and allow removing individual entries or clearing the whole selection. + +`SelectedObjectsList.vue` renders the selection from store state and dispatches selection mutations. + +#### Scenario: Remove an object from the selection +- WHEN the user removes an item from the selected list +- THEN it MUST be removed from the selection state diff --git a/openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md b/openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md new file mode 100644 index 00000000..7e58867a --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md @@ -0,0 +1,10 @@ +# Tasks + +- [x] task-1: fe-object-modals#REQ-FE-101 — View object detail modal (retroactive annotation) +- [x] task-2: fe-object-modals#REQ-FE-102 — Create/edit object modal (retroactive annotation) +- [x] task-3: fe-object-modals#REQ-FE-103 — Merge objects (retroactive annotation) +- [x] task-4: fe-object-modals#REQ-FE-104 — Migrate object between register/schema (retroactive annotation) +- [x] task-5: fe-object-modals#REQ-FE-105 — Upload/download object files (retroactive annotation) +- [x] task-6: fe-object-modals#REQ-FE-106 — Delete/lock single object (retroactive annotation) +- [x] task-7: fe-object-modals#REQ-FE-107 — Bulk object operations (retroactive annotation) +- [x] task-8: fe-object-modals#REQ-FE-108 — Selected-objects list management (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-fe-organizations/proposal.md b/openspec/changes/retrofit-2026-05-26-fe-organizations/proposal.md new file mode 100644 index 00000000..01cd5bcb --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-organizations/proposal.md @@ -0,0 +1,22 @@ +# Retrofit — fe-organizations + +Describes observed behavior of the frontend organization & contact-person UI as REQ(s) under the `fe-organizations` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every non-trivial frontend method gets a spec). + +## Affected code units + +- src/components/AddContactpersoonModal.vue +- src/components/ContactpersonenList.vue +- src/modals/OrganisationModal.vue +- src/components/cards/OrganisatieCard.vue +- src/modals/object/ChangeOrganisatieStatusDialog.vue +- src/views/widgets/ConceptOrganisatiesWidget.vue + +## Approach + +- Describe the observed UI behavior for adding/listing contact persons, editing organisations, and changing organisation status. +- Group methods implementing the same observable behavior under one REQ. +- Annotate each method with `@spec` pointing at the matching task. + +## Impact + +- Documentation only. No runtime behavior change. diff --git a/openspec/changes/retrofit-2026-05-26-fe-organizations/specs/fe-organizations/spec.md b/openspec/changes/retrofit-2026-05-26-fe-organizations/specs/fe-organizations/spec.md new file mode 100644 index 00000000..eb927292 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-organizations/specs/fe-organizations/spec.md @@ -0,0 +1,76 @@ +--- +status: draft +retrofit: true +--- + +# Frontend Organizations Specification + +## Purpose + +Captures the observed behavior of the organisation and contact-person UI: adding contact persons, listing them with linked-user management, creating/editing organisations, the organisation card, organisation status changes, and the concept-organisations dashboard widget. + +## ADDED Requirements + +### Requirement: Add contact person (REQ-FE-201) + +The add-contact-person modal SHALL let the user enter a new contact person for an organisation, validate the form, generate an identifier where needed, and dispatch the create action. + +`AddContactpersoonModal.vue` validates the form, generates a UUID for new entries, dispatches the save and closes on success. + +#### Scenario: Add a valid contact person +- WHEN the user submits a valid contact-person form +- THEN the modal MUST dispatch the create action and close + +### Requirement: Contact persons list & user management (REQ-FE-202) + +The contact-persons list SHALL display an organisation's contact persons with their linked Nextcloud-user details and SHALL allow per-person user-management actions (convert to user, change password, enable/disable, update groups). + +`ContactpersonenList.vue` fetches contact persons and bulk user info from the store, renders status per person, and dispatches user-management actions with confirmation and result feedback. + +#### Scenario: List contact persons with user details +- WHEN the list is opened for an organisation +- THEN it MUST display each contact person with their linked-user status + +#### Scenario: Run a user-management action +- WHEN the user triggers a per-person action (e.g. enable/disable, change password) +- THEN the list MUST dispatch the action and reflect the updated state + +### Requirement: Create/edit organisation (REQ-FE-203) + +The organisation modal SHALL let the user create or edit an organisation, validate the form and dispatch a save to the store. + +`OrganisationModal.vue` builds the organisation form, validates it, and dispatches a create/update with success/error feedback. + +#### Scenario: Save an organisation +- WHEN the user submits a valid organisation form +- THEN the modal MUST dispatch the save and report the result + +### Requirement: Organisation card display & actions (REQ-FE-204) + +The organisation card SHALL display an organisation summary and SHALL expose contextual actions and, when its current view is contact persons, refresh the linked user data on view switch. + +`OrganisatieCard.vue` formats the organisation summary and website URL, executes object actions, and reloads user data when switching to the contact-persons view. + +#### Scenario: Display an organisation card +- WHEN a card renders for an organisation +- THEN it MUST show the formatted summary and available actions + +### Requirement: Change organisation status (REQ-FE-205) + +The change-status dialog SHALL let the user move an organisation between lifecycle statuses (e.g. concept → published), confirm the change and dispatch it. + +`ChangeOrganisatieStatusDialog.vue` confirms the target status and dispatches the status change. + +#### Scenario: Change organisation status +- WHEN the user confirms a new status +- THEN the dialog MUST dispatch the status change and report the result + +### Requirement: Concept organisations widget (REQ-FE-206) + +The concept-organisations dashboard widget SHALL list organisations still in concept status and link to them. + +`ConceptOrganisatiesWidget.vue` fetches concept organisations and renders them with navigation links. + +#### Scenario: Show concept organisations +- WHEN the dashboard widget loads +- THEN it MUST display the organisations currently in concept status diff --git a/openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md b/openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md new file mode 100644 index 00000000..c275c921 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md @@ -0,0 +1,8 @@ +# Tasks + +- [x] task-1: fe-organizations#REQ-FE-201 — Add contact person (retroactive annotation) +- [x] task-2: fe-organizations#REQ-FE-202 — Contact persons list & user management (retroactive annotation) +- [x] task-3: fe-organizations#REQ-FE-203 — Create/edit organisation (retroactive annotation) +- [x] task-4: fe-organizations#REQ-FE-204 — Organisation card display & actions (retroactive annotation) +- [x] task-5: fe-organizations#REQ-FE-205 — Change organisation status (retroactive annotation) +- [x] task-6: fe-organizations#REQ-FE-206 — Concept organisations widget (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-fe-settings-ui/proposal.md b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/proposal.md new file mode 100644 index 00000000..7cf31936 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/proposal.md @@ -0,0 +1,27 @@ +# Retrofit — fe-settings-ui + +Describes observed behavior of the frontend settings UI as REQ(s) under the `fe-settings-ui` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every non-trivial frontend method gets a spec). + +## Affected code units + +- src/views/settings/Settings.vue +- src/views/settings/SoftwareCatalogSettings.vue +- src/views/settings/sections/OpenRegisterIntegration.vue +- src/views/settings/sections/UserGroupsConfiguration.vue +- src/views/settings/sections/EmailConfiguration.vue +- src/views/settings/sections/CronjobConfiguration.vue +- src/views/settings/sections/OrganizationSynchronization.vue +- src/views/settings/sections/ArchiMateImportExport.vue +- src/views/settings/sections/StatisticsOverview.vue +- src/views/settings/sections/VersionInformation.vue +- src/navigation/Configuration.vue + +## Approach + +- Describe the observed behavior of each settings section (load config, edit, save, test, status feedback). +- Group methods implementing the same observable behavior under one REQ per section. +- Annotate each method with `@spec` pointing at the matching task. + +## Impact + +- Documentation only. No runtime behavior change. diff --git a/openspec/changes/retrofit-2026-05-26-fe-settings-ui/specs/fe-settings-ui/spec.md b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/specs/fe-settings-ui/spec.md new file mode 100644 index 00000000..0e21e079 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/specs/fe-settings-ui/spec.md @@ -0,0 +1,116 @@ +--- +status: draft +retrofit: true +--- + +# Frontend Settings UI Specification + +## Purpose + +Captures the observed behavior of the admin/settings UI: the settings shell and each configuration section (OpenRegister integration, user groups, email, cronjob, organization sync, ArchiMate import/export, statistics, version) plus the navigation configuration panel. + +## ADDED Requirements + +### Requirement: Settings shell & navigation (REQ-FE-301) + +The settings shell SHALL present the available configuration sections, switch between them, and surface the overall configuration status. + +`Settings.vue` and `SoftwareCatalogSettings.vue` load the consolidated configuration, render section navigation, and report whether the app is fully configured. + +#### Scenario: Open settings +- WHEN the settings view loads +- THEN it MUST display the configuration sections and the current configuration status + +### Requirement: OpenRegister integration configuration (REQ-FE-302) + +The OpenRegister integration section SHALL let the admin select registers/schemas for the app's object types, validate the selection, run auto-configuration, and persist it. + +`OpenRegisterIntegration.vue` loads OpenRegister essentials, binds register/schema selections per object type, validates and saves the configuration, and supports auto-configure. + +#### Scenario: Save OpenRegister mapping +- WHEN the admin selects registers/schemas and saves +- THEN the section MUST persist the configuration and report the result + +### Requirement: User groups configuration (REQ-FE-303) + +The user-groups section SHALL let the admin configure which Nextcloud groups map to the app's roles and persist that mapping. + +`UserGroupsConfiguration.vue` loads available groups and the current mapping, edits it, and saves. + +#### Scenario: Save group mapping +- WHEN the admin changes the group mapping and saves +- THEN the section MUST persist the mapping and report the result + +### Requirement: Email configuration (REQ-FE-304) + +The email section SHALL let the admin configure email delivery settings, test the connection, send a test email, and persist the settings. + +`EmailConfiguration.vue` loads email config, validates input, tests the connection / sends a test message, and saves. + +#### Scenario: Test and save email settings +- WHEN the admin tests the email connection and saves +- THEN the section MUST report the test result and persist the settings + +### Requirement: Cronjob configuration (REQ-FE-305) + +The cronjob section SHALL let the admin view and configure the app's scheduled jobs and persist the configuration. + +`CronjobConfiguration.vue` loads cronjob config/status, edits schedule settings, and saves. + +#### Scenario: Save cronjob configuration +- WHEN the admin changes the cronjob settings and saves +- THEN the section MUST persist the configuration and report the result + +### Requirement: Organization synchronization configuration (REQ-FE-306) + +The organization-sync section SHALL let the admin configure the external organisation source, the sync time window, run/trigger a sync, and view sync status. + +`OrganizationSynchronization.vue` loads sync config and status, updates the time window, triggers synchronisation, and reports status. + +#### Scenario: Configure and run organisation sync +- WHEN the admin sets the sync window and triggers a sync +- THEN the section MUST dispatch the sync and report its status + +### Requirement: ArchiMate import/export (REQ-FE-307) + +The ArchiMate section SHALL let the admin export the catalog to ArchiMate and import an ArchiMate file, validating the round-trip and reporting import/export status. + +`ArchiMateImportExport.vue` loads ArchiMate status, exports to ArchiMate, imports an uploaded file, validates the round-trip, and surfaces progress/result. + +#### Scenario: Export to ArchiMate +- WHEN the admin triggers an export +- THEN the section MUST produce the ArchiMate export and report status + +#### Scenario: Import an ArchiMate file +- WHEN the admin uploads and confirms an ArchiMate file +- THEN the section MUST dispatch the import and report the result + +### Requirement: Statistics overview (REQ-FE-308) + +The statistics overview SHALL load and display aggregate object counts and statistics for the catalog. + +`StatisticsOverview.vue` loads object counts and statistics from the store and renders them. + +#### Scenario: View statistics +- WHEN the statistics section loads +- THEN it MUST display the current object counts and statistics + +### Requirement: Version information (REQ-FE-309) + +The version section SHALL load and display the app's version information. + +`VersionInformation.vue` loads version info from the store and renders it. + +#### Scenario: View version information +- WHEN the version section loads +- THEN it MUST display the current version information + +### Requirement: Navigation configuration panel (REQ-FE-310) + +The navigation configuration panel SHALL load the current configuration and persist edits made by the user. + +`Configuration.vue` fetches the configuration data and saves changes. + +#### Scenario: Save navigation configuration +- WHEN the user edits and saves the configuration +- THEN the panel MUST persist it and report the result diff --git a/openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md new file mode 100644 index 00000000..6b70aec9 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md @@ -0,0 +1,12 @@ +# Tasks + +- [x] task-1: fe-settings-ui#REQ-FE-301 — Settings shell & navigation (retroactive annotation) +- [x] task-2: fe-settings-ui#REQ-FE-302 — OpenRegister integration configuration (retroactive annotation) +- [x] task-3: fe-settings-ui#REQ-FE-303 — User groups configuration (retroactive annotation) +- [x] task-4: fe-settings-ui#REQ-FE-304 — Email configuration (retroactive annotation) +- [x] task-5: fe-settings-ui#REQ-FE-305 — Cronjob configuration (retroactive annotation) +- [x] task-6: fe-settings-ui#REQ-FE-306 — Organization synchronization configuration (retroactive annotation) +- [x] task-7: fe-settings-ui#REQ-FE-307 — ArchiMate import/export (retroactive annotation) +- [x] task-8: fe-settings-ui#REQ-FE-308 — Statistics overview (retroactive annotation) +- [x] task-9: fe-settings-ui#REQ-FE-309 — Version information (retroactive annotation) +- [x] task-10: fe-settings-ui#REQ-FE-310 — Navigation configuration panel (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/proposal.md b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/proposal.md new file mode 100644 index 00000000..6e9686d0 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/proposal.md @@ -0,0 +1,23 @@ +# Retrofit — fe-shell-navigation + +Describes observed behavior of the frontend app shell, navigation surfaces and generic presentational components as REQ(s) under the `fe-shell-navigation` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every non-trivial frontend method gets a spec). + +## Affected code units + +- src/views/Dashboard.vue +- src/sidebars/directory/DirectorySideBar.vue +- src/sidebars/search/SearchSideBar.vue +- src/components/PaginationComponent.vue +- src/components/CollapsibleSection.vue +- src/components/AlwaysVisibleSection.vue +- src/components/PublishedIcon.vue + +## Approach + +- Describe the observed behavior of the dashboard, the directory/search sidebars, pagination and the reusable section/icon components. +- Group methods implementing the same observable behavior under one REQ. +- Annotate each method with `@spec` pointing at the matching task. + +## Impact + +- Documentation only. No runtime behavior change. diff --git a/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/specs/fe-shell-navigation/spec.md b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/specs/fe-shell-navigation/spec.md new file mode 100644 index 00000000..a9f0abd1 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/specs/fe-shell-navigation/spec.md @@ -0,0 +1,56 @@ +--- +status: draft +retrofit: true +--- + +# Frontend Shell & Navigation Specification + +## Purpose + +Captures the observed behavior of the dashboard view, the directory and search sidebars, the pagination control, and the reusable presentational components (collapsible/always-visible sections, published-status icon). + +## ADDED Requirements + +### Requirement: Dashboard view (REQ-FE-401) + +The dashboard SHALL load and display the catalog's overview data (counts, recent items, widgets) and allow the user to navigate into detail views. + +`Dashboard.vue` fetches dashboard data from the store, formats it for display, and routes the user into object/organisation detail. + +#### Scenario: Open the dashboard +- WHEN the dashboard loads +- THEN it MUST display the overview data and navigation entries + +### Requirement: Directory & search sidebars (REQ-FE-402) + +The directory and search sidebars SHALL let the user browse/filter the catalog and apply search criteria that update the active object list. + +`DirectorySideBar.vue` renders the directory tree/filters and applies selection; `SearchSideBar.vue` collects search criteria and dispatches the search to update results. + +#### Scenario: Apply a directory filter +- WHEN the user selects a directory entry or applies a search +- THEN the sidebar MUST update the active object list accordingly + +### Requirement: Pagination control (REQ-FE-403) + +The pagination control SHALL expose page navigation and emit page-change events that update the displayed result set. + +`PaginationComponent.vue` computes page state and emits navigation events to the parent. + +#### Scenario: Change page +- WHEN the user navigates to another page +- THEN the control MUST emit the page change so the parent reloads that page + +### Requirement: Reusable presentational components (REQ-FE-404) + +The reusable section and icon components SHALL render their content/state consistently: collapsible/always-visible sections SHALL toggle and render their slot content, and the published-status icon SHALL render the correct icon and tooltip for an object's publication state. + +`CollapsibleSection.vue` toggles open/closed and renders its content; `AlwaysVisibleSection.vue` renders a permanently-visible section; `PublishedIcon.vue` derives the icon/tooltip from the object's publication status. + +#### Scenario: Toggle a collapsible section +- WHEN the user toggles a collapsible section +- THEN it MUST show or hide its slot content + +#### Scenario: Render publication status +- WHEN the published icon renders for an object +- THEN it MUST reflect that object's publication state and tooltip diff --git a/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md new file mode 100644 index 00000000..c8a3a66a --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md @@ -0,0 +1,6 @@ +# Tasks + +- [x] task-1: fe-shell-navigation#REQ-FE-401 — Dashboard view (retroactive annotation) +- [x] task-2: fe-shell-navigation#REQ-FE-402 — Directory & search sidebars (retroactive annotation) +- [x] task-3: fe-shell-navigation#REQ-FE-403 — Pagination control (retroactive annotation) +- [x] task-4: fe-shell-navigation#REQ-FE-404 — Reusable presentational components (retroactive annotation) diff --git a/openspec/changes/retrofit-2026-05-26-fe-stores/proposal.md b/openspec/changes/retrofit-2026-05-26-fe-stores/proposal.md new file mode 100644 index 00000000..ac206ef9 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-stores/proposal.md @@ -0,0 +1,23 @@ +# Retrofit — fe-stores + +Describes observed behavior of the frontend Pinia stores, the object-operations store plugin, the theme service and the heartbeat utility as REQ(s) under the `fe-stores` capability. Code already exists — this change retroactively specifies it (strict coverage policy: every non-trivial frontend method gets a spec). + +## Affected code units + +- src/store/modules/settings.js +- src/store/modules/organisatie.js +- src/store/modules/navigation.js +- src/store/modules/catalog.js +- src/store/plugins/softwarecatalogPlugin.js +- src/services/getTheme.js +- src/utils/heartbeat.js + +## Approach + +- Describe the observed state, actions and side effects of each store module, the shared object-operations plugin, the theme helper and the heartbeat client. +- Group actions implementing the same observable behavior under one REQ per module. +- Annotate each method with `@spec` pointing at the matching task. Pure framework plumbing (Pinia plugin installer, generic `$patch` passthrough, class constructor) is marked `@spec exclude`. + +## Impact + +- Documentation only. No runtime behavior change. diff --git a/openspec/changes/retrofit-2026-05-26-fe-stores/specs/fe-stores/spec.md b/openspec/changes/retrofit-2026-05-26-fe-stores/specs/fe-stores/spec.md new file mode 100644 index 00000000..a85f7328 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-stores/specs/fe-stores/spec.md @@ -0,0 +1,94 @@ +--- +status: draft +retrofit: true +--- + +# Frontend Stores Specification + +## Purpose + +Captures the observed behavior of the Pinia store modules (settings, organisation, navigation, catalog), the shared object-operations store plugin, the theme helper service, and the heartbeat client utility. + +## ADDED Requirements + +### Requirement: Settings store (REQ-FE-501) + +The settings store SHALL load, hold and persist the app's configuration (general, OpenRegister, email, user-groups, sync, ArchiMate, statistics, version) and SHALL expose actions to validate, auto-configure, test (email/round-trip) and poll status, surfacing errors to consumers. + +`settings.js` exposes load*/save*/test*/validate*/poll actions and clears/holds error and status state for the settings UI. + +#### Scenario: Load configuration +- WHEN a load action runs +- THEN the store MUST populate the corresponding configuration state + +#### Scenario: Save configuration +- WHEN a save action runs with valid input +- THEN the store MUST persist it and reflect success or error state + +### Requirement: Organisation/contact-person store (REQ-FE-502) + +The organisation store SHALL fetch contact persons (with linked-user details) for an organisation and SHALL expose user-management actions (convert to user, change password, enable/disable, update groups, fetch groups/user info). + +`organisatie.js` fetches contact persons and bulk/single user info and dispatches user-management mutations, holding error state. + +#### Scenario: Fetch contact persons with user details +- WHEN the fetch action runs for an organisation +- THEN the store MUST populate the contact persons with their linked-user details + +#### Scenario: Run a user-management action +- WHEN a user-management action runs +- THEN the store MUST perform it and reflect the updated state + +### Requirement: Navigation store (REQ-FE-503) + +The navigation store SHALL hold the active UI navigation state — the open modal, open dialog, current selection and selected organisation — and SHALL expose setters that carry transfer data between views. + +`navigation.js` exposes setModal/setDialog/setSelected/setSelectedOrganisatie and a transfer-data accessor. + +#### Scenario: Open a modal +- WHEN `setModal` is called with a modal identifier +- THEN the store MUST record the active modal so the shell renders it + +### Requirement: Catalog store (REQ-FE-504) + +The catalog store SHALL hold catalog state and SHALL expose actions to clear errors and reset its state. + +`catalog.js` exposes clearError and reset. + +#### Scenario: Reset the catalog store +- WHEN `reset` is called +- THEN the store MUST return to its initial state + +### Requirement: Object operations store plugin (REQ-FE-505) + +The object-operations store plugin SHALL provide the shared CRUD and lifecycle actions for register objects — fetch/save/patch/copy/delete, publish/depublish, lock/unlock, validate, download, merge, the bulk-operation runner, selection management, and the supporting initialization/refresh helpers — and SHALL surface per-object error state. + +`softwarecatalogPlugin.js` augments stores with these object actions (e.g. `saveObject`, `deleteObject`, `publishObject`, `lockObject`, `mergeObjects`, `massPublishObjects`, `refreshObjectList`, `setActiveObject`, `toggleSelectAllObjects`, `updateColumnFilter`). The plugin installer and the generic `$patch` passthrough are framework plumbing and are excluded from coverage. + +#### Scenario: Save an object +- WHEN `saveObject` is dispatched +- THEN the plugin MUST create or update the object and refresh the relevant state + +#### Scenario: Run a bulk operation +- WHEN a mass-operation action (e.g. `massPublishObjects`) is dispatched over the selection +- THEN the plugin MUST apply the operation to each selected object and report progress + +### Requirement: Theme service (REQ-FE-506) + +The theme service SHALL detect the active Nextcloud theme (light/dark) and expose the corresponding theme variables to consumers. + +`getTheme.js` exposes `getTheme`, `getThemeVariables` and `isDarkTheme`. + +#### Scenario: Detect dark theme +- WHEN the active theme is dark +- THEN `isDarkTheme()` MUST return true and `getThemeVariables()` MUST return the dark variables + +### Requirement: Heartbeat client (REQ-FE-507) + +The heartbeat client SHALL periodically send a heartbeat to the backend while active and SHALL allow starting, stopping and querying the heartbeat, plus wrapping an async operation with an active heartbeat. + +`heartbeat.js` exposes start/stop/startHeartbeat/stopHeartbeat/sendHeartbeat/isHeartbeatRunning/withHeartbeat. The class constructor is excluded as initialization-only. + +#### Scenario: Start and stop the heartbeat +- WHEN `start()` is called and later `stop()` +- THEN the client MUST begin sending heartbeats and then cease, with `isHeartbeatRunning()` reflecting the state diff --git a/openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md b/openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md new file mode 100644 index 00000000..100a0339 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md @@ -0,0 +1,9 @@ +# Tasks + +- [x] task-1: fe-stores#REQ-FE-501 — Settings store (retroactive annotation) +- [x] task-2: fe-stores#REQ-FE-502 — Organisation/contact-person store (retroactive annotation) +- [x] task-3: fe-stores#REQ-FE-503 — Navigation store (retroactive annotation) +- [x] task-4: fe-stores#REQ-FE-504 — Catalog store (retroactive annotation) +- [x] task-5: fe-stores#REQ-FE-505 — Object operations store plugin (retroactive annotation) +- [x] task-6: fe-stores#REQ-FE-506 — Theme service (retroactive annotation) +- [x] task-7: fe-stores#REQ-FE-507 — Heartbeat client (retroactive annotation) diff --git a/src/App.vue b/src/App.vue index d4f48161..01cc1629 100644 --- a/src/App.vue +++ b/src/App.vue @@ -64,6 +64,9 @@ export default { Dialogs, }, + /** + * @spec exclude Vue dependency-injection provider — framework plumbing + */ provide() { return { // Channel for CnDetailPage → host-rendered CnObjectSidebar. @@ -132,11 +135,17 @@ export default { }, computed: { + /** + * @spec exclude pure passthrough getter of injected permissions — DI getter + */ permissions() { return window.OC?.currentUser?.permissions ?? [] }, }, + /** + * @spec exclude Vue lifecycle hook — SPA shell bootstrap + */ async created() { // SoftwareCatalog stores still need to come up so legacy custom // components (OrganisatieIndexView, SoftwareCatalogSettingsPage) @@ -159,6 +168,7 @@ export default { * * @param {string} key Translation key. * @return {string} Translated string (or the key on miss). + * @spec exclude i18n wrapper around @nextcloud/l10n translate */ translateForApp(key) { return ncT('softwarecatalog', key) diff --git a/src/components/AddContactpersoonModal.vue b/src/components/AddContactpersoonModal.vue index 4fbb4957..0cbf0749 100644 --- a/src/components/AddContactpersoonModal.vue +++ b/src/components/AddContactpersoonModal.vue @@ -117,6 +117,9 @@ export default { }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ isFormValid() { return this.formData.voornaam.trim() && this.formData.achternaam.trim() @@ -126,11 +129,17 @@ export default { }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ closeModal() { this.resetForm() this.$emit('close') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ resetForm() { this.formData = { voornaam: '', @@ -140,11 +149,17 @@ export default { this.loading = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ async saveContactpersoon() { if (!this.isFormValid) { showError(this.t('softwarecatalog', 'Please fill in all required fields with valid data')) @@ -197,6 +212,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-1 + */ generateUuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0 diff --git a/src/components/AlwaysVisibleSection.vue b/src/components/AlwaysVisibleSection.vue index 609b0990..d1e36176 100644 --- a/src/components/AlwaysVisibleSection.vue +++ b/src/components/AlwaysVisibleSection.vue @@ -238,6 +238,7 @@ export default defineComponent({ methods: { /** * Handle save button click + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 */ handleSave() { this.$emit('save') @@ -245,6 +246,7 @@ export default defineComponent({ /** * Handle refresh button click + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 */ handleRefresh() { this.$emit('refresh') diff --git a/src/components/CollapsibleSection.vue b/src/components/CollapsibleSection.vue index 17b9498a..66d0cdc4 100644 --- a/src/components/CollapsibleSection.vue +++ b/src/components/CollapsibleSection.vue @@ -279,6 +279,7 @@ export default { methods: { /** * Toggle section expanded state + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 */ toggleExpanded() { this.isExpanded = !this.isExpanded @@ -286,6 +287,7 @@ export default { /** * Handle save button click + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 */ handleSave() { this.$emit('save') @@ -293,6 +295,7 @@ export default { /** * Handle refresh button click + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 */ handleRefresh() { this.$emit('refresh') diff --git a/src/components/ContactpersonenList.vue b/src/components/ContactpersonenList.vue index acb11817..81eff142 100644 --- a/src/components/ContactpersonenList.vue +++ b/src/components/ContactpersonenList.vue @@ -394,10 +394,16 @@ export default { }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ organisatieStore() { return useOrganisatieStore() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ contactpersonen() { // Use contactpersonen from organisation data if available, otherwise fall back to store if ( @@ -411,19 +417,31 @@ export default { return this.organisatieStore.getContactpersonen }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ loading() { return this.organisatieStore.isLoading }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ error() { return this.organisatieStore.getError }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ availableGroups() { return this.organisatieStore.getAvailableGroups }, // Password validation computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ passwordValidation() { return { minLength: this.newPassword.length >= 10, @@ -437,6 +455,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ isPasswordValid() { return Object.values(this.passwordValidation).every( (requirement) => requirement, @@ -445,6 +466,9 @@ export default { }, watch: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ newPassword(newVal) { // Clear existing timeout if (this.pwnedCheckTimeout) { @@ -466,12 +490,18 @@ export default { }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ async mounted() { await this.loadData() // Load user info and groups to get status information await this.loadUserInfoAndGroups() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ beforeDestroy() { // Clean up timeouts to prevent memory leaks if (this.userStatusRefreshTimeout) { @@ -490,6 +520,7 @@ export default { * Compute SHA-1 hash of a string * @param {string} str - String to hash * @return {Promise} SHA-1 hash in hexadecimal format (uppercase) + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async sha1(str) { // Simple SHA-1 implementation @@ -590,6 +621,7 @@ export default { * @param {number} value - Value to rotate * @param {number} amount - Amount to rotate * @return {number} Rotated value + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ rotl(value, amount) { return ((value << amount) | (value >>> (32 - amount))) >>> 0 @@ -598,6 +630,7 @@ export default { /** * Check if password is in Have I Been Pwned database * @param {string} password - Password to check + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async checkPasswordPwned(password) { if (!password || password.length < 10) { @@ -659,6 +692,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ async loadData() { try { // Only fetch available groups, contactpersonen come from organisation data @@ -677,6 +713,7 @@ export default { * Process contactpersonen data from organisation object to match expected format. * @param {Array} rawContactpersonen - Raw contactpersonen data from organisation. * @return {Array} Processed contactpersonen with user information. + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ processContactpersonen(rawContactpersonen) { return rawContactpersonen.map((contactpersoon) => { @@ -716,6 +753,7 @@ export default { /** * Load user info and available groups in parallel + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async loadUserInfoAndGroups() { // Prevent multiple simultaneous calls @@ -790,6 +828,7 @@ export default { * Update contactpersonen with bulk user info. * @param {object} bulkUserInfo - User info object keyed by contactpersoon ID. * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ updateContactpersonenWithUserInfo(bulkUserInfo) { if (!this.organisationData.contactpersonen) return @@ -837,6 +876,7 @@ export default { /** * Refresh user statuses from Nextcloud for all contact persons * @deprecated Use loadUserInfoAndGroups() instead for better performance + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async refreshUserStatuses() { console.info( @@ -847,6 +887,7 @@ export default { /** * Public method to refresh user statuses - can be called from parent component + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async refreshUserData() { console.info('Public refreshUserData called') @@ -857,6 +898,7 @@ export default { * Get contactperson name. * @param {object} contactpersoon - The contact person object. * @return {string} The contact person's name. + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ getContactpersoonName(contactpersoon) { const data = contactpersoon.data @@ -874,6 +916,7 @@ export default { * Filter groups to only show those available in the modal. * @param {object} contactpersoon - The contact person object. * @return {Array} Filtered array of group IDs. + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ getFilteredGroups(contactpersoon) { if (!contactpersoon.user.groups || contactpersoon.user.groups.length === 0) { @@ -893,6 +936,7 @@ export default { * Format group name. * @param {string} groupId - The group ID. * @return {string} Formatted group name. + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ formatGroupName(groupId) { const groupMap = { @@ -903,6 +947,9 @@ export default { return groupMap[groupId] || groupId }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ async convertToUser(contactpersoon) { console.info('convertToUser called with:', contactpersoon) console.info( @@ -1004,6 +1051,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ openPasswordDialog(contactpersoon) { this.selectedContactpersoon = contactpersoon this.newPassword = '' @@ -1016,6 +1066,9 @@ export default { this.showPasswordDialog = true }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ closePasswordDialog() { this.showPasswordDialog = false this.selectedContactpersoon = null @@ -1029,6 +1082,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ async savePassword() { if (!this.newPassword || this.newPassword.length < 10) { showError( @@ -1083,6 +1139,7 @@ export default { * Open groups management dialog. * @param {object} contactpersoon - The contact person object. * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async openGroupsDialog(contactpersoon) { this.selectedContactpersoon = contactpersoon @@ -1106,6 +1163,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ closeGroupsDialog() { this.showGroupsDialog = false this.selectedContactpersoon = null @@ -1113,6 +1173,9 @@ export default { this.groupsLoading = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 + */ toggleGroup(groupId, checked) { if (checked) { if (!this.selectedGroups.includes(groupId)) { @@ -1129,6 +1192,7 @@ export default { /** * Save user groups. * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async saveGroups() { if (!this.selectedContactpersoon) return @@ -1162,6 +1226,7 @@ export default { /** * Disable a user account * @param {object} contactpersoon - The contact person object + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async disableUser(contactpersoon) { try { @@ -1182,6 +1247,7 @@ export default { /** * Enable a user account * @param {object} contactpersoon - The contact person object + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ async enableUser(contactpersoon) { try { @@ -1204,6 +1270,7 @@ export default { * @param {string} contactpersoonId - The ID of the contact person. * @param {boolean} disabled - Whether the user is disabled. * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ updateContactpersoonStatus(contactpersoonId, disabled) { // Find and update the contactpersoon in the organisation data. @@ -1242,6 +1309,7 @@ export default { * @param {string} contactpersoonId - The ID of the contact person. * @param {Array} groups - Array of group IDs. * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-2 */ updateContactpersoonGroups(contactpersoonId, groups) { // Find and update the contactpersoon in the organisation data. diff --git a/src/components/PaginationComponent.vue b/src/components/PaginationComponent.vue index 0dd23c11..c622e84f 100644 --- a/src/components/PaginationComponent.vue +++ b/src/components/PaginationComponent.vue @@ -155,6 +155,7 @@ export default { /** * Get current page size option object * @return {object} Current page size option object + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-3 */ currentPageSizeOption() { return this.pageSizeOptions.find(option => option.value === this.currentPageSize) || this.pageSizeOptions[1] @@ -162,6 +163,7 @@ export default { /** * Calculate visible page numbers for pagination * @return {Array} Array of page numbers and ellipsis + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-3 */ visiblePages() { const current = this.currentPage @@ -209,6 +211,7 @@ export default { * Change to a specific page * @param {number} page - The page number to change to * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-3 */ changePage(page) { if (page !== this.currentPage && page >= 1 && page <= this.totalPages) { @@ -224,6 +227,7 @@ export default { * Change page size * @param {object} option - Selected page size option * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-3 */ changePageSize(option) { if (option.value !== this.currentPageSize) { diff --git a/src/components/PublishedIcon.vue b/src/components/PublishedIcon.vue index 1fd89121..59cd7d34 100644 --- a/src/components/PublishedIcon.vue +++ b/src/components/PublishedIcon.vue @@ -19,6 +19,9 @@ export default { }, }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-4 + */ defaultTooltip() { return this.isPublished ? 'Published' : 'Not Published' }, diff --git a/src/components/SelectedObjectsList.vue b/src/components/SelectedObjectsList.vue index cab0f0d9..82bd70f4 100644 --- a/src/components/SelectedObjectsList.vue +++ b/src/components/SelectedObjectsList.vue @@ -109,6 +109,7 @@ export default { /** * Get objects to display (either from props or from store) * @return {Array} Array of publication objects + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-8 */ selectedObjects() { return this.objects || objectStore.selectedObjects || [] @@ -118,6 +119,7 @@ export default { /** * Remove object from selected objects in the store * @param {string} objectId - The object ID to remove + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-8 */ removeObject(objectId) { // Always remove from store - the store is the source of truth @@ -135,6 +137,7 @@ export default { * Get display name for an object * @param {object} obj - The object to get name for * @return {string} The display name + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-8 */ getObjectName(obj) { return obj['@self']?.name @@ -148,6 +151,7 @@ export default { * Get schema name for an object * @param {object} obj - The object to get schema for * @return {string} The schema name or fallback text + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-8 */ getObjectSchema(obj) { // Try to get schema name from various possible locations @@ -166,6 +170,7 @@ export default { * Get error message for an object * @param {object} obj - The object to get error for * @return {string|null} The error message or null if no error + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-8 */ getObjectError(obj) { const objectId = obj.id || obj['@self']?.id diff --git a/src/components/cards/OrganisatieCard.vue b/src/components/cards/OrganisatieCard.vue index c270b668..31b27067 100644 --- a/src/components/cards/OrganisatieCard.vue +++ b/src/components/cards/OrganisatieCard.vue @@ -197,6 +197,9 @@ export default { * Watch for changes in currentView and refresh user data when switching to contactpersonen */ currentView: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 + */ async handler(newView, oldView) { // Only trigger when actually switching TO contactpersonen view from a different view if (newView === 'contactpersonen' && oldView && oldView !== 'contactpersonen') { @@ -231,6 +234,7 @@ export default { * Get the summary/tooltip text for the organisation * @param {object} item - The organisation object * @return {string} The summary text + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ getOrganisatieSummary(item) { if (item?.beschrijvingKort) return item.beschrijvingKort @@ -244,6 +248,7 @@ export default { * Execute an object action * @param {object} action - The action to execute * @param {object} item - The item to execute the action on + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ executeObjectAction(action, item) { if (action.handler) { @@ -255,6 +260,7 @@ export default { * Format website URL to ensure it has protocol * @param {string} url - The website URL * @return {string} Formatted URL with protocol + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ formatWebsiteUrl(url) { if (!url) return '#' @@ -269,6 +275,7 @@ export default { * @param {string} text - Text to truncate * @param {number} maxLength - Maximum length * @return {string} Truncated text + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ truncateText(text, maxLength = 150) { if (!text || text.length <= maxLength) return text @@ -277,6 +284,7 @@ export default { /** * Toggle between organisation and contactpersonen views + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ async toggleView() { const newView = this.currentView === 'organisatie' ? 'contactpersonen' : 'organisatie' @@ -297,6 +305,7 @@ export default { /** * Get the organisation address * @return {string} The organisation's address + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-4 */ getOrganisatieAdres() { const adres = this.item?.adres diff --git a/src/dialogs/Dialogs.vue b/src/dialogs/Dialogs.vue index 320f43d0..be77d7eb 100644 --- a/src/dialogs/Dialogs.vue +++ b/src/dialogs/Dialogs.vue @@ -38,6 +38,9 @@ export default { MassUnlockObjects, MassValidateObjects, }, + /** + * @spec exclude Pinia store wiring in setup() — bootstrap plumbing + */ setup() { return { navigationStore, diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index c2364e47..ba5a9e98 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -56,6 +56,9 @@ export default { MigrationObject, MergeObject, }, + /** + * @spec exclude Pinia store wiring in setup() — bootstrap plumbing + */ setup() { return { navigationStore, @@ -67,6 +70,7 @@ export default { * or null if the modal is not a generic object edit modal. * * @return {string|null} The object type key, or null + * @spec exclude computed passthrough of navigation store modal type — DI getter */ genericObjectModalType() { const modal = navigationStore.modal diff --git a/src/modals/OrganisationModal.vue b/src/modals/OrganisationModal.vue index 278436ec..819b0db9 100644 --- a/src/modals/OrganisationModal.vue +++ b/src/modals/OrganisationModal.vue @@ -181,6 +181,9 @@ export default { isCopyMode() { return this.mode === 'copy' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ modalTitle() { if (this.isEditMode) { return this.t('softwarecatalog', 'Edit Organisation') @@ -195,12 +198,18 @@ export default { }, watch: { organisation: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ handler() { this.loadOrganisationData() }, immediate: true, }, show: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ handler(newVal) { if (newVal) { this.resetForm() @@ -210,6 +219,9 @@ export default { immediate: true, }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ beforeUnmount() { // Clean up countdown interval if (this.countdownInterval) { @@ -217,6 +229,9 @@ export default { } }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ resetForm() { this.formData = { naam: '', @@ -241,6 +256,9 @@ export default { this.countdownInterval = null } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ loadOrganisationData() { if (!this.organisation) return @@ -264,16 +282,23 @@ export default { this.selectedType = this.organisationTypes.find(type => type.value === this.formData.type) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ handleTypeChange(selectedOption) { console.info('Type changed:', selectedOption) this.formData.type = selectedOption ? selectedOption.value : '' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ closeModal() { this.$emit('close') }, /** * Get only the changed properties between original and current form data * @return {object} Object containing only the changed properties + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 */ getChangedProperties() { if (!this.isEditMode) { @@ -305,6 +330,9 @@ export default { return changes }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-3 + */ async saveOrganisation() { if (!this.isFormValid) { showError(this.t('softwarecatalog', 'Please fill in all required fields')) diff --git a/src/modals/object/ChangeOrganisatieStatusDialog.vue b/src/modals/object/ChangeOrganisatieStatusDialog.vue index 563b55aa..8d31155c 100644 --- a/src/modals/object/ChangeOrganisatieStatusDialog.vue +++ b/src/modals/object/ChangeOrganisatieStatusDialog.vue @@ -94,6 +94,7 @@ export default { /** * Get the organisation name for display * @return {string} The organisation name + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-5 */ getOrganisatieName() { const organisatie = objectStore.getActiveObject('organisatie') @@ -103,6 +104,7 @@ export default { /** * Close the dialog * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-5 */ closeDialog() { this.success = null @@ -114,6 +116,7 @@ export default { /** * Change the organisation status. * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-5 */ async changeStatus() { this.loading = true diff --git a/src/modals/object/DeleteObject.vue b/src/modals/object/DeleteObject.vue index 9be4c52b..6d9c2389 100644 --- a/src/modals/object/DeleteObject.vue +++ b/src/modals/object/DeleteObject.vue @@ -71,6 +71,9 @@ export default { } }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-6 + */ closeDialog() { navigationStore.setDialog(false) clearTimeout(this.closeModalTimeout) @@ -78,6 +81,9 @@ export default { this.loading = false this.error = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-6 + */ async deleteObject() { this.loading = true diff --git a/src/modals/object/DownloadObject.vue b/src/modals/object/DownloadObject.vue index 8100bb82..e1bb74c9 100644 --- a/src/modals/object/DownloadObject.vue +++ b/src/modals/object/DownloadObject.vue @@ -75,6 +75,9 @@ export default { closeModalTimeout: null, } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ mounted() { if (objectStore.objectItem?.id) { this.downloadObject() @@ -84,6 +87,9 @@ export default { json, jsonParseLinter, getTheme, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ closeModal() { navigationStore.setModal(false) clearTimeout(this.closeModalTimeout) @@ -91,6 +97,9 @@ export default { this.loading = false this.error = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ async downloadObject() { this.loading = true diff --git a/src/modals/object/LockObject.vue b/src/modals/object/LockObject.vue index d62e48ee..62027abf 100644 --- a/src/modals/object/LockObject.vue +++ b/src/modals/object/LockObject.vue @@ -84,6 +84,9 @@ export default { } }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-6 + */ closeModal() { navigationStore.setModal(false) clearTimeout(this.closeModalTimeout) @@ -93,6 +96,9 @@ export default { this.process = '' this.duration = 3600 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-6 + */ async lockObject() { this.loading = true diff --git a/src/modals/object/MassDeleteObject.vue b/src/modals/object/MassDeleteObject.vue index 9ccd1dd2..a5024be5 100644 --- a/src/modals/object/MassDeleteObject.vue +++ b/src/modals/object/MassDeleteObject.vue @@ -100,6 +100,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to delete + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToDelete() { return objectStore.selectedObjects || [] @@ -108,6 +109,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = objectStore.selectedObjects?.length || 0 @@ -122,10 +124,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -134,12 +142,18 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ navigateToDeleted() { // Close the dialog first this.closeDialog() // Navigate to the deleted objects section navigationStore.setSelected('deleted') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async deleteObject() { this.loading = true @@ -174,6 +188,9 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() diff --git a/src/modals/object/MassDepublishObjects.vue b/src/modals/object/MassDepublishObjects.vue index 9f7e110e..ba3942b6 100644 --- a/src/modals/object/MassDepublishObjects.vue +++ b/src/modals/object/MassDepublishObjects.vue @@ -100,6 +100,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to depublish + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToDepublish() { return objectStore.selectedObjects || [] @@ -108,6 +109,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = this.objectsToDepublish.length @@ -121,10 +123,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -133,11 +141,17 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async depublishObjects() { this.loading = true diff --git a/src/modals/object/MassLockObjects.vue b/src/modals/object/MassLockObjects.vue index f99c51dc..e324944e 100644 --- a/src/modals/object/MassLockObjects.vue +++ b/src/modals/object/MassLockObjects.vue @@ -116,6 +116,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to lock + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToLock() { return objectStore.selectedObjects || [] @@ -124,6 +125,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = this.objectsToLock.length @@ -137,10 +139,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -149,11 +157,17 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async lockObjects() { this.loading = true diff --git a/src/modals/object/MassPublishObjects.vue b/src/modals/object/MassPublishObjects.vue index 157221d8..7e7b533a 100644 --- a/src/modals/object/MassPublishObjects.vue +++ b/src/modals/object/MassPublishObjects.vue @@ -100,6 +100,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to publish + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToPublish() { return objectStore.selectedObjects || [] @@ -108,6 +109,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = objectStore.selectedObjects?.length || 0 @@ -121,10 +123,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -133,11 +141,17 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async publishObjects() { this.loading = true diff --git a/src/modals/object/MassUnlockObjects.vue b/src/modals/object/MassUnlockObjects.vue index dc2ef459..d74fcbc5 100644 --- a/src/modals/object/MassUnlockObjects.vue +++ b/src/modals/object/MassUnlockObjects.vue @@ -100,6 +100,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to unlock + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToUnlock() { return objectStore.selectedObjects || [] @@ -108,6 +109,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = this.objectsToUnlock.length @@ -121,10 +123,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -133,11 +141,17 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async unlockObjects() { this.loading = true diff --git a/src/modals/object/MassValidateObjects.vue b/src/modals/object/MassValidateObjects.vue index f7d3e60a..67209c5c 100644 --- a/src/modals/object/MassValidateObjects.vue +++ b/src/modals/object/MassValidateObjects.vue @@ -105,6 +105,7 @@ export default { /** * Get the objects to operate on from selected objects * @return {Array} Array of objects to validate + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ objectsToValidate() { return objectStore.selectedObjects || [] @@ -113,6 +114,7 @@ export default { /** * Get the dialog title based on number of objects * @return {string} Dialog title + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 */ dialogTitle() { const count = this.objectsToValidate.length @@ -126,10 +128,16 @@ export default { this.initializeSelection() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ initializeSelection() { // Store the original count for success message this.originalSelectedCount = objectStore.selectedObjects?.length || 0 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ closeDialog() { // Clear any pending timeout that might reopen the dialog if (this.closeModalTimeout) { @@ -138,11 +146,17 @@ export default { } navigationStore.setDialog(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeDialog() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-7 + */ async validateObjects() { this.loading = true diff --git a/src/modals/object/MergeObject.vue b/src/modals/object/MergeObject.vue index d5efe406..0d299f9d 100644 --- a/src/modals/object/MergeObject.vue +++ b/src/modals/object/MergeObject.vue @@ -450,9 +450,15 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ sourceObject() { return objectStore.objectItem }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ mergeableProperties() { if (!this.sourceObject || !this.selectedTargetObject) { return [] @@ -463,6 +469,9 @@ export default { return [...new Set([...sourceProps, ...targetProps])] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ canMerge() { return Object.keys(this.mergedData).length > 0 }, @@ -471,6 +480,9 @@ export default { this.initializeMerge() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ initializeMerge() { if (!this.sourceObject) { this.closeModal() @@ -479,6 +491,9 @@ export default { this.loadSourceData() this.searchObjects() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ async searchObjects() { if (!catalogStore.catalogiItem || !catalogStore.schemaItem) { return @@ -498,20 +513,32 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ selectTargetObject(obj) { this.selectedTargetObject = obj }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ nextStep() { if (this.step === 1 && this.selectedTargetObject) { this.step = 2 this.initializeMergeData() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ previousStep() { if (this.step === 2) { this.step = 1 } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ initializeMergeData() { // Initialize merge data with default values this.mergedData = {} @@ -554,6 +581,9 @@ export default { // eslint-disable-next-line no-console console.log('Initial propertySelections after setup:', this.propertySelections) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ getMergeOptions(property) { const options = [] @@ -580,6 +610,9 @@ export default { return options }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ onPropertySelectionChange(property, selectedOption) { // eslint-disable-next-line no-console console.log('Property selection change:', property, selectedOption) @@ -596,6 +629,9 @@ export default { } } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ displayValue(value, maxLength = 100) { if (value === null || value === undefined) { return 'N/A' @@ -615,11 +651,17 @@ export default { return displayText }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ truncateText(text, maxLength) { if (!text) return '' if (text.length <= maxLength) return text return text.substring(0, maxLength) + '...' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ async performMerge() { if (!this.canMerge) { return @@ -681,6 +723,9 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ viewMergedObject() { // Navigate to the merged object in view mode if (this.selectedTargetObject) { @@ -688,18 +733,30 @@ export default { navigationStore.setModal('viewObject') } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ toggleFileList() { this.showFileList = !this.showFileList }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ toggleRelationList() { this.showRelationList = !this.showRelationList }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ formatFileSize(bytes) { if (!bytes) return 'N/A' const sizes = ['Bytes', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(1024)) return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ getFileType(filename) { if (!filename) return 'Unknown' const ext = filename.split('.').pop()?.toLowerCase() @@ -721,6 +778,9 @@ export default { } return types[ext] || ext?.toUpperCase() || 'Unknown' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ async loadSourceData() { // Load files and relations for the source object if (!this.sourceObject) return @@ -743,6 +803,9 @@ export default { this.sourceRelations = [] } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-3 + */ closeModal() { navigationStore.setModal(false) }, diff --git a/src/modals/object/MigrationObject.vue b/src/modals/object/MigrationObject.vue index 1469cf8f..eb55ebad 100644 --- a/src/modals/object/MigrationObject.vue +++ b/src/modals/object/MigrationObject.vue @@ -377,6 +377,9 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ sourceRegister() { // Get register info from the first selected object if (this.selectedObjects.length === 0) return null @@ -388,6 +391,9 @@ export default { // If it's just an ID, try to find it in available registers return objectStore.availableRegisters.find(r => r.id === register) || { id: register, title: register } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ sourceSchema() { // Get schema info from the first selected object if (this.selectedObjects.length === 0) return null @@ -399,6 +405,9 @@ export default { // If it's just an ID, try to find it in available schemas return objectStore.availableSchemas.find(s => s.id === schema) || { id: schema, title: schema } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ targetPropertyOptions() { const options = this.targetProperties.map(prop => ({ label: `${prop.name} (${prop.type})`, @@ -412,6 +421,9 @@ export default { return options }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ canMigrate() { // Check if we have target register/schema and at least one property mapping const hasValidMappings = Object.values(this.uiMappings).some(option => option && option.value) @@ -422,6 +434,9 @@ export default { this.initializeMigration() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ initializeMigration() { // Get selected objects from the store or navigation context this.selectedObjects = objectStore.selectedObjects || [] @@ -431,6 +446,9 @@ export default { } this.loadAvailableRegisters() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ async loadAvailableRegisters() { this.loading = true try { @@ -444,6 +462,9 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ async onRegisterChange() { if (!this.targetRegister) { this.availableSchemas = [] @@ -463,18 +484,27 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ async onSchemaChange() { if (!this.targetSchema) { return } await this.loadSchemaProperties() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ removeObject(objectId) { this.selectedObjects = this.selectedObjects.filter(obj => obj.id !== objectId) if (this.selectedObjects.length === 0) { this.closeModal() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ nextStep() { if (this.step === 1 && this.selectedObjects.length > 0) { this.step = 2 @@ -482,11 +512,17 @@ export default { this.step = 3 } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ previousStep() { if (this.step > 1) { this.step-- } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ async loadSchemaProperties() { if (!this.sourceSchema || !this.targetSchema) { return @@ -510,6 +546,9 @@ export default { console.error('Error loading schema properties:', error) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ extractSchemaProperties(schema) { // Extract properties from schema definition const properties = [] @@ -524,6 +563,9 @@ export default { } return properties }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ initializePropertyMappings() { this.mapping = {} this.uiMappings = {} @@ -545,6 +587,9 @@ export default { } }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ async performMigration() { if (!this.canMigrate) { return @@ -591,13 +636,22 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ closeModal() { navigationStore.setModal(false) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ updateMappingFromUI(sourceProperty) { // Convert UI mappings to our simple mapping format this.convertUIToMapping() }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ convertUIToMapping() { // Convert from UI format (source -> target option) to our format (target -> source) this.mapping = {} @@ -608,6 +662,9 @@ export default { } } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-4 + */ convertMappingToUI() { // Convert from our format (target -> source) to UI format (source -> target option) this.uiMappings = {} diff --git a/src/modals/object/ObjectModal.vue b/src/modals/object/ObjectModal.vue index c5ec161c..c92adb8b 100644 --- a/src/modals/object/ObjectModal.vue +++ b/src/modals/object/ObjectModal.vue @@ -269,12 +269,18 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ catalogOptions() { return objectStore.getCollection('catalog').results.map(catalog => ({ id: catalog.id, label: catalog.title, })) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ registerOptions() { if (!this.selectedCatalogus) { return [] @@ -290,6 +296,9 @@ export default { label: register.title, })) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ schemaOptions() { if (!this.selectedRegister || !this.selectedCatalogus) { return [] @@ -311,12 +320,21 @@ export default { label: schema.title, })) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ fullSelectedSchema() { return objectStore.availableSchemas.find(schema => schema.id === this.selectedSchema?.id) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ schemaProperties() { return this.fullSelectedSchema?.properties || {} }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ dialogTitle() { const typeName = this.objectTypeKey.charAt(0).toUpperCase() + this.objectTypeKey.slice(1) return this.isNewObject ? `Add ${typeName}` : `Edit ${typeName}` @@ -324,6 +342,9 @@ export default { }, watch: { objectStore: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ handler(newValue) { if (newValue) { this.initializeData() @@ -332,6 +353,9 @@ export default { deep: true, }, 'navigationStore.modal': { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ handler(newValue) { if (newValue === 'objectModal') { // Reinitialize when modal opens @@ -345,6 +369,9 @@ export default { }, }, jsonData: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ handler(newValue) { if (this.activeTab === 1 && this.isValidJson(newValue)) { this.updateFormFromJson() @@ -352,6 +379,9 @@ export default { }, }, formData: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ handler(newValue) { if (this.activeTab === 0) { this.updateJsonFromForm() @@ -367,6 +397,9 @@ export default { clearTimeout(this.closeModalTimeout) }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ initializeData() { const activeCatalog = objectStore.getActiveObject('catalog') @@ -466,6 +499,9 @@ export default { } } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ async saveObject() { if (!this.selectedRegister || !this.selectedSchema) { this.error = 'Register and schema are required' @@ -562,6 +598,9 @@ export default { this.loading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ updateFormFromJson() { try { const parsed = JSON.parse(this.jsonData) @@ -571,6 +610,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ updateJsonFromForm() { try { this.jsonData = JSON.stringify(this.formData, null, 2) @@ -579,6 +621,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ isValidJson(str) { if (!str || !str.trim()) { return false @@ -591,6 +636,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ formatJSON() { try { if (this.jsonData) { @@ -602,6 +650,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ closeModal() { navigationStore.setModal(false) clearTimeout(this.closeModalTimeout) @@ -622,11 +673,17 @@ export default { return this.formData[key] ?? '' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ setFieldValue(key, value) { if (this.formData[key] === value) return this.$set(this.formData, key, value) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-2 + */ openUploadFilesModal() { // Set the navigationStore modal to 'uploadFiles' to show the UploadFiles modal navigationStore.setModal('uploadFiles') diff --git a/src/modals/object/UploadObject.vue b/src/modals/object/UploadObject.vue index 6835ff20..cf2289d5 100644 --- a/src/modals/object/UploadObject.vue +++ b/src/modals/object/UploadObject.vue @@ -154,12 +154,18 @@ export default { hasUpdated: false, } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ mounted() { this.initializeMappings() this.initializeSchemas() this.initializeRegisters() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ initializeMappings() { this.mappingsLoading = true @@ -179,6 +185,9 @@ export default { this.mappingsLoading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ initializeSchemas() { this.schemasLoading = true @@ -198,6 +207,9 @@ export default { this.schemasLoading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ initializeRegisters() { this.registersLoading = true @@ -217,6 +229,9 @@ export default { this.registersLoading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ closeModal() { navigationStore.setModal(false) this.success = null @@ -228,6 +243,9 @@ export default { url: '', } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ async uploadObject() { this.loading = true @@ -251,9 +269,15 @@ export default { this.loading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ prettifyJson() { this.object = JSON.stringify(JSON.parse(this.object), null, 2) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-5 + */ validateJson(json) { try { JSON.parse(json) diff --git a/src/modals/object/ViewObject.vue b/src/modals/object/ViewObject.vue index 467d9386..10e46a2b 100644 --- a/src/modals/object/ViewObject.vue +++ b/src/modals/object/ViewObject.vue @@ -960,14 +960,23 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ currentObject() { return objectStore.getActiveObject('publication') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isNewObject() { const obj = objectStore.getActiveObject('publication') return !obj || !obj?.['@self']?.id }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ objectProperties() { // Return array of [key, value] pairs, excluding '@self' and 'id' const objectData = this.currentObject || {} @@ -1081,6 +1090,7 @@ export default { /** * Filter out constant and immutable properties based on showConstantProperties state * @return {Array} Filtered properties array + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ filteredObjectProperties() { if (this.showConstantProperties) { @@ -1103,12 +1113,16 @@ export default { /** * Check if there are any constant or immutable properties * @return {boolean} True if there are constant/immutable properties + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ hasConstantOrImmutableProperties() { return this.objectProperties.some(([key, value]) => { return this.isConstantOrImmutable(key) }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ metadataProperties() { // Return array of [key, value] for metadata display if (!this.currentObject) return [] @@ -1207,6 +1221,9 @@ export default { return metadata }, // Files tab computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ paginatedFiles() { const filesData = objectStore.getRelatedData('publication', 'files') const files = filesData?.results || [] @@ -1217,30 +1234,51 @@ export default { } return files }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ filesTotalPages() { const filesPagination = objectStore.getPagination('publication_files') return filesPagination.pages }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ filesTotalItems() { const filesPagination = objectStore.getPagination('publication_files') return filesPagination.total }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ filesCurrentPageSize() { const filesPagination = objectStore.getPagination('publication_files') return filesPagination.limit }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ allFilesSelected() { return this.paginatedFiles.length > 0 && this.paginatedFiles.every(file => this.selectedAttachments.includes(file.id)) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ someFilesSelected() { return this.selectedAttachments.length > 0 && !this.allFilesSelected }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ catalogOptions() { return objectStore.getCollection('catalog').results.map(catalog => ({ id: catalog.id, label: catalog.title, })) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ registerOptions() { if (!this.selectedCatalog) { return [] @@ -1260,6 +1298,9 @@ export default { label: register.title, })) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ schemaOptions() { if (!this.selectedRegister || !this.selectedCatalog) { return [] @@ -1288,13 +1329,22 @@ export default { hasSelectedSchema() { return this.selectedSchema !== null && this.showProperties }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ allSelectionsComplete() { return this.selectedCatalog && this.selectedRegister && this.selectedSchema }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ shouldShowPublishedIcon() { return this.currentObject && this.currentObject['@self'] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ themeOptions() { const themes = objectStore.getCollection('theme').results || [] return themes.map(theme => ({ @@ -1304,6 +1354,9 @@ export default { }, // Replace the existing themeFormData with this new version themeFormData: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ get() { if (!this.formData.themes || !Array.isArray(this.formData.themes)) { return [] @@ -1315,6 +1368,9 @@ export default { return theme ? { id: theme.id, label: theme.title || `#${theme.id}` } : { id: themeId, label: themeId } }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ set(selectedThemes) { // Extract just the IDs from the selected theme objects const themeIds = selectedThemes.map(theme => typeof theme === 'object' ? theme.id : theme) @@ -1324,6 +1380,9 @@ export default { }, watch: { currentObject: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(newValue) { if (newValue) { this.initializeData() @@ -1332,6 +1391,9 @@ export default { deep: true, }, jsonData: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(newValue) { if (this.isValidJson(newValue)) { this.updateFormFromJson() @@ -1341,6 +1403,9 @@ export default { formData: { deep: true, immediate: true, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(obj) { // Create a clean copy of the form data const draft = JSON.stringify(obj, null, 2) @@ -1351,6 +1416,9 @@ export default { }, }, selectedCatalog: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(newCatalog) { // Auto-select register if there's only one if (newCatalog && this.registerOptions.length === 1) { @@ -1363,6 +1431,9 @@ export default { }, }, selectedRegister: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(newRegister) { // Auto-select schema if there's only one if (newRegister && this.schemaOptions.length === 1) { @@ -1374,6 +1445,9 @@ export default { }, }, selectedSchema: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handler(newSchema) { if (!newSchema) { this.showProperties = false @@ -1384,6 +1458,9 @@ export default { }, }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ mounted() { this.initializeData() // Fetch themes for the theme options dropdown @@ -1392,6 +1469,9 @@ export default { this.getAllTags() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getModalTitle() { // For new objects, show "Create Publication" if (this.isNewObject) { @@ -1429,6 +1509,9 @@ export default { return `${name} (${schemaName})` }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ closeModal() { // Clear state first this.activeTab = 0 @@ -1457,14 +1540,23 @@ export default { // Close modal navigationStore.setModal(null) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handleDialogClose(isOpen) { if (!isOpen) { this.closeModal() } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ proceedToProperties() { this.showProperties = true }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ initializeData() { if (!this.currentObject) { // For new objects, initialize with empty form data and auto-select if possible @@ -1538,6 +1630,9 @@ export default { this.jsonData = JSON.stringify(filtered, null, 2) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async saveObject() { this.isSaving = true this.error = null @@ -1618,6 +1713,9 @@ export default { this.isSaving = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ updateFormFromJson() { try { const parsed = JSON.parse(this.jsonData) @@ -1626,6 +1724,9 @@ export default { this.error = 'Invalid JSON format' } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isValidJson(str) { if (!str || !str.trim()) { return false @@ -1637,6 +1738,9 @@ export default { return false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ formatJSON() { try { if (this.jsonData) { @@ -1647,6 +1751,9 @@ export default { // Keep invalid JSON as-is } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isValidDate(value) { if (!value || typeof value !== 'string') return false @@ -1667,16 +1774,25 @@ export default { const date = new Date(value) return date instanceof Date && !isNaN(date) && date.getFullYear() > 1900 }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isDateTimeProperty(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] return schemaProperty && schemaProperty.type === 'string' && ['date', 'time', 'date-time'].includes(schemaProperty.format) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDateTimePropertyFormat(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] return schemaProperty?.format || 'unknown' }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ formatDateTimeValue(key, value) { if (!value) return '' @@ -1708,10 +1824,16 @@ export default { return value } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ formatValue(val) { return JSON.stringify(val, null, 2) }, getTheme, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async copyToClipboard(text) { try { await navigator.clipboard.writeText(text) @@ -1722,6 +1844,9 @@ export default { } }, // Property validation and editing methods + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyValidationClass(key, value) { // Skip @self as it's metadata if (key === '@self') { @@ -1755,6 +1880,9 @@ export default { return 'property-invalid' } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyErrorMessage(key, value) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -1795,6 +1923,9 @@ export default { getPropertyNewMessage(key) { return `Property '${key}' is defined in the schema but doesn't have a value yet. Click to add a value.` }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isPropertyEditable(key, value) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -1814,6 +1945,9 @@ export default { return true }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getEditabilityWarning(key, value) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -1828,6 +1962,9 @@ export default { return null }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handleRowClick(key, event) { // Don't select if clicking on an input or button if (event.target.tagName === 'INPUT' || event.target.tagName === 'BUTTON' || event.target.closest('.value-input-container')) { @@ -1847,6 +1984,9 @@ export default { this.selectProperty(key) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ selectProperty(key) { this.selectedProperty = key @@ -1861,6 +2001,9 @@ export default { } }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ updatePropertyValue(key, newValue) { // Ensure formData is an object before updating if (!this.formData || Array.isArray(this.formData)) { @@ -1882,11 +2025,17 @@ export default { this.$set(this.formData, key, processedValue) }, // Test method to verify Vue methods are working + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ testVueMethod(message) { if (process.env.NODE_ENV === 'development') { alert(`Vue method works: ${message}`) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ handleDateTimeUpdate(key, newValue) { // Ensure formData is an object before updating if (!this.formData || Array.isArray(this.formData)) { @@ -1937,6 +2086,9 @@ export default { this.$set(this.formData, key, processedValue) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ processDateTimeValue(key, value) { // Get schema information to determine if this is a date/time field const schemaProperties = this.getSchemaProperties() @@ -2013,6 +2165,9 @@ export default { return value } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyInputType(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -2040,6 +2195,9 @@ export default { return 'text' } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyInputComponent(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -2073,6 +2231,9 @@ export default { return 'NcTextField' } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyDisplayName(key) { // Ensure we always have a valid key if (!key || typeof key !== 'string') { @@ -2091,6 +2252,9 @@ export default { return key }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyTooltip(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -2107,16 +2271,25 @@ export default { // Fallback to property key info return `Property: ${key}` }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyMinimum(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] return schemaProperty?.minimum }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyMaximum(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] return schemaProperty?.maximum }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getPropertyStep(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -2128,6 +2301,9 @@ export default { } return undefined }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getMarkdownEditorOptions(key) { return { placeholder: this.getPropertyDisplayName(key), @@ -2163,6 +2339,9 @@ export default { }, } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ removeBordersFromEditor(editor) { try { // Get the editor container @@ -2191,6 +2370,9 @@ export default { console.warn('Could not remove borders from editor:', error) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ updateMarkdownValue(key, editorInstance) { // Get the current content from the editor as MARKDOWN let content = '' @@ -2214,6 +2396,9 @@ export default { // Update the form data this.updatePropertyValue(key, content) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDisplayValue(key, value) { if (key === 'themes') { const themes = objectStore.getCollection('theme').results || [] @@ -2284,6 +2469,9 @@ export default { // Return the value as-is for everything else return value }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDateTimeValue(key, value) { // Get the current value (either from formData or original value) const currentValue = this.formData[key] !== undefined ? this.formData[key] : value @@ -2321,6 +2509,9 @@ export default { return currentValue } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDateTimePickerValue(key, value) { // Get the current value (either from formData or original value) const currentValue = this.formData[key] !== undefined ? this.formData[key] : value @@ -2369,6 +2560,9 @@ export default { return null } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDateTimePickerType(key) { const schemaProperties = this.getSchemaProperties() const schemaProperty = schemaProperties[key] @@ -2403,6 +2597,9 @@ export default { return pickerType }, // Publish/Depublish methods + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ openPublishModal() { if (this.currentObject['@self']?.published) { this.publishDate = new Date(this.currentObject['@self'].published) @@ -2411,6 +2608,9 @@ export default { } this.showPublishModal = true }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ openDepublishModal() { if (this.currentObject['@self']?.depublished) { this.depublishDate = new Date(this.currentObject['@self'].depublished) @@ -2419,16 +2619,25 @@ export default { } this.showDepublishModal = true }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ closePublishModal() { this.showPublishModal = false this.publishDate = null this.isPublishing = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ closeDepublishModal() { this.showDepublishModal = false this.depublishDate = null this.isDepublishing = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async publishObject() { this.isPublishing = true try { @@ -2498,6 +2707,9 @@ export default { this.isPublishing = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async depublishObject() { this.isDepublishing = true try { @@ -2571,6 +2783,7 @@ export default { /** * Open a file in the Nextcloud Files app * @param {object} file - The file object to open + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ openFile(file) { const dirPath = file.path.substring(0, file.path.lastIndexOf('/')) @@ -2582,6 +2795,7 @@ export default { * Format file size for display * @param {number} bytes - The file size in bytes * @return {string} The formatted file size + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ formatFileSize(bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] @@ -2591,6 +2805,9 @@ export default { if (i === 0) return bytes + ' ' + sizes[i] return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ toggleSelectAllFiles(checked) { if (checked) { // Add all current page files to selection @@ -2605,6 +2822,9 @@ export default { this.selectedAttachments = this.selectedAttachments.filter(id => !currentPageIds.includes(id)) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ toggleFileSelection(fileId, checked) { if (checked) { if (!this.selectedAttachments.includes(fileId)) { @@ -2614,6 +2834,9 @@ export default { this.selectedAttachments = this.selectedAttachments.filter(id => id !== fileId) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async onFilesPageChanged(page) { if (!this.currentObject) return @@ -2630,6 +2853,9 @@ export default { _limit: this.filesCurrentPageSize, }, publicationData) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async onFilesPageSizeChanged(pageSize) { if (!this.currentObject) return @@ -2646,6 +2872,9 @@ export default { _limit: pageSize, }, publicationData) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async publishSelectedFiles() { if (this.selectedAttachments.length === 0) return @@ -2691,6 +2920,9 @@ export default { this.publishLoading = [] } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async depublishSelectedFiles() { if (this.selectedAttachments.length === 0) return @@ -2736,6 +2968,9 @@ export default { this.depublishLoading = [] } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async deleteSelectedFiles() { if (this.selectedAttachments.length === 0) return @@ -2780,6 +3015,9 @@ export default { this.fileIdsLoading = [] } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async publishFile(file) { try { this.publishLoading.push(file.id) @@ -2809,6 +3047,9 @@ export default { this.publishLoading = this.publishLoading.filter(id => id !== file.id) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async depublishFile(file) { try { this.depublishLoading.push(file.id) @@ -2838,6 +3079,9 @@ export default { this.depublishLoading = this.depublishLoading.filter(id => id !== file.id) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async deleteFile(file) { try { this.fileIdsLoading.push(file.id) @@ -2867,14 +3111,23 @@ export default { this.fileIdsLoading = this.fileIdsLoading.filter(id => id !== file.id) } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ editFileLabels(file) { this.editingTags = file.id this.editedTags = file.labels || [] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ cancelFileLabelEditing() { this.editingTags = null this.editedTags = [] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async getAllTags() { this.tagsLoading = true try { @@ -2895,6 +3148,9 @@ export default { this.tagsLoading = false } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ async saveTags(file, editedTags) { try { const publication = this.currentObject @@ -2942,6 +3198,9 @@ export default { } }, // Utility method to get register and schema IDs from publication object + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getRegisterSchemaIds(publication) { const registerId = typeof publication['@self'].register === 'object' ? publication['@self'].register?.id || publication['@self'].register?.uuid @@ -2952,18 +3211,30 @@ export default { return { registerId, schemaId } }, // Action button methods + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ uploadFiles() { // Open the upload files modal (same as in PublicationDetail.vue) navigationStore.setDialog('uploadFiles') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ shouldShowPublishAction(object) { if (!object || !object['@self']) return false return object['@self'].published === null || object['@self'].published === undefined }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ shouldShowDepublishAction(object) { if (!object || !object['@self']) return false return object['@self'].published !== null && object['@self'].published !== undefined }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ singlePublishObject() { if (!this.currentObject) return @@ -2977,6 +3248,9 @@ export default { // Open the mass publish dialog navigationStore.setDialog('massPublishObjects') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ singleDepublishObject() { if (!this.currentObject) return @@ -2990,6 +3264,9 @@ export default { // Open the mass depublish dialog navigationStore.setDialog('massDepublishObjects') }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ singleDeleteObject() { if (!this.currentObject) return @@ -3004,6 +3281,9 @@ export default { navigationStore.setDialog('massDeleteObject') }, // Schema handling methods + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getSchemaProperties() { let properties = {} @@ -3037,6 +3317,9 @@ export default { return properties }, // Helper method to rebuild object with schema properties after API operations + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ rebuildObjectWithSchemaProperties(apiResult) { // Start with the API result merged with current object const mergedObject = { @@ -3086,6 +3369,7 @@ export default { /** * Clean formData to ensure it's a proper object with correct property keys * @return {object} Cleaned form data object + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ cleanFormData() { const cleaned = {} @@ -3118,6 +3402,7 @@ export default { /** * Build complete object data including all schema properties * @return {object} Complete object with all properties from schema and current object + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ buildCompleteObjectData() { const schemaProperties = this.getSchemaProperties() @@ -3193,6 +3478,9 @@ export default { }, // Property dropping methods + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ canDropProperty(key, value) { // Don't show drop button for metadata properties if (key === '@self' || key === 'id') { @@ -3216,6 +3504,7 @@ export default { * Check if a property is constant or immutable * @param {string} key - Property key * @return {boolean} True if property is constant or immutable + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 */ isConstantOrImmutable(key) { const schemaProperties = this.getSchemaProperties() @@ -3248,6 +3537,9 @@ export default { return isImmutableByName }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ getDropPropertyTooltip(key) { const schemaProperties = this.getSchemaProperties() const isSchemaProperty = Object.prototype.hasOwnProperty.call(schemaProperties, key) @@ -3259,6 +3551,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ dropProperty(key) { const schemaProperties = this.getSchemaProperties() const isSchemaProperty = Object.prototype.hasOwnProperty.call(schemaProperties, key) @@ -3311,6 +3606,9 @@ export default { }, // Enhanced property validation and editing methods (from openregister version) + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-object-modals/tasks.md#task-1 + */ isValidPropertyValue(key, value, schemaProperty) { // Handle null/undefined values if (value === null || value === undefined || value === '') { diff --git a/src/navigation/Configuration.vue b/src/navigation/Configuration.vue index 881b5c2e..748c6633 100644 --- a/src/navigation/Configuration.vue +++ b/src/navigation/Configuration.vue @@ -268,6 +268,9 @@ export default { }, methods: { // We use the catalogi in the menu so lets fetch those + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-10 + */ fetchData(newPage) { this.loading = true @@ -286,6 +289,9 @@ export default { console.error(err) }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-10 + */ saveConfig() { // Simple POST request with a JSON body using fetch const requestOptions = { diff --git a/src/services/getTheme.js b/src/services/getTheme.js index d4993f00..eb09ca2c 100644 --- a/src/services/getTheme.js +++ b/src/services/getTheme.js @@ -2,6 +2,7 @@ * Get the current theme from Nextcloud * * @return {string} The current theme name + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-6 */ export function getTheme() { // Try to get theme from Nextcloud's OCA.Theming @@ -22,6 +23,7 @@ export function getTheme() { * Check if the current theme is dark * * @return {boolean} True if dark theme is active + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-6 */ export function isDarkTheme() { return getTheme() === 'dark' @@ -31,6 +33,7 @@ export function isDarkTheme() { * Get theme-specific CSS variables * * @return {object} Theme CSS variables + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-6 */ export function getThemeVariables() { const root = document.documentElement diff --git a/src/sidebars/directory/DirectorySideBar.vue b/src/sidebars/directory/DirectorySideBar.vue index e6f496d8..9baf6b73 100644 --- a/src/sidebars/directory/DirectorySideBar.vue +++ b/src/sidebars/directory/DirectorySideBar.vue @@ -162,12 +162,18 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ listingItem() { return directoryStore.listingItem }, }, watch: { checkedMetadata: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ handler(newValue, oldValue) { const metadataUrl = Object.entries(newValue)[0][0] const shouldCopyMetadata = Object.entries(newValue)[0][1] @@ -180,6 +186,9 @@ export default { deep: true, }, listingItem: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ handler(newValue, oldValue) { if (newValue !== false && metadataStore?.metaDataList) { this.loading = true @@ -190,14 +199,23 @@ export default { immediate: true, // Run the handler immediately on initialization }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ created() { metadataStore.refreshMetaDataList() this.checkMetadataSwitches() }, methods: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ openLink(url, type = '') { window.open(url, type) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ getMetadataId(metadataUrl) { let metadataId metadataStore.metaDataList.forEach((metadataItem) => { @@ -207,6 +225,9 @@ export default { }) return metadataId }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ checkMetadataSwitches() { if (Array.isArray(directoryStore?.listingItem?.metadata)) { directoryStore.listingItem.metadata.forEach((metadataUrl) => { @@ -218,6 +239,9 @@ export default { } this.loading = false }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ copyMetadata(metadataUrl) { this.loading = true fetch( @@ -239,6 +263,9 @@ export default { this.loading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ createMetadata(data) { this.loading = true data.title = 'KOPIE: ' + data.title @@ -268,6 +295,9 @@ export default { this.loading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ deleteMetadata(metadataUrl) { this.loading = true const metadataId = this.getMetadataId(metadataUrl) @@ -289,6 +319,9 @@ export default { this.loading = false }) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ synDirectroy() { this.syncLoading = true fetch( diff --git a/src/sidebars/search/SearchSideBar.vue b/src/sidebars/search/SearchSideBar.vue index fe51d177..70e7113e 100644 --- a/src/sidebars/search/SearchSideBar.vue +++ b/src/sidebars/search/SearchSideBar.vue @@ -98,18 +98,27 @@ export default { watch: { search: 'debouncedSearch', metadata: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ handler() { this.debouncedSearch() }, deep: true, }, catalogi: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ handler() { this.debouncedSearch() }, deep: true, }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-2 + */ mounted() { metadataStore.refreshMetaDataList() catalogiStore.refreshCatalogiList() diff --git a/src/store/modules/catalog.js b/src/store/modules/catalog.js index 7cd5b0d6..a56a650a 100644 --- a/src/store/modules/catalog.js +++ b/src/store/modules/catalog.js @@ -63,6 +63,7 @@ export const useCatalogStore = defineStore('catalog', { /** * Clear error + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-4 */ clearError() { this.error = null @@ -70,6 +71,7 @@ export const useCatalogStore = defineStore('catalog', { /** * Reset store state + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-4 */ reset() { this.catalogs = [] diff --git a/src/store/modules/navigation.js b/src/store/modules/navigation.js index 19870eaf..d1d5c9d9 100644 --- a/src/store/modules/navigation.js +++ b/src/store/modules/navigation.js @@ -17,18 +17,30 @@ export const useNavigationStore = defineStore('ui', { transferData: null, }), actions: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-3 + */ setSelected(selected) { this.selected = selected console.log('Active menu item set to ' + selected) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-3 + */ setSelectedOrganisatie(selectedOrganisatie) { this.selectedOrganisatie = selectedOrganisatie console.log('Active organisatie menu set to ' + selectedOrganisatie) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-3 + */ setModal(modal) { this.modal = modal console.log('Active modal set to ' + modal) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-3 + */ setDialog(dialog, properties) { this.dialog = dialog this.dialogProperties = properties || null @@ -37,6 +49,9 @@ export const useNavigationStore = defineStore('ui', { setTransferData(transferData) { this.transferData = transferData }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-3 + */ getTransferData() { const tempData = this.transferData this.transferData = null diff --git a/src/store/modules/organisatie.js b/src/store/modules/organisatie.js index f7435733..ccbc69a6 100644 --- a/src/store/modules/organisatie.js +++ b/src/store/modules/organisatie.js @@ -61,6 +61,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Fetch contactpersonen for an organisation * @param {string} organisationId - The organisation ID * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async fetchContactpersonen(organisationId) { this.loading = true @@ -103,6 +104,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Convert a contactpersoon to a user account * @param {string} contactpersoonId - The contactpersoon ID * @return {Promise} Result of conversion + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async convertToUser(contactpersoonId) { // Don't set global loading state for individual contactpersoon actions @@ -145,6 +147,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * @param {string} username - The username * @param {string} newPassword - The new password * @return {Promise} Result of password change + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async changePassword(username, newPassword) { // Don't set global loading state for individual contactpersoon actions @@ -190,6 +193,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * @param {string} username - The username * @param {Array} groups - Array of group names * @return {Promise} Result of group update + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async updateUserGroups(username, groups) { // Don't set global loading state for individual contactpersoon actions @@ -239,6 +243,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Fetch user info and available groups for a specific contactpersoon * @param {string} contactpersoonId - The contactpersoon ID * @return {Promise} User info and available groups + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async fetchUserInfo(contactpersoonId) { try { @@ -278,6 +283,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Fetch contact persons with user details for an organization * @param {string} organizationUuid - The organization UUID * @return {Promise} Array of contact persons with user details + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async fetchContactPersonsWithUserDetails(organizationUuid) { try { @@ -313,6 +319,7 @@ export const useOrganisatieStore = defineStore('organisatie', { /** * Fetch available groups for user assignment (fallback method) * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async fetchAvailableGroups() { try { @@ -346,6 +353,7 @@ export const useOrganisatieStore = defineStore('organisatie', { /** * Clear error state + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ clearError() { this.error = null @@ -353,6 +361,7 @@ export const useOrganisatieStore = defineStore('organisatie', { /** * Clear contactpersonen data + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ clearContactpersonen() { this.contactpersonen = [] @@ -362,6 +371,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Disable a user account * @param {string} contactpersoonId - The contactpersoon UUID to disable * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async disableUser(contactpersoonId) { try { @@ -386,6 +396,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Enable a user account * @param {string} contactpersoonId - The contactpersoon UUID to enable * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async enableUser(contactpersoonId) { try { @@ -410,6 +421,7 @@ export const useOrganisatieStore = defineStore('organisatie', { * Get user info for multiple contactpersonen in one request * @param {Array} contactpersoonIds - Array of contactpersoon UUIDs * @return {Promise} Bulk user info object keyed by contactpersoon ID + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-2 */ async getBulkUserInfo(contactpersoonIds) { try { diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js index 511c829a..2782aa94 100644 --- a/src/store/modules/settings.js +++ b/src/store/modules/settings.js @@ -346,6 +346,7 @@ export const useSettingsStore = defineStore('settings', { /** * Clear error + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ clearError() { this.error = null @@ -354,6 +355,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load statistics from the objects/counts endpoint * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadStatistics() { this.loadingStatistics = true @@ -393,6 +395,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load all settings from the API + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadSettings() { // Prevent multiple simultaneous calls @@ -457,6 +460,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load essential data for OpenRegister configuration dropdowns * Only loads the data needed for register/schema selection + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadOpenRegisterEssentials() { this.clearError() @@ -498,6 +502,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load Voorzieningen configuration with focused loading state + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadVoorzieningenConfigFocused() { this.loadingVoorzieningenSchemas = true @@ -519,6 +524,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load AMEF configuration with focused loading state + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadAmefConfigFocused() { this.loadingAmefSchemas = true @@ -543,6 +549,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {string} catalogLocation - The new catalog location URL * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async updateCatalogLocation(catalogLocation) { try { @@ -558,6 +565,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {number} syncTimeWindow - The new sync time window value * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async updateSyncTimeWindow(syncTimeWindow) { try { @@ -572,6 +580,7 @@ export const useSettingsStore = defineStore('settings', { * Load general configuration from focused endpoint * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadGeneralConfig() { this.loadingGeneralSettings = true @@ -601,6 +610,7 @@ export const useSettingsStore = defineStore('settings', { * Load organization synchronization configuration from focused endpoint * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadSyncConfig() { this.loadingSyncSettings = true @@ -628,6 +638,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load version information from focused endpoint + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadVersionInfo() { this.loadingVersionInfo = true @@ -649,6 +660,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load ArchiMate status from focused endpoint + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadArchiMateStatus() { this.loadingArchiMateStatus = true @@ -675,6 +687,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load object counts from focused endpoint + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadObjectCounts() { this.loadingObjectCounts = true @@ -702,6 +715,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load email configuration from focused endpoint + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadEmailConfig() { this.loadingEmailConfig = true @@ -724,6 +738,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load user groups configuration from focused endpoint + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadUserGroupsConfig() { this.loadingUserGroups = true @@ -754,6 +769,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load only user groups configuration (for individual component refresh) + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadUserGroupsOnly() { const response = await fetch('/index.php/apps/softwarecatalog/api/user-groups/config') @@ -778,6 +794,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load AMEF configuration from focused endpoint * Used in full settings load - doesn't interfere with focused loading states + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadAmefConfig() { try { @@ -798,6 +815,7 @@ export const useSettingsStore = defineStore('settings', { /** * Load Voorzieningen configuration from focused endpoint * Used in full settings load - doesn't interfere with focused loading states + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async loadVoorzieningenConfig() { try { @@ -817,6 +835,7 @@ export const useSettingsStore = defineStore('settings', { /** * Initialize configuration object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ initializeConfiguration() { // Initialize register-specific configuration @@ -848,6 +867,7 @@ export const useSettingsStore = defineStore('settings', { /** * Populate register selections using the focused endpoint configs + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ populateRegisterSelectionsFromFocused() { // Voorzieningen register @@ -878,6 +898,7 @@ export const useSettingsStore = defineStore('settings', { /** * Populate schema selections using the focused endpoint configs + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ populateSchemaSelectionsFromFocused() { const findOption = (schemaId, options) => { @@ -945,6 +966,7 @@ export const useSettingsStore = defineStore('settings', { * Import ArchiMate file with proper error handling (async approach) * @param {('speed'|'memory')} processingMode Processing strategy * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ importArchiMateFile(processingMode = 'speed') { if (!this.selectedFile) { @@ -1012,6 +1034,7 @@ export const useSettingsStore = defineStore('settings', { /** * Start status polling with more frequent initial polls + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ startStatusPolling() { if (this.statusPollingInterval) { @@ -1027,6 +1050,7 @@ export const useSettingsStore = defineStore('settings', { /** * Stop status polling + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ stopStatusPolling() { if (this.statusPollingInterval) { @@ -1042,6 +1066,7 @@ export const useSettingsStore = defineStore('settings', { * Prevents concurrent calls to avoid stacking requests * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async refreshArchiMateStatus() { if (this.isStatusPolling) { @@ -1072,6 +1097,7 @@ export const useSettingsStore = defineStore('settings', { /** * Clear ArchiMate import status * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async clearImportStatus() { try { @@ -1112,6 +1138,7 @@ export const useSettingsStore = defineStore('settings', { /** * Clear ArchiMate export status * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async clearExportStatus() { try { @@ -1155,6 +1182,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {object} register Selected register object * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ handleVoorzieningenRegisterChange(register) { if (register) { @@ -1173,6 +1201,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {object} register Selected register object * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ handleAmefRegisterChange(register) { if (register) { @@ -1190,6 +1219,7 @@ export const useSettingsStore = defineStore('settings', { * This method can be expanded to add validation logic * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ validateConfiguration() { // Configuration validation logic can be added here @@ -1199,6 +1229,7 @@ export const useSettingsStore = defineStore('settings', { * Save configuration to backend using focused endpoints * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async saveConfiguration() { try { @@ -1400,6 +1431,7 @@ export const useSettingsStore = defineStore('settings', { * Sets up the entire application configuration in one operation * * @return {Promise} Configuration result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async consolidatedAutoConfigure() { try { @@ -1442,6 +1474,7 @@ export const useSettingsStore = defineStore('settings', { * Reset auto-configuration flag and optionally schema/register keys * Calls POST /api/settings/reset-auto-config * @return {Promise} Result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async resetAutoConfig() { try { @@ -1469,6 +1502,7 @@ export const useSettingsStore = defineStore('settings', { * Force update: forced import + version sync * Calls POST /api/settings/force-update * @return {Promise} Result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async forceUpdate() { try { @@ -1495,6 +1529,7 @@ export const useSettingsStore = defineStore('settings', { * Save email settings * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async saveEmailSettings() { try { @@ -1510,6 +1545,7 @@ export const useSettingsStore = defineStore('settings', { * Test email connection * * @return {Promise} Test result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async testEmailConnection() { try { @@ -1554,6 +1590,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {string} testEmail Test email address * @return {Promise} Test result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async sendTestEmail(testEmail = '') { try { @@ -1599,6 +1636,7 @@ export const useSettingsStore = defineStore('settings', { * * @param {string} format Export format * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async exportToArchiMate(format = 'xml') { this.exporting = true @@ -1659,6 +1697,7 @@ export const useSettingsStore = defineStore('settings', { * Test ArchiMate round-trip functionality * * @return {Promise} Test result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ async testRoundTrip() { try { @@ -1696,6 +1735,7 @@ export const useSettingsStore = defineStore('settings', { /** * Cleanup method to stop polling when store is destroyed + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ cleanup() { this.stopStatusPolling() @@ -1704,6 +1744,7 @@ export const useSettingsStore = defineStore('settings', { /** * Reset store state + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-1 */ reset() { this.stopStatusPolling() diff --git a/src/store/plugins/softwarecatalogPlugin.js b/src/store/plugins/softwarecatalogPlugin.js index abdb06cd..764cb601 100644 --- a/src/store/plugins/softwarecatalogPlugin.js +++ b/src/store/plugins/softwarecatalogPlugin.js @@ -73,6 +73,7 @@ function separateResults(results) { * Softwarecatalog plugin factory. * * @return {object} Plugin definition for createObjectStore + * @spec exclude Pinia plugin installer factory — store bootstrap plumbing */ export function softwarecatalogPlugin() { return { @@ -237,6 +238,7 @@ export function softwarecatalogPlugin() { * Fetch app settings from the softwarecatalog API. * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async fetchSettings() { try { @@ -276,6 +278,7 @@ export function softwarecatalogPlugin() { * Registers each schema from the voorzieningen register. * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async initializeVoorzieningenObjectTypes() { try { @@ -305,6 +308,7 @@ export function softwarecatalogPlugin() { * * @param {string} objectType Type of object * @return {{source: string, schema: string, register: string}} Schema config + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ getSchemaConfig(objectType) { // Check registered types first @@ -357,6 +361,7 @@ export function softwarecatalogPlugin() { * @param {string} type Object type * @param {object} object Object to set as active * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async setActiveObject(type, object) { this.activeObjects = { ...this.activeObjects, [type]: object } @@ -392,6 +397,7 @@ export function softwarecatalogPlugin() { * Clear active object for type. * * @param {string} type Object type + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ clearActiveObject(type) { this.activeObjects = { ...this.activeObjects, [type]: null } @@ -415,6 +421,7 @@ export function softwarecatalogPlugin() { * * @param {object} objectItem Object with @self metadata * @return {Promise<{ok: boolean}>} Response-like object for backward compat + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async downloadObject(objectItem) { const objectId = objectItem.id || objectItem['@self']?.id @@ -448,6 +455,7 @@ export function softwarecatalogPlugin() { * @param {object} params Query parameters * @param {object|null} organisatieData Optional org-specific config * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async fetchRelatedData(type, id, dataType, params = {}, organisatieData = null) { const loadingKey = `${type}_${id}_${dataType}` @@ -527,6 +535,7 @@ export function softwarecatalogPlugin() { * @param {string|object} typeOrObject Type slug or object item * @param {object} dataOrConfig Object data or { register, schema } config * @return {Promise} Saved object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async saveObject(typeOrObject, dataOrConfig) { if (typeof typeOrObject === 'string') { @@ -601,6 +610,7 @@ export function softwarecatalogPlugin() { * @param {string|object} typeOrObject Type slug or full object * @param {string} [id] Object ID (only for new signature) * @return {Promise} Success + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async deleteObject(typeOrObject, id) { if (typeof typeOrObject === 'string' && id) { @@ -656,6 +666,7 @@ export function softwarecatalogPlugin() { * @param {string} id Object ID * @param {object} changes Object with changed properties * @return {Promise} Updated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async patchObject(type, id, changes) { this.loading = { ...this.loading, [`${type}_${id}`]: true } @@ -705,6 +716,7 @@ export function softwarecatalogPlugin() { * @param {string} type Object type * @param {string} id Object ID to copy * @return {Promise} The newly created copy + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async copyObject(type, id) { const originalObject = this.objects?.[type]?.[id] @@ -726,6 +738,7 @@ export function softwarecatalogPlugin() { * * @param {object} objectItem Object to publish * @return {Promise} Updated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async publishObject(objectItem) { const objectId = objectItem.id || objectItem['@self']?.id @@ -753,6 +766,7 @@ export function softwarecatalogPlugin() { * * @param {object} objectItem Object to depublish * @return {Promise} Updated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async depublishObject(objectItem) { const objectId = objectItem.id || objectItem['@self']?.id @@ -782,6 +796,7 @@ export function softwarecatalogPlugin() { * @param {string} [process] Process name. Defaults to null. * @param {number} [duration] Duration in seconds. Defaults to null. * @return {Promise} Updated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async lockObject(objectItem, process = null, duration = null) { const objectId = objectItem.id || objectItem['@self']?.id @@ -815,6 +830,7 @@ export function softwarecatalogPlugin() { * * @param {object} objectItem Object to unlock * @return {Promise} Updated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async unlockObject(objectItem) { const objectId = objectItem.id || objectItem['@self']?.id @@ -842,6 +858,7 @@ export function softwarecatalogPlugin() { * * @param {object} objectItem Object to validate * @return {Promise} Validated object + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async validateObject(objectItem) { const objectId = objectItem.id || objectItem['@self']?.id @@ -873,6 +890,7 @@ export function softwarecatalogPlugin() { * @param {Function} operation Per-object operation function * @param {Function} [onProgress] Progress callback. Defaults to null. * @return {Promise<{successful: Array, failed: Array}>} Results + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async _runMassOperation(objects, operation, onProgress = null) { this.clearAllObjectErrors() @@ -908,26 +926,44 @@ export function softwarecatalogPlugin() { return { successful, failed } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massPublishObjects(objects, onProgress = null) { return this._runMassOperation(objects, (obj) => this.publishObject(obj), onProgress) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massDepublishObjects(objects, onProgress = null) { return this._runMassOperation(objects, (obj) => this.depublishObject(obj), onProgress) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massDeleteObjects(objects, onProgress = null) { return this._runMassOperation(objects, (obj) => this.deleteObject(obj), onProgress) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massLockObjects(objects, process = null, duration = null, onProgress = null) { return this._runMassOperation(objects, (obj) => this.lockObject(obj, process, duration), onProgress) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massUnlockObjects(objects, onProgress = null) { return this._runMassOperation(objects, (obj) => this.unlockObject(obj), onProgress) }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ async massValidateObjects(objects, onProgress = null) { return this._runMassOperation(objects, (obj) => this.validateObject(obj), onProgress) }, @@ -940,6 +976,9 @@ export function softwarecatalogPlugin() { this.selectedObjects = objects }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ toggleSelectAllObjects() { const organisatieCollection = this.collections?.organisatie const results = Array.isArray(organisatieCollection) ? organisatieCollection : organisatieCollection?.results @@ -960,11 +999,17 @@ export function softwarecatalogPlugin() { this.objectErrors = { ...this.objectErrors, [objectId]: error } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ clearObjectError(objectId) { const { [objectId]: _, ...rest } = this.objectErrors this.objectErrors = rest }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ clearAllObjectErrors() { this.objectErrors = {} }, @@ -977,6 +1022,9 @@ export function softwarecatalogPlugin() { // Column Management // ========================================== + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ updateColumnFilter(id, enabled) { this.columnFilters = { ...this.columnFilters, [id]: enabled } @@ -993,6 +1041,9 @@ export function softwarecatalogPlugin() { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ initializeProperties(schema) { if (!schema?.properties) { this.properties = {} @@ -1011,6 +1062,9 @@ export function softwarecatalogPlugin() { this.properties = properties }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 + */ initializeColumnFilters() { const filters = {} Object.keys(this.metadata).forEach((key) => { @@ -1038,6 +1092,7 @@ export function softwarecatalogPlugin() { * @param {string} params.fileAction What to do with files * @param {string} params.relationAction What to do with relations * @return {Promise} Merge result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async mergeObjects({ register, schema, sourceObjectId, target, object, fileAction, relationAction }) { const response = await fetch( @@ -1057,6 +1112,7 @@ export function softwarecatalogPlugin() { * Fetch available mappings from the OpenRegister API. * * @return {Promise<{data: Array}>} Mappings result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ async getMappings() { const response = await fetch( @@ -1069,6 +1125,7 @@ export function softwarecatalogPlugin() { /** * Refresh the current object list by refetching all registered types. + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ refreshObjectList() { const registeredTypes = Object.keys(this.objectTypeRegistry || {}) @@ -1081,6 +1138,9 @@ export function softwarecatalogPlugin() { // State Management // ========================================== + /** + * @spec exclude generic Pinia $patch passthrough — store bootstrap plumbing + */ setState(type, { success, error }) { if (success !== undefined) { this.success = { ...this.success, [type]: success } @@ -1092,6 +1152,7 @@ export function softwarecatalogPlugin() { /** * Clear the softwarecatalog sub-resources (called by base clearAllSubResources). + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-5 */ clearSoftwarecatalog() { this.objectItem = null diff --git a/src/utils/heartbeat.js b/src/utils/heartbeat.js index f1c6ecdc..df210976 100644 --- a/src/utils/heartbeat.js +++ b/src/utils/heartbeat.js @@ -18,6 +18,7 @@ class Heartbeat { * Create a new heartbeat instance * * @param {number} interval - Heartbeat interval in milliseconds (default: 30000 = 30s) + * @spec exclude class constructor — DI/initialization only */ constructor(interval = 30000) { this.interval = interval @@ -30,6 +31,7 @@ class Heartbeat { * Start sending heartbeat requests * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ start() { if (this.isRunning) { @@ -52,6 +54,7 @@ class Heartbeat { * Stop sending heartbeat requests * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ stop() { if (!this.isRunning) { @@ -73,6 +76,7 @@ class Heartbeat { * * @private * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ async sendHeartbeat() { try { @@ -117,6 +121,7 @@ const heartbeat = new Heartbeat() * * @param {number} interval - Optional custom interval in milliseconds * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ export function startHeartbeat(interval) { if (interval) { @@ -129,6 +134,7 @@ export function startHeartbeat(interval) { * Stop heartbeat * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ export function stopHeartbeat() { heartbeat.stop() @@ -138,6 +144,7 @@ export function stopHeartbeat() { * Check if heartbeat is running * * @return {boolean} True if running + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ export function isHeartbeatRunning() { return heartbeat.running @@ -149,6 +156,7 @@ export function isHeartbeatRunning() { * @param {Function} operation - Async function to execute * @param {number} interval - Optional heartbeat interval in milliseconds * @return {Promise} Promise that resolves with the operation result + * @spec openspec/changes/retrofit-2026-05-26-fe-stores/tasks.md#task-7 */ export async function withHeartbeat(operation, interval = 30000) { try { diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 1ee2fcf1..d5507ad7 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -195,6 +195,7 @@ export default { /** * Widget definitions for CnDashboardPage * @return {Array} Widget definition array + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ widgetDefs() { return [ @@ -207,6 +208,7 @@ export default { /** * Get object statistics for the table display * @return {Array} Array of object statistics + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ objectStatistics() { const stats = [] @@ -246,6 +248,7 @@ export default { /** * Get first half of statistics for first table * @return {Array} First half of statistics + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ firstTableStats() { const stats = this.objectStatistics @@ -256,6 +259,7 @@ export default { /** * Get second half of statistics for second table * @return {Array} Second half of statistics + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ secondTableStats() { const stats = this.objectStatistics @@ -273,6 +277,7 @@ export default { /** * Load dashboard data * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ async loadDashboardData() { this.loading = true @@ -304,6 +309,7 @@ export default { * Get schema configuration for object type * @param {string} objectType - Object type slug * @return {object | null} Schema configuration + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ getSchemaConfig(objectType) { if (!objectStore.settings?.schemaConfigurations) { @@ -325,6 +331,7 @@ export default { * Get icon component for specific object type * @param {string} objectType - Object type slug * @return {object} Vue icon component + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ getIconForObjectType(objectType) { const iconMap = { @@ -348,6 +355,7 @@ export default { * Navigate to object type management page * @param {string} objectType - Object type slug to navigate to * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ navigateToObjectType(objectType) { // Handle special cases for plural routing @@ -365,6 +373,7 @@ export default { /** * Navigate to organizations page * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ navigateToOrganizations() { navigationStore.setSelected('organisaties') @@ -374,6 +383,7 @@ export default { * Navigate to configuration page - opens admin settings in new tab * @param {string} route - Route to navigate to (legacy parameter) * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ navigateToConfiguration(route) { const settingsUrl = `${window.location.protocol}//${window.location.host}/index.php/settings/admin/softwarecatalog` @@ -384,6 +394,7 @@ export default { * Format object type name for display * @param {string} objectType - The object type slug * @return {string} Formatted object type name + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ formatObjectTypeName(objectType) { // Convert camelCase/kebab-case to proper case @@ -399,6 +410,7 @@ export default { * Format date for display * @param {Date} date - Date to format * @return {string} Formatted date string + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ formatDate(date) { return date.toLocaleDateString('en-GB', { @@ -413,6 +425,7 @@ export default { /** * Refresh all data - force reload settings and all collections * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-shell-navigation/tasks.md#task-1 */ async refreshAllData() { console.info('Dashboard: Refreshing all data...') diff --git a/src/views/settings/Settings.vue b/src/views/settings/Settings.vue index f29c5f5a..f665c51c 100644 --- a/src/views/settings/Settings.vue +++ b/src/views/settings/Settings.vue @@ -194,6 +194,7 @@ export default defineComponent({ * Generates options for register selection dropdown * * @return {Array} Array of register options with label and value + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ registerOptions() { return this.settings.availableRegisters.map(register => ({ @@ -206,6 +207,7 @@ export default defineComponent({ * Determines if the selected register has schemas * * @return {boolean} True if the selected register has schemas, false otherwise + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ hasSchemas() { if (!this.selectedRegister) return false @@ -220,6 +222,7 @@ export default defineComponent({ * Returns filtered schema options, excluding those that are already used * * @return {Array} Array of available schema options + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ computedSchemaOptions() { const usedSchemaIds = Object.values(this.configuration) @@ -233,6 +236,7 @@ export default defineComponent({ * Check if catalog location has changed * * @return {boolean} True if catalog location has changed + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ catalogLocationChanged() { return this.catalogLocation !== this.originalCatalogLocation @@ -252,6 +256,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async loadSettings() { try { @@ -277,6 +282,7 @@ export default defineComponent({ /** * Initializes the configuration object based on existing settings + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ initializeConfiguration() { // Create empty configuration for each object type @@ -329,6 +335,7 @@ export default defineComponent({ /** * Automatically selects the opencatalogi register if it exists + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ autoSelectOpenCatalogiRegister() { // Look for a register with "opencatalogi" in the name @@ -367,6 +374,7 @@ export default defineComponent({ * Auto-selects schemas that match object type names * * @param {object} register - The selected register object + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ autoSelectMatchingSchemas(register) { // Only proceed if register has schemas array @@ -399,6 +407,7 @@ export default defineComponent({ * Updates schema options based on the selected register * * @param {string} registerId - The ID of the selected register + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ updateSchemaOptions(registerId) { const register = this.settings.availableRegisters.find(r => r.id.toString() === registerId) @@ -417,6 +426,7 @@ export default defineComponent({ * * @param {string} objectType - The object type to format * @return {string} The formatted title + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ formatTitle(objectType) { return objectType.charAt(0).toUpperCase() + objectType.slice(1) @@ -424,6 +434,7 @@ export default defineComponent({ /** * Handles register change event + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ handleRegisterChange() { if (this.selectedRegister) { @@ -456,6 +467,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async saveAll() { if (!this.selectedRegister || !this.hasSchemas) { @@ -498,6 +510,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async loadConfiguration() { this.loadingConfiguration = true @@ -526,6 +539,7 @@ export default defineComponent({ * * @param {string} value - New catalog location value * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ onCatalogLocationChange(value) { this.catalogLocation = value @@ -536,6 +550,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async saveCatalogLocation() { if (!this.catalogLocationChanged) { diff --git a/src/views/settings/SoftwareCatalogSettings.vue b/src/views/settings/SoftwareCatalogSettings.vue index 523a10ef..ff8b41db 100644 --- a/src/views/settings/SoftwareCatalogSettings.vue +++ b/src/views/settings/SoftwareCatalogSettings.vue @@ -135,6 +135,9 @@ export default defineComponent({ Web, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 + */ setup() { // Use the settings store return { @@ -160,6 +163,7 @@ export default defineComponent({ * Check if catalog location has changed * * @return {boolean} True if catalog location has changed + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ catalogLocationChanged() { return this.catalogLocation !== (this.store.settings.catalogLocation || '') @@ -171,6 +175,9 @@ export default defineComponent({ */ watch: { 'store.settings.catalogLocation': { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 + */ handler(newValue) { if (newValue !== undefined && newValue !== null) { this.catalogLocation = newValue @@ -179,6 +186,9 @@ export default defineComponent({ immediate: true, }, 'store.loadingGeneralSettings': { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 + */ handler(newValue, oldValue) { // When loading finishes, update the catalog location if (oldValue === true && newValue === false) { @@ -190,6 +200,7 @@ export default defineComponent({ /** * Load settings data when component is created + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async created() { await this.store.loadSettings() @@ -203,6 +214,7 @@ export default defineComponent({ * * @param {string} value - New catalog location value * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ onCatalogLocationChange(value) { this.catalogLocation = value @@ -213,6 +225,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async saveGeneralSettings() { this.savingCatalogLocation = true @@ -234,6 +247,7 @@ export default defineComponent({ * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-1 */ async refreshGeneralSettings() { try { diff --git a/src/views/settings/sections/ArchiMateImportExport.vue b/src/views/settings/sections/ArchiMateImportExport.vue index eb15bd1c..c583f42c 100644 --- a/src/views/settings/sections/ArchiMateImportExport.vue +++ b/src/views/settings/sections/ArchiMateImportExport.vue @@ -459,6 +459,9 @@ export default { Close, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 + */ setup() { return { store: settingsStore, @@ -497,6 +500,7 @@ export default { * * @param {Event} event - File input change event * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ handleFileSelect(event) { const file = event.target.files[0] @@ -513,6 +517,7 @@ export default { * * @param {number} bytes - File size in bytes * @return {string} Formatted file size string + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ formatFileSize(bytes) { if (bytes === 0) return '0 Bytes' @@ -527,6 +532,7 @@ export default { * * @param {number} seconds - Time in seconds * @return {string} Formatted time string + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ formatTime(seconds) { if (seconds < 1) { @@ -545,6 +551,7 @@ export default { * * @param {number} megabytes - Memory size in megabytes * @return {string} Formatted memory string + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ formatMemory(megabytes) { if (megabytes < 1024) { @@ -559,6 +566,7 @@ export default { * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ async importArchiMateFile() { if (!this.selectedFile) { @@ -626,6 +634,7 @@ export default { * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ async exportArchiMateFile() { this.exporting = true @@ -702,6 +711,7 @@ export default { /** * Export organization-specific ArchiMate file with enriched views + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ async exportOrgArchiMateFile() { if (!this.selectedOrganization) return @@ -776,6 +786,7 @@ export default { * Clear import results and errors * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ clearResults() { this.importResult = null @@ -786,6 +797,7 @@ export default { * Reset import state and show upload interface again * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ resetImport() { // Clear all import state @@ -805,6 +817,7 @@ export default { * Show detailed error information * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ showErrorDetails() { if (this.importResult?.statistics?.summary?.total_errors > 0) { @@ -816,6 +829,7 @@ export default { * Hide detailed error information * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ hideErrorDetails() { this.showErrors = false @@ -826,6 +840,7 @@ export default { * * @param {string} errorType - The error type to format * @return {string} Formatted error type + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ formatErrorType(errorType) { const typeMap = { @@ -847,6 +862,7 @@ export default { * * @param {string} errorMessage - Error message to check * @return {boolean} True if it's a configuration error + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ isConfigurationError(errorMessage) { return errorMessage && ( @@ -861,6 +877,7 @@ export default { * * @param {string} errorMessage - Full error message * @return {string} Clean description of the error + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ getConfigurationErrorDescription(errorMessage) { if (!errorMessage) return '' @@ -889,6 +906,7 @@ export default { * * @param {string} errorMessage - Full error message * @return {Array} Array of missing configuration items + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ getMissingConfigItems(errorMessage) { if (!errorMessage) return [] @@ -937,6 +955,7 @@ export default { * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-7 */ async loadOrganizations() { try { diff --git a/src/views/settings/sections/CronjobConfiguration.vue b/src/views/settings/sections/CronjobConfiguration.vue index 0f3f2ead..f17f2b4d 100644 --- a/src/views/settings/sections/CronjobConfiguration.vue +++ b/src/views/settings/sections/CronjobConfiguration.vue @@ -209,6 +209,7 @@ export default { * Format users for NcSelect * * @return {Array} User options for select + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ userOptions() { return this.users.map(user => ({ @@ -221,6 +222,7 @@ export default { * Format organisations for NcSelect * * @return {Array} Organisation options for select + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ organisationOptions() { return this.organisations.map(org => ({ @@ -239,6 +241,7 @@ export default { * Load all configuration data * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async loadConfig() { this.loading = true @@ -260,6 +263,7 @@ export default { * Load cronjob configurations * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async loadCronjobs() { try { @@ -290,6 +294,7 @@ export default { * Update organisation labels in cronjobs from loaded organisations * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ updateOrganisationLabels() { if (this.organisations.length === 0) return @@ -311,6 +316,7 @@ export default { * Update user labels in cronjobs from loaded users * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ updateUserLabels() { if (this.users.length === 0) return @@ -332,6 +338,7 @@ export default { * Load available users * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async loadUsers() { this.loadingUsers = true @@ -356,6 +363,7 @@ export default { * the OpenRegister organisations endpoint directly. * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async loadOrganisations() { this.loadingOrganisations = true @@ -386,6 +394,7 @@ export default { * * @param {object} job The job to check * @return {boolean} True if the job can be saved + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ canSaveJob(job) { return job.selectedUser?.value && job.selectedOrganisation?.value @@ -397,6 +406,7 @@ export default { * @param {string} jobId The job ID * @param {boolean} enabled Whether the job is enabled * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async updateJobEnabled(jobId, enabled) { const job = this.cronjobs.find(j => j.id === jobId) @@ -411,6 +421,7 @@ export default { * * @param {object} job The job to save * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async saveJobConfig(job) { this.savingJob = job.id @@ -446,6 +457,7 @@ export default { * * @param {object} job The job to run * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ async runJob(job) { if (!job.userId || !job.organisationUuid) { @@ -476,6 +488,7 @@ export default { * * @param {number} seconds Interval in seconds * @return {string} Formatted interval + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ formatInterval(seconds) { if (seconds < 60) { @@ -495,6 +508,7 @@ export default { * @param {string} app App name * @param {string} text Text to translate * @return {string} Translated text + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-5 */ t(app, text) { return text diff --git a/src/views/settings/sections/EmailConfiguration.vue b/src/views/settings/sections/EmailConfiguration.vue index 415e1b75..4cd52d2e 100644 --- a/src/views/settings/sections/EmailConfiguration.vue +++ b/src/views/settings/sections/EmailConfiguration.vue @@ -447,6 +447,9 @@ export default { StandardTabs, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 + */ setup() { return { store: settingsStore, @@ -521,11 +524,23 @@ export default { }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 + */ loading() { return this.store.loading }, emailSettings: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 + */ get() { return this.store.emailSettings }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 + */ set(value) { this.store.emailSettings = value }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 + */ canSave() { // Always allow saving for email settings return true @@ -539,6 +554,7 @@ export default { methods: { /** * Save email settings using the settings store + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async saveEmailSettings() { this.saving = true @@ -553,6 +569,7 @@ export default { /** * Test email connection using the settings store + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async testEmailConnection() { this.testingConnection = true @@ -574,6 +591,7 @@ export default { /** * Send test email using the settings store + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async sendTestEmail() { this.testingEmail = true @@ -596,6 +614,7 @@ export default { /** * Load email templates from settings store * Templates are loaded as part of the consolidated configuration + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async loadTemplates() { try { @@ -610,6 +629,7 @@ export default { /** * Get active template name + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ getActiveTemplateName() { const template = this.availableTemplates.find(t => t.key === this.activeTemplate) @@ -618,6 +638,7 @@ export default { /** * Get active template description + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ getActiveTemplateDescription() { const template = this.availableTemplates.find(t => t.key === this.activeTemplate) @@ -626,6 +647,7 @@ export default { /** * Get active template variables + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ getActiveTemplateVariables() { const template = this.availableTemplates.find(t => t.key === this.activeTemplate) @@ -644,6 +666,7 @@ export default { * * @param {string} content Description: New template content to save for the currently active template * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ updateTemplateContent(content) { this.templates[this.activeTemplate] = content @@ -654,6 +677,7 @@ export default { * * @param {string} variable Description: Variable key (e.g., 'user.email') to format as a template placeholder * @return {string} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ formatTemplateVariable(variable) { return `{{ ${variable} }}` @@ -664,6 +688,7 @@ export default { * * @param {string} variable Description: Variable key to insert (e.g., 'organization.name') * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ insertVariable(variable) { const formattedVariable = this.formatTemplateVariable(variable) @@ -674,6 +699,7 @@ export default { /** * Reset template to default * TODO: Implement template reset functionality when backend supports it + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async resetTemplate() { try { @@ -689,6 +715,7 @@ export default { /** * Save template * TODO: Implement template save functionality when backend supports it + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-4 */ async saveTemplate() { this.savingTemplate = true diff --git a/src/views/settings/sections/OpenRegisterIntegration.vue b/src/views/settings/sections/OpenRegisterIntegration.vue index feb4d022..932226c1 100644 --- a/src/views/settings/sections/OpenRegisterIntegration.vue +++ b/src/views/settings/sections/OpenRegisterIntegration.vue @@ -197,6 +197,7 @@ export default { * Provides access to the settings store * * @return {object} Setup object with store reference + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ setup() { return { @@ -218,27 +219,63 @@ export default { computed: { // Store-connected computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ loading() { return this.store.loadingOpenRegisterConfig }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ loadingRegisters() { return this.store.isLoadingRegisters }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ loadingVoorzieningenSchemas() { return this.store.isLoadingVoorzieningenSchemas }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ loadingAmefSchemas() { return this.store.isLoadingAmefSchemas }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ versionInfo() { return this.store.versionInfo }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ configuration() { return this.store.configuration }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ registerOptions() { return this.store.registerOptions }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ voorzieningenSchemaOptions() { return this.store.voorzieningenSchemaOptions }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ amefSchemaOptions() { return this.store.amefSchemaOptions }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ voorzieningenSchemas() { return this.store.voorzieningenSchemas }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ amefSchemas() { return this.store.amefSchemas }, // Dynamic list of all voorzieningen schema config entries + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ voorzieningenItems() { return [ { key: 'voorzieningen_organisatie_schema', title: 'Organisatie Schema', description: 'Schema for organizations' }, @@ -258,6 +295,9 @@ export default { }, // Dynamic list of all AMEF schema config entries + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ amefItems() { return [ { key: 'amef_element_schema', title: 'Element Schema', description: 'Schema for ArchiMate elements' }, @@ -272,11 +312,23 @@ export default { // Two-way computed properties for register selections voorzieningenRegister: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ get() { return this.store.voorzieningenRegister }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ set(value) { this.store.voorzieningenRegister = value }, }, amefRegister: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ get() { return this.store.amefRegister }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 + */ set(value) { this.store.amefRegister = value }, }, @@ -284,6 +336,7 @@ export default { * Determines if configuration can be saved * * @return {boolean} True if configuration is valid and can be saved + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ canSave() { // Check if any register is selected @@ -302,6 +355,7 @@ export default { /** * Component lifecycle - load initial data * Only loads essential data needed for register/schema dropdowns + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ async mounted() { // Load only essential data for OpenRegister configuration dropdowns @@ -320,6 +374,7 @@ export default { * * @param {object} register Selected register object * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ handleVoorzieningenRegisterChange(register) { this.store.handleVoorzieningenRegisterChange(register) @@ -331,6 +386,7 @@ export default { * * @param {object} register Selected register object * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ handleAmefRegisterChange(register) { this.store.handleAmefRegisterChange(register) @@ -341,6 +397,7 @@ export default { * Triggers validation in the store * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ validateConfiguration() { this.store.validateConfiguration() @@ -351,6 +408,7 @@ export default { * Compares current configuration with original values * * @return {boolean} True if AMEF configuration has changed + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ hasAmefConfigChanges() { if (!this.amefRegister) return false @@ -376,6 +434,7 @@ export default { * Compares current configuration with original values * * @return {boolean} True if Voorzieningen configuration has changed + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ hasVoorzieningenConfigChanges() { if (!this.voorzieningenRegister) return false @@ -407,6 +466,7 @@ export default { * Saves the current configuration to the backend * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ async saveConfiguration() { this.saving = true @@ -426,6 +486,7 @@ export default { * Reloads only essential data needed for the dropdowns * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-2 */ async refreshSettings() { try { diff --git a/src/views/settings/sections/OrganizationSynchronization.vue b/src/views/settings/sections/OrganizationSynchronization.vue index 29cf15da..e01a7038 100644 --- a/src/views/settings/sections/OrganizationSynchronization.vue +++ b/src/views/settings/sections/OrganizationSynchronization.vue @@ -381,6 +381,7 @@ export default { * Provides access to the settings store * * @return {object} Setup object with store reference + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ setup() { return { @@ -426,7 +427,13 @@ export default { computed: { // Store-connected computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 + */ loading() { return this.store.loadingSyncSettings }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 + */ settings() { return this.store.settings }, /** @@ -444,6 +451,9 @@ export default { */ watch: { 'settings.syncTimeWindow': { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 + */ handler(newValue) { if (newValue !== undefined) { this.loadSavedConfiguration() @@ -452,6 +462,9 @@ export default { immediate: true, }, 'store.loadingSyncSettings': { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 + */ handler(newValue, oldValue) { // When loading finishes, reload the configuration if (oldValue === true && newValue === false) { @@ -466,6 +479,7 @@ export default { * Load sync status when component is created * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async created() { await this.loadSavedConfiguration() @@ -477,6 +491,7 @@ export default { * Load saved time window configuration * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async loadSavedConfiguration() { try { @@ -507,6 +522,7 @@ export default { * Load synchronization status * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async loadSyncStatus() { this.loadingSyncStatus = true @@ -532,6 +548,7 @@ export default { * Perform manual synchronization * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async performManualSync() { this.performingSync = true @@ -583,6 +600,7 @@ export default { * Handle time window change * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async handleTimeWindowChange() { await this.loadSyncStatus() @@ -593,6 +611,7 @@ export default { * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async saveConfiguration() { this.savingConfig = true @@ -617,6 +636,7 @@ export default { * * @async * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async refreshConfiguration() { try { @@ -631,6 +651,7 @@ export default { * Get time window description * * @return {string} Description of the current time window + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ getTimeWindowDescription() { if (!this.selectedTimeWindow) return '' @@ -647,6 +668,7 @@ export default { * * @param {number} minutes Time window in minutes * @return {string} Formatted time window + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ formatTimeWindow(minutes) { if (!minutes || minutes === 0) return 'Full sync' @@ -660,6 +682,7 @@ export default { * * @param {number} num Number to format * @return {string} Formatted number + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ formatNumber(num) { if (!num) return '0' @@ -671,6 +694,7 @@ export default { * * @param {string} timestamp Last sync timestamp * @return {string} Formatted time + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ formatLastSyncTime(timestamp) { if (!timestamp) return 'Never' @@ -687,6 +711,7 @@ export default { * * @param {number} count Processing count * @return {string} CSS class name + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ getProcessingClass(count) { if (!count || count === 0) return 'processing-none' @@ -699,6 +724,7 @@ export default { * Perform organisation sync to voorzieningen register * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-6 */ async performOrganisationSync() { this.performingOrgSync = true diff --git a/src/views/settings/sections/StatisticsOverview.vue b/src/views/settings/sections/StatisticsOverview.vue index 8d8969e1..1e7f744b 100644 --- a/src/views/settings/sections/StatisticsOverview.vue +++ b/src/views/settings/sections/StatisticsOverview.vue @@ -266,6 +266,9 @@ export default defineComponent({ SyncIcon, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ setup() { const settingsStore = useSettingsStore() @@ -300,29 +303,47 @@ export default defineComponent({ }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ statistics() { return this.settingsStore.statistics }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ formattedStatistics() { return this.settingsStore.formattedStatistics }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ loadingStats() { return this.settingsStore.loadingStatistics }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ error() { return this.settingsStore.error }, // Get compliance count for the dialog + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ complianceCount() { const complianceStat = this.formattedStatistics.find(stat => stat.type === 'Compliancy') return complianceStat ? complianceStat.count : 0 }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 + */ async mounted() { // Load statistics when component mounts await this.refreshStatistics() @@ -331,6 +352,7 @@ export default defineComponent({ methods: { /** * Refresh statistics data + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ async refreshStatistics() { await this.settingsStore.loadStatistics() @@ -340,6 +362,7 @@ export default defineComponent({ * Format number with thousand separators * @param {number} num - Number to format * @return {string} Formatted number + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ formatNumber(num) { if (num === 0) return '0' @@ -350,6 +373,7 @@ export default defineComponent({ * Format timestamp for display * @param {number} timestamp - Unix timestamp * @return {string} Formatted date/time + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ formatTimestamp(timestamp) { if (!timestamp) return 'Unknown' @@ -359,6 +383,7 @@ export default defineComponent({ /** * Show the bulk sync dialog + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ showBulkSyncDialog() { this.showSyncDialog = true @@ -368,6 +393,7 @@ export default defineComponent({ /** * Close the bulk sync dialog + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ closeBulkSyncDialog() { this.showSyncDialog = false @@ -378,6 +404,7 @@ export default defineComponent({ /** * Reset sync state + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ resetSyncState() { this.syncProgress = { @@ -396,6 +423,7 @@ export default defineComponent({ /** * Start the bulk sync process + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ async startBulkSync() { this.bulkSyncLoading = true @@ -424,6 +452,7 @@ export default defineComponent({ /** * Perform the bulk sync API call * @return {Promise} API response + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-8 */ async performBulkSync() { const response = await fetch('/index.php/apps/softwarecatalog/api/bulk-sync-standards', { diff --git a/src/views/settings/sections/UserGroupsConfiguration.vue b/src/views/settings/sections/UserGroupsConfiguration.vue index c26b89ff..d17989ec 100644 --- a/src/views/settings/sections/UserGroupsConfiguration.vue +++ b/src/views/settings/sections/UserGroupsConfiguration.vue @@ -318,6 +318,7 @@ export default { * Provides access to the settings store * * @return {object} Setup object with store reference + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ setup() { return { @@ -345,22 +346,44 @@ export default { computed: { // Store-connected computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ loading() { return this.store.loading }, genericUserGroups: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ get() { return this.store.genericUserGroups || [] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ set(value) { this.store.genericUserGroups = value }, }, organizationAdminGroups: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ get() { return this.store.organizationAdminGroups || [] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ set(value) { this.store.organizationAdminGroups = value }, }, superUserGroups: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ get() { return this.store.superUserGroups || [] }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ set(value) { this.store.superUserGroups = value }, }, /** * Check if there are any unsaved changes + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ hasChanges() { // This would ideally check if the current values differ from the saved values @@ -371,6 +394,9 @@ export default { }, }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 + */ mounted() { // ensure we fetch latest user groups when opening the section this.loadAllGroups().catch(() => {}) @@ -382,6 +408,7 @@ export default { * * @param {string} group Group name * @return {string} Group description + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ getGroupDescription(group) { const descriptions = { @@ -399,6 +426,7 @@ export default { * Add a new generic user group * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ addGroup() { this.genericUserGroups.push('') @@ -409,6 +437,7 @@ export default { * * @param {number} index Index of the group to remove * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ removeGroup(index) { this.genericUserGroups.splice(index, 1) @@ -420,6 +449,7 @@ export default { * @param {number} index Index of the group to update * @param {string} value New group name * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ updateGroupName(index, value) { this.genericUserGroups[index] = value @@ -430,6 +460,7 @@ export default { * Add a new organization admin group * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ addOrganizationAdminGroup() { this.organizationAdminGroups.push('') @@ -440,6 +471,7 @@ export default { * * @param {number} index Index of the group to remove * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ removeOrganizationAdminGroup(index) { this.organizationAdminGroups.splice(index, 1) @@ -451,6 +483,7 @@ export default { * @param {number} index Index of the group to update * @param {string} value New group name * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ updateOrganizationAdminGroupName(index, value) { this.organizationAdminGroups[index] = value @@ -461,6 +494,7 @@ export default { * Add a new super user group * * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ addSuperUserGroup() { this.superUserGroups.push('') @@ -471,6 +505,7 @@ export default { * * @param {number} index Index of the group to remove * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ removeSuperUserGroup(index) { this.superUserGroups.splice(index, 1) @@ -482,6 +517,7 @@ export default { * @param {number} index Index of the group to update * @param {string} value New group name * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ updateSuperUserGroupName(index, value) { this.superUserGroups[index] = value @@ -489,6 +525,7 @@ export default { /** * Save all groups using the centralized settings store + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ async saveAllGroups() { try { @@ -503,6 +540,7 @@ export default { /** * Load all groups from the store + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-3 */ async loadAllGroups() { try { diff --git a/src/views/settings/sections/VersionInformation.vue b/src/views/settings/sections/VersionInformation.vue index 52330575..2ea7061e 100644 --- a/src/views/settings/sections/VersionInformation.vue +++ b/src/views/settings/sections/VersionInformation.vue @@ -249,6 +249,7 @@ export default { * Provides access to the settings store * * @return {object} Setup object with store reference + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 */ setup() { return { @@ -271,7 +272,13 @@ export default { computed: { // Store-connected computed properties + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 + */ loadingVersionInfo() { return this.store.loadingVersionInfo }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 + */ versionInfo() { return this.store.versionInfo }, }, @@ -280,6 +287,7 @@ export default { * Perform consolidated auto-configuration using the settings store * * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 */ async consolidatedAutoConfigure() { this.autoConfiguring = true @@ -305,6 +313,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 + */ async handleResetAutoConfig() { this.autoConfiguring = true this.resetAutoConfigResult = null @@ -321,6 +332,9 @@ export default { } }, + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-settings-ui/tasks.md#task-9 + */ async handleForceUpdate() { this.autoConfiguring = true this.consolidatedResult = null diff --git a/src/views/widgets/ConceptOrganisatiesWidget.vue b/src/views/widgets/ConceptOrganisatiesWidget.vue index b75cca62..1e683147 100644 --- a/src/views/widgets/ConceptOrganisatiesWidget.vue +++ b/src/views/widgets/ConceptOrganisatiesWidget.vue @@ -80,6 +80,9 @@ export default { } }, computed: { + /** + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-6 + */ items() { return objectStore.getCollection('organisatie').results .filter((item) => item.status?.toLowerCase() === 'concept') @@ -99,6 +102,7 @@ export default { * Handle accepting an organisatie (change status to actief) * @param {object} item - The organisatie item to accept * @return {void} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-6 */ async onAccept(item) { this.processingIds.push(item.id) @@ -114,6 +118,7 @@ export default { /** * Fetch the organisatie data * @return {Promise} + * @spec openspec/changes/retrofit-2026-05-26-fe-organizations/tasks.md#task-6 */ async fetchData() { this.loading = true