From a013d478e0371db18edd889168a22ebf63bfc766 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 16 Feb 2026 09:57:28 +0100 Subject: [PATCH 1/2] Automatische versies --- .../ModuleComplianceSubscriber.php | 16 +- lib/Service/ModuleVersionService.php | 163 +++++++++++++++++ lib/Service/SettingsService.php | 1 + lib/Settings/softwarecatalogus_register.json | 172 ++++++++++++++++-- 4 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 lib/Service/ModuleVersionService.php diff --git a/lib/EventListener/ModuleComplianceSubscriber.php b/lib/EventListener/ModuleComplianceSubscriber.php index 8eabf3e0..fb21f0df 100644 --- a/lib/EventListener/ModuleComplianceSubscriber.php +++ b/lib/EventListener/ModuleComplianceSubscriber.php @@ -19,6 +19,7 @@ namespace OCA\SoftwareCatalog\EventListener; use OCA\SoftwareCatalog\Service\ModuleComplianceService; +use OCA\SoftwareCatalog\Service\ModuleVersionService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -109,7 +110,7 @@ public function handle(Event $event): void // Handle module compliance update $moduleComplianceService = $this->container->get(ModuleComplianceService::class); $moduleComplianceService->handleModuleComplianceUpdate($object); - + $logger->info('ModuleComplianceSubscriber: Successfully processed module compliance update', [ 'objectId' => $objectId, 'timestamp' => date('Y-m-d H:i:s') @@ -123,5 +124,18 @@ public function handle(Event $event): void 'trace' => $e->getTraceAsString() ]); } + + // Ensure the module has at least one version (default 1.0.0). + try { + $moduleVersionService = $this->container->get(ModuleVersionService::class); + $moduleVersionService->ensureDefaultVersion($object); + } catch (\Exception $e) { + $logger->error('ModuleComplianceSubscriber: Failed to ensure default module version', [ + 'objectId' => $objectId, + 'exception' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } } } diff --git a/lib/Service/ModuleVersionService.php b/lib/Service/ModuleVersionService.php new file mode 100644 index 00000000..2998b61e --- /dev/null +++ b/lib/Service/ModuleVersionService.php @@ -0,0 +1,163 @@ + + * @copyright 2024 Conduction B.V. + * @license AGPL-3.0-or-later https://www.gnu.org/licenses/agpl-3.0.html + * @version 1.0.0 + * @link https://github.com/ConductionNL/SoftwareCatalog + */ + +declare(strict_types=1); + +namespace OCA\SoftwareCatalog\Service; + +use OCA\OpenRegister\Service\ObjectService; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * Service for ensuring modules always have at least one version. + * + * @category Service + * @package OCA\SoftwareCatalog\Service + */ +class ModuleVersionService +{ + /** + * Constructor for ModuleVersionService + * + * @param ContainerInterface $container The DI container + * @param SettingsService $settingsService The settings service + * @param LoggerInterface $logger The logger instance + */ + public function __construct( + private readonly ContainerInterface $container, + private readonly SettingsService $settingsService, + private readonly LoggerInterface $logger + ) { + } + + /** + * Ensures a module has at least one version. + * + * Queries moduleVersie objects linked to this module. If none exist, + * creates a default version with versie "1.0.0" using the module's + * name and short description. + * + * @param object $moduleObject The module object entity + * + * @return void + */ + public function ensureDefaultVersion(object $moduleObject): void + { + $moduleUuid = $moduleObject->getUuid(); + + $this->logger->info('ModuleVersionService: Checking if module has versions', [ + 'moduleUuid' => $moduleUuid, + ]); + + try { + $objectService = $this->getObjectService(); + if ($objectService === null) { + $this->logger->error('ModuleVersionService: ObjectService not available'); + return; + } + + // Get schema and register IDs. + $moduleVersieSchemaId = $this->settingsService->getSchemaIdForObjectType('moduleVersie'); + $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); + $registerId = $voorzieningenConfig['register'] ?? null; + + if ($moduleVersieSchemaId === null || $registerId === null) { + $this->logger->warning('ModuleVersionService: moduleVersie schema or register not configured', [ + 'moduleVersieSchemaId' => $moduleVersieSchemaId, + 'registerId' => $registerId, + ]); + return; + } + + // Query moduleVersie objects where module == this module's UUID. + $query = [ + '@self' => [ + 'schema' => (int) $moduleVersieSchemaId, + 'register' => (int) $registerId, + ], + 'module' => $moduleUuid, + ]; + $existingVersions = $objectService->searchObjects( + query: $query, + _rbac: false, + _multitenancy: false + ); + + $versionCount = is_array($existingVersions) ? count($existingVersions) : 0; + + if ($versionCount > 0) { + $this->logger->info('ModuleVersionService: Module already has versions, skipping', [ + 'moduleUuid' => $moduleUuid, + 'versionCount' => $versionCount, + ]); + return; + } + + // No versions exist — create a default 1.0.0 version. + $moduleData = $moduleObject->getObject(); + $moduleName = $moduleData['voorkeurnaam'] ?? $moduleData['naam'] ?? 'Onbekende applicatie'; + $moduleDescription = $moduleData['beschrijvingKort'] ?? ''; + + $versionData = [ + 'module' => $moduleUuid, + 'versie' => '1.0.0', + 'beschrijvingKort' => $moduleDescription, + 'beschrijvingLang' => '', + 'status' => 'in gebruik', + ]; + + $savedVersion = $objectService->saveObject( + object: $versionData, + register: (int) $registerId, + schema: (int) $moduleVersieSchemaId, + _rbac: false, + _multitenancy: false + ); + + $this->logger->info('ModuleVersionService: Created default version 1.0.0', [ + 'moduleUuid' => $moduleUuid, + 'moduleName' => $moduleName, + 'versionUuid' => $savedVersion->getUuid(), + ]); + } catch (\Exception $e) { + $this->logger->error('ModuleVersionService: Failed to ensure default version', [ + 'moduleUuid' => $moduleUuid, + 'exception' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + } + + /** + * Get the object service from the DI container. + * + * @return ObjectService|null The object service or null if not available + */ + private function getObjectService(): ?ObjectService + { + try { + return $this->container->get('OCA\OpenRegister\Service\ObjectService'); + } catch (\Exception $e) { + $this->logger->error('ModuleVersionService: Failed to get ObjectService', [ + 'exception' => $e->getMessage(), + ]); + return null; + } + } +} diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index d1b3acb5..91381b0c 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -715,6 +715,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int $voorzieningenKeyMap = [ 'module' => 'module_schema', 'compliancy' => 'compliancy_schema', + 'moduleVersie' => 'moduleVersie_schema', ]; // Only check voorzieningen config if object type exists in the key map diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index feb1f27d..5d6aa64c 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -3,7 +3,7 @@ "info": { "title": "Software Catalog Register", "description": "Register containing AMEF and Voorzieningen schemas for the VNG Software Catalog application. This configuration includes schemas for applications, services, organizations, and compliance tracking.", - "version": "2.0.6" + "version": "2.1.0" }, "x-openregister": { "type": "application", @@ -56,10 +56,31 @@ "slug": "home", "contents": [ { + "id": 1, "type": "RichText", - "id": "bs2ccjxkup", "data": { - "content": "

Welkom bij de Softwarecatalogus

Ontdek en vergelijk software voor de Nederlandse overheid. Deze catalogus biedt inzicht in beschikbare softwareoplossingen, leveranciers en compliance.

" + "content": "

Over de Softwarecatalogus

" + } + }, + { + "id": 2, + "type": "RichText", + "data": { + "content": "

De Softwarecatalogus biedt een overzicht van software die wordt gebruikt binnen de Nederlandse gemeenten. Leveranciers en gemeenten werken hierin samen aan het (door)ontwikkelen van procesmatige en technische standaarden.

Op deze website vindt u informatie over softwarepakketten, leveranciers, standaarden en het gebruik daarvan door gemeenten. Zo willen we transparant zijn over de softwaremarkt voor de overheid.

" + } + }, + { + "id": 3, + "type": "RichText", + "data": { + "content": "" + } + }, + { + "id": 4, + "type": "RichText", + "data": { + "content": "

Meer weten over de Softwarecatalogus?

" } } ], @@ -68,7 +89,7 @@ "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", "register": "publication", "schema": "page", - "version": "0.0.3" + "version": "0.0.4" } }, { @@ -483,6 +504,138 @@ "version": "0.0.1" } } + ], + "glossary": [ + { + "title": "Addendum", + "summary": "Aanvullende afspraak bij het convenant met nadere detaillering voor één standaard", + "description": "Voor elke op het convenant aanvullende afspraak die nadere detaillering behoeft, worden losse addenda (aanvullingen) aan het convenant gehangen. In een addendum wordt voor 1 standaard een realisatietermijn afgesproken. Leveranciers streven ernaar de betreffende standaard tijdig en juist in de softwareproducten in te bouwen, te implementeren en te laten gebruiken door gemeenten.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Addendum", + "keywords": ["Addenda"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Convenant VNG", + "summary": "Algemene samenwerkingsafspraken met leveranciers, gebruikersverenigingen en samenwerkingsverbanden", + "description": "Hierin zijn algemene samenwerkingsafspraken vastgelegd met leveranciers, gebruikersverenigingen, en samenwerkingsverbanden die voor gemeenten software inkopen. De samenwerking is gericht op het gezamenlijk (door)ontwikkelen van procesmatige en technische standaarden o.a. door vakmatig kennis te delen.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Convenant%20VNG", + "keywords": ["VNG-convenant", "convenant"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Eindproduct-standaard", + "summary": "Standaard waarbij functionaliteit, services en rolverdeling scherp gedefinieerd zijn", + "description": "Hierbij zijn de functionaliteit, de (web)services en rolverdeling tussen systemen gedetailleerd en scherp gedefinieerd. Juiste toepassing is preventief goed testbaar en daardoor is de voorspelbaarheid op goed functioneren hoog.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Eindproduct-standaard", + "keywords": ["Eindproduct-standaarden"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Gegevensstandaard", + "summary": "Afspraken over meervoudig gebruik en uitwisseling van gegevens", + "description": "Om ervoor te zorgen dat de uitgewisselde gegevens juist zijn en op de juiste manier worden gebruikt, met de juiste betekenis, dienen er zeer goede afspraken gemaakt te worden. De afspraken over het meervoudig gebruik van gegevens en de (gemeentelijke) gegevensuitwisseling zijn vastgelegd in gegevensstandaarden. Ook hiervoor geldt dat de leverancier dient aan te geven op welke wijze hieraan wordt voldaan.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Gegevensstandaard", + "keywords": ["Gegevens-standaarden", "Gegevensstandaarden"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Grondstof-standaard", + "summary": "Standaard die specialistische kennis en uitgebreide specificaties vereist", + "description": "Hierbij moeten n\u00f3g meer dan bij half-fabrikaten specificaties en activiteiten worden uitgevoerd om interoperabiliteit te kunnen realiseren. Dit vergt specialistische kennis.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Grondstof-standaard", + "keywords": ["Grondstof-standaarden"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Halffabrikaat-standaard", + "summary": "Standaard waarbij de opdrachtgever aanvullende specificaties moet toevoegen", + "description": "Hierbij dient de opdrachtgever aanvullende specificaties toe te voegen.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Halffabrikaat-standaard", + "keywords": ["Halffabrikaat-standaarden"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Leverancier", + "summary": "Aanbieder van standaard-softwarepakketten voor gemeentelijke taken", + "description": "Aanbieders van standaard-software(pakketten) voor gemeentelijke taken. Alleen leveranciers die het convenant met VNG ondertekend hebben, mogen hun gegevens in de Softwarecatalogus zetten.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Leverancier", + "keywords": ["Leveranciers"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Referentiecomponent", + "summary": "Verbinding tussen softwareaanbod van leveranciers en softwaregebruik van gemeenten", + "description": "De verbinding tussen softwareaanbod leveranciers en softwaregebruik gemeenten. Een softwarepakket kan functionaliteit bieden voor \u00e9\u00e9n of meerdere referentiecomponenten, daarmee krijgt u een globaal beeld van de functionaliteit.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Referentiecomponent", + "keywords": ["Referentiecomponenten"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "SaaS", + "summary": "Software as a Service: applicatie aangeboden vanuit de cloud door een leverancier", + "description": "Bij Software as a Service maakt een gemeente of samenwerking gebruik van een applicatie die door een leverancier vanuit de cloud wordt aangeboden. De gemeente of samenwerking heeft g\u00e9\u00e9n toegang tot de onderliggende infrastructuur en software, oftewel het technisch beheer wordt geheel door de leverancier verzorgd.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#SaaS", + "keywords": ["Software as a Service"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + }, + { + "title": "Standaard", + "summary": "Norm of set van eisen waaraan gemeentelijke software moet voldoen", + "description": "Norm of set van eisen/voorwaarden/afspraken waaraan (gemeentelijke) software moet voldoen. (Open) Standaarden zijn opgenomen die voor gemeenten van belang zijn voor interne en externe applicatiekoppelingen, gegevensmanagement, bedrijfsvoering en digitale dienstverlening. Standaarden kunnen als producten worden getypeerd in eindproducten, halffabrikaten, grondstoffen en gegevensstandaarden. Deze typering is een indicatie wat de benodigde inspanning is om op basis van de standaard tot een werkende koppeling te komen. VNG adviseert gemeenten bij voorkeur functioneel passende eindproduct-standaarden te gebruiken en pas daarna (in aflopende prioriteit) halffabrikaat- en grondstof-standaarden, en als laatste maatwerk toe te passen.", + "externalLink": "https://www.softwarecatalogus.nl/lexicon#Standaard", + "keywords": ["Standaarden"], + "@self": { + "configuration": "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/ConductionNL/opencatalogi/blob/master/apps-extra/opencatalogi/lib/Settings/publication_register_magic.json", + "register": "publication", + "schema": "glossary", + "version": "0.0.1" + } + } ] } } @@ -992,7 +1145,7 @@ "slug": "dienst", "title": "Dienst", "description": "Een specifiek aanbod van een dienst op een of meerdere applicaties door een leverancier", - "version": "0.1.4", + "version": "0.1.5", "summary": "", "icon": "Handshake", "required": [ @@ -1069,13 +1222,6 @@ "maxLength": 500, "example": "https://dienst.voorbeeld.nl" }, - "status": { - "description": "De status van dit aanbod", - "type": "string", - "default": "Concept", - "example": "Bijvoorbeeld: Concept", - "title": "status" - }, "contactpersoon": { "description": "Selecteer de contactpersoon voor deze dienst", "type": "object", @@ -3300,7 +3446,7 @@ }, "configuration": { "autoPublish": false, - "objectNameField": "naam", + "objectNameField": "{{ moduleA }} {{ gegevensuitwisselingRichting | map: AnaarB=→, BnaarA=←, bi-directioneel=↔ }} {{ moduleB }}", "objectSummaryField": "beschrijvingKort", "objectDescriptionField": "beschrijvingLang" } From 7c5b5a704fde3070d30ae7c808155d18d45c6240 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 16 Feb 2026 13:01:00 +0100 Subject: [PATCH 2/2] Loking into user contactpersoon sync And prepare final go on archimate --- __pycache__/compare_archimate.cpython-38.pyc | Bin 0 -> 10206 bytes compare_archimate.py | 436 ++ lib/AppInfo/Application.php | 5 + lib/Controller/ContactpersonenController.php | 12 +- .../UserProfileUpdatedEventListener.php | 196 + lib/Service/ArchiMateExportService.php | 157 +- lib/Service/ArchiMateImportService.php | 137 +- lib/Service/ContactpersoonService.php | 61 +- .../ContactPersonHandler.php | 12 +- lib/Settings/softwarecatalogus_register.json | 30 + .../softwarecatalogus_register_old.json | 5435 ----------------- test_archimate_unified.sh | 672 +- 12 files changed, 1506 insertions(+), 5647 deletions(-) create mode 100644 __pycache__/compare_archimate.cpython-38.pyc create mode 100644 compare_archimate.py create mode 100644 lib/EventListener/UserProfileUpdatedEventListener.php delete mode 100644 lib/Settings/softwarecatalogus_register_old.json diff --git a/__pycache__/compare_archimate.cpython-38.pyc b/__pycache__/compare_archimate.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c10b01dbe096dfe9240c564d0e42fd90afb05388 GIT binary patch literal 10206 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 RckDmcX6;3L%AU2^{{mN`RUiNW literal 0 HcmV?d00001 diff --git a/compare_archimate.py b/compare_archimate.py new file mode 100644 index 00000000..116cf265 --- /dev/null +++ b/compare_archimate.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +""" +ArchiMate XML Semantic Comparison Tool + +Compares two ArchiMate 3.x XML files for structural equivalence: +- Model identifier and name +- Element counts, identifiers, and types +- Relationship counts, identifiers, types, and source/target +- View counts and identifiers +- Organization item counts +- PropertyDefinition counts, identifiers, and names +- Type distribution matching + +Usage: + python3 compare_archimate.py [--json] + +Exit codes: + 0 = all checks passed + 1 = one or more checks failed + 2 = error (file not found, parse error, etc.) +""" + +import sys +import json as json_module +import xml.etree.ElementTree as ET +from collections import Counter + +NS = {"am": "http://www.opengroup.org/xsd/archimate/3.0/"} +XSI = "http://www.w3.org/2001/XMLSchema-instance" + + +def parse_archimate(filepath): + """Parse an ArchiMate XML file and extract structural info.""" + tree = ET.parse(filepath) + root = tree.getroot() + + info = { + "model_id": root.get("identifier", ""), + "model_name": "", + "elements": {}, + "relationships": {}, + "views": {}, + "organizations": [], + "property_definitions": {}, + } + + # Model name + name_el = root.find("am:name", NS) + if name_el is not None: + info["model_name"] = (name_el.text or "").strip() + + # Elements + for el in root.findall(".//am:elements/am:element", NS): + eid = el.get("identifier", "") + etype = el.get(f"{{{XSI}}}type", "") + if eid: + info["elements"][eid] = etype + + # Relationships + for rel in root.findall(".//am:relationships/am:relationship", NS): + rid = rel.get("identifier", "") + rtype = rel.get(f"{{{XSI}}}type", "") + source = rel.get("source", "") + target = rel.get("target", "") + if rid: + info["relationships"][rid] = { + "type": rtype, + "source": source, + "target": target, + } + + # Views (diagrams) + for view in root.findall(".//am:views/am:diagrams/am:view", NS): + vid = view.get("identifier", "") + vname_el = view.find("am:name", NS) + vname = (vname_el.text or "").strip() if vname_el is not None else "" + if vid: + info["views"][vid] = vname + + # Organizations — count all elements recursively. + # ArchiMate organizations use with optional identifierRef (element references) + # or