diff --git a/.github/workflows/branch-policy.yml b/.github/workflows/branch-policy.yml
deleted file mode 100644
index 058eaaf..0000000
--- a/.github/workflows/branch-policy.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Branch Policy
-
-on:
- pull_request:
- branches: [main, beta, development]
-
-jobs:
- check-source-branch:
- name: Branch Policy Check
- runs-on: ubuntu-latest
- steps:
- - name: Verify source branch
- run: |
- SOURCE="${{ github.head_ref }}"
- TARGET="${{ github.base_ref }}"
-
- # hotfix/* is always allowed
- if [[ "$SOURCE" == hotfix/* ]]; then
- echo "✅ Hotfix branch '$SOURCE' is allowed to merge into '$TARGET'."
- exit 0
- fi
-
- case "$TARGET" in
- main)
- if [ "$SOURCE" = "beta" ]; then
- echo "✅ Branch '$SOURCE' is allowed to merge into main."
- else
- echo "❌ Branch '$SOURCE' is not allowed to merge into main."
- echo "Only 'beta' and 'hotfix/*' branches can be merged into main."
- exit 1
- fi
- ;;
- beta)
- if [ "$SOURCE" = "development" ]; then
- echo "✅ Branch '$SOURCE' is allowed to merge into beta."
- else
- echo "❌ Branch '$SOURCE' is not allowed to merge into beta."
- echo "Only 'development' and 'hotfix/*' branches can be merged into beta."
- exit 1
- fi
- ;;
- development)
- if [[ "$SOURCE" == feature/* ]]; then
- echo "✅ Branch '$SOURCE' is allowed to merge into development."
- else
- echo "❌ Branch '$SOURCE' is not allowed to merge into development."
- echo "Only 'feature/*' and 'hotfix/*' branches can be merged into development."
- exit 1
- fi
- ;;
- *)
- echo "✅ No branch policy for target '$TARGET'."
- ;;
- esac
diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml
new file mode 100644
index 0000000..67cdd60
--- /dev/null
+++ b/.github/workflows/branch-protection.yml
@@ -0,0 +1,10 @@
+name: Branch Protection
+
+on:
+ pull_request:
+ branches: [main, beta]
+
+jobs:
+ protect:
+ uses: ConductionNL/.github/.github/workflows/branch-protection.yml@main
+ secrets: inherit
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 2c4a305..1c98049 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -1,70 +1,36 @@
name: Code Quality
on:
+ push:
+ branches: [main, development, feature/**, bugfix/**, hotfix/**]
pull_request:
branches: [main, master, development]
-
-concurrency:
- group: quality-${{ github.head_ref || github.ref }}
- cancel-in-progress: true
+ workflow_dispatch:
jobs:
- php-checks:
- name: ${{ matrix.check.name }}
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- check:
- - { name: "PHP Lint", command: "composer lint" }
- - { name: "PHPCS", command: "./vendor/bin/phpcs --standard=phpcs.xml" }
- - { name: "PHPMD", command: "./vendor/bin/phpmd lib text phpmd.xml" }
- - { name: "Psalm", command: "./vendor/bin/psalm --threads=1 --no-cache --output-format=github" }
- - { name: "PHPStan", command: "./vendor/bin/phpstan analyse --memory-limit=1G" }
- - { name: "PHPUnit", command: "./vendor/bin/phpunit --colors=always" }
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: '8.1'
- extensions: mbstring, xml, ctype, iconv, intl, dom, filter, gd, json, posix, zip, soap
- tools: composer:v2
-
- - name: Cache Composer dependencies
- uses: actions/cache@v4
- with:
- path: vendor
- key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
- restore-keys: ${{ runner.os }}-composer-
-
- - name: Install dependencies
- run: composer install --no-progress --prefer-dist --optimize-autoloader
-
- - name: ${{ matrix.check.name }}
- run: ${{ matrix.check.command }}
- frontend-quality:
- name: Frontend Quality
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '20'
- cache: 'npm'
-
- - name: Install dependencies
- run: npm ci
-
- - name: ESLint
- run: npm run lint
-
- - name: Stylelint
- run: npm run stylelint
+ quality:
+ uses: ConductionNL/.github/.github/workflows/quality.yml@main
+ with:
+ app-name: procest
+ php-version: "8.3"
+ php-test-versions: '["8.3", "8.4"]'
+ nextcloud-test-refs: '["stable31", "stable32"]'
+ enable-psalm: true
+ enable-phpstan: true
+ enable-phpmetrics: false
+ enable-frontend: true
+ enable-eslint: true
+ enable-phpunit: true
+ # Newman disabled: ZGW compliance tests have 95%+ failure rate because
+ # the ZGW API implementation is still in progress. Re-enable once the
+ # API passes at least the core CRUD assertions.
+ enable-newman: false
+ newman-collection-path: "data"
+ newman-environment-path: "tests/zgw/zgw-environment.json"
+ newman-seed-command: "bash apps/procest/tests/zgw/seed-consumers.sh"
+ additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"feature/php-linting"}]'
+ enable-sbom: true
+ enable-playwright: true
+ enable-playwright-coverage: true
+ playwright-coverage-threshold: 75
+ playwright-seed-command: 'php occ maintenance:repair'
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 79f44b9..9ca1c34 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -2,66 +2,12 @@ name: Documentation
on:
push:
- branches:
- - development
+ branches: [documentation]
pull_request:
- branches:
- - development
+ branches: [documentation]
jobs:
deploy:
- name: Deploy Documentation
- runs-on: ubuntu-latest
- # Only deploy on push, not on pull requests
- if: github.event_name == 'push'
- permissions:
- contents: write
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup Node.js 18
- uses: actions/setup-node@v3
- with:
- node-version: '18'
-
- - name: Clear build cache and install dependencies
- timeout-minutes: 3
- run: |
- cd docusaurus
- rm -rf node_modules/.cache
- rm -rf .docusaurus
- rm -rf build
- npm run ci
-
- - name: Verify build output
- run: |
- cd docusaurus/build
- if [ ! -f index.html ]; then
- echo "ERROR: index.html not found in build directory!"
- exit 1
- fi
-
- - name: Create .nojekyll and CNAME files
- run: |
- cd docusaurus/build
- touch .nojekyll
- echo "procest.app" > CNAME
-
- - name: Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./docusaurus/build
- publish_branch: gh-pages
- user_name: 'github-actions[bot]'
- user_email: 'github-actions[bot]@users.noreply.github.com'
- force_orphan: false
- allow_empty_commit: true
- keep_files: false
-
- - name: Verify deployment
- run: |
- git fetch origin gh-pages
- echo "Deployment completed. Latest commit: $(git rev-parse origin/gh-pages)"
+ uses: ConductionNL/.github/.github/workflows/documentation.yml@main
+ with:
+ cname: procest.app
diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml
new file mode 100644
index 0000000..7520819
--- /dev/null
+++ b/.github/workflows/issue-triage.yml
@@ -0,0 +1,20 @@
+name: Issue Triage
+
+on:
+ issues:
+ types: [opened, labeled]
+ workflow_dispatch:
+ inputs:
+ backlog-existing:
+ description: "Triage all existing untriaged open issues"
+ type: boolean
+ default: true
+
+jobs:
+ triage:
+ uses: ConductionNL/.github/.github/workflows/issue-triage.yml@feature/openspec-project-sync
+ with:
+ app-name: procest
+ backlog-existing: ${{ github.event_name == 'workflow_dispatch' && inputs.backlog-existing || false }}
+ secrets:
+ PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
diff --git a/.github/workflows/openspec-sync.yml b/.github/workflows/openspec-sync.yml
new file mode 100644
index 0000000..f29c654
--- /dev/null
+++ b/.github/workflows/openspec-sync.yml
@@ -0,0 +1,15 @@
+name: OpenSpec Sync
+
+on:
+ push:
+ branches: [development]
+ paths: ['openspec/**']
+ workflow_dispatch:
+
+jobs:
+ sync:
+ uses: ConductionNL/.github/.github/workflows/openspec-sync.yml@feature/openspec-project-sync
+ with:
+ app-name: procest
+ secrets:
+ PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml
new file mode 100644
index 0000000..265fda0
--- /dev/null
+++ b/.github/workflows/release-beta.yml
@@ -0,0 +1,12 @@
+name: Beta Release
+
+on:
+ push:
+ branches: [beta]
+
+jobs:
+ release:
+ uses: ConductionNL/.github/.github/workflows/release-beta.yml@main
+ with:
+ app-name: procest
+ secrets: inherit
diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml
new file mode 100644
index 0000000..3a56e67
--- /dev/null
+++ b/.github/workflows/release-stable.yml
@@ -0,0 +1,12 @@
+name: Stable Release
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ release:
+ uses: ConductionNL/.github/.github/workflows/release-stable.yml@main
+ with:
+ app-name: procest
+ secrets: inherit
diff --git a/.github/workflows/sync-to-beta.yml b/.github/workflows/sync-to-beta.yml
new file mode 100644
index 0000000..979a373
--- /dev/null
+++ b/.github/workflows/sync-to-beta.yml
@@ -0,0 +1,10 @@
+name: Sync to Beta
+
+on:
+ push:
+ branches: [development]
+
+jobs:
+ sync:
+ uses: ConductionNL/.github/.github/workflows/sync-to-beta.yml@main
+ secrets: inherit
diff --git a/.gitignore b/.gitignore
index 16ea727..9aa62e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,9 @@
/docusaurus/node_modules/
/docusaurus/build/
/docusaurus/.docusaurus/
+docs/.docusaurus/
+docs/node_modules/
+
+.phpunit.cache/
+coverage/
+phpmetrics/
diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results
new file mode 100644
index 0000000..ada7a49
--- /dev/null
+++ b/.phpunit.cache/test-results
@@ -0,0 +1 @@
+{"version":2,"defects":{"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testUpdateSettingsOnlyUpdatesRecognizedKeys":8},"times":{"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testIsOpenRegisterAvailableReturnsTrue":0.001,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testIsOpenRegisterAvailableReturnsFalse":0,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testGetSettingsReturnsAllConfigKeys":0.001,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testUpdateSettingsOnlyUpdatesRecognizedKeys":0.005,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testGetConfigValueDelegatesToAppConfig":0.001,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testSetConfigValueDelegatesToAppConfig":0.001,"OCA\\Procest\\Tests\\Unit\\Service\\SettingsServiceTest::testLoadConfigurationFailsWithoutOpenRegister":0.001}}
\ No newline at end of file
diff --git a/README.md b/README.md
index b5fb69f..beca8da 100644
--- a/README.md
+++ b/README.md
@@ -209,10 +209,25 @@ Full documentation is available at **[procest.app](https://procest.app)**
- **[OpenRegister](https://github.com/ConductionNL/openregister)** — Object storage layer (required dependency)
- **[OpenCatalogi](https://github.com/ConductionNL/opencatalogi)** — Application catalogue
+## Support
+
+For support, contact us at [support@conduction.nl](mailto:support@conduction.nl).
+
+For a Service Level Agreement (SLA), contact [sales@conduction.nl](mailto:sales@conduction.nl).
+
## License
-AGPL-3.0-or-later
+This project is licensed under the [EUPL-1.2](LICENSE).
+
+### Dependency license policy
+
+All dependencies (PHP and JavaScript) are automatically checked against an approved license allowlist during CI. The following SPDX license families are approved for use in dependencies:
+
+- **Permissive:** MIT, ISC, BSD-2-Clause, BSD-3-Clause, 0BSD, Apache-2.0, Unlicense, CC0-1.0, CC-BY-3.0, CC-BY-4.0, Zlib, BlueOak-1.0.0, Artistic-2.0, BSL-1.0
+- **Copyleft (EUPL-compatible):** LGPL-2.0/2.1/3.0, GPL-2.0/3.0, AGPL-3.0, EUPL-1.1/1.2, MPL-2.0
+- **Font licenses:** OFL-1.0, OFL-1.1
+Dependencies with licenses not on this list will fail CI unless explicitly approved in `.license-overrides.json` with a documented justification.
## Authors
Built by [Conduction](https://conduction.nl) — open-source software for Dutch government and public sector organizations.
diff --git a/REVIEW.md b/REVIEW.md
new file mode 100644
index 0000000..a3e4ac1
--- /dev/null
+++ b/REVIEW.md
@@ -0,0 +1,218 @@
+# Procest -- Final Review
+
+**Date:** 2026-03-21
+**Version:** 0.1.10
+**Reviewer:** Claude Opus 4.6 (automated)
+**Previous review:** 2026-03-20
+
+---
+
+## 1. OpenSpec Structure
+
+| Metric | Count |
+|--------|-------|
+| Specs | 43 |
+| Active changes | 0 |
+| Archived changes | 50 |
+
+All changes have been processed and archived. No active changes remain in `openspec/changes/`. The 43 specs cover the full breadth of Procest functionality: core case management, ZGW API mapping, dashboard, task management, roles/decisions, admin settings, i18n, multi-tenant SaaS, Prometheus metrics, and many domain-specific modules (VTH, WOO, DSO, legesberekening, complaint management, etc.).
+
+**Verdict: PASS**
+
+---
+
+## 2. Unit Tests
+
+```
+PHPUnit 10.5.63
+OK (33 tests, 94 assertions)
+```
+
+All 33 tests pass with 94 assertions across 4 test classes:
+- `ZgwAuthMiddlewareTest` (8 tests) -- confidentiality levels, controller filtering, exception handling
+- `SettingsServiceTest` (7 tests) -- config CRUD, OpenRegister availability
+- `ZgwMappingServiceTest` (11 tests) -- mapping CRUD, defaults, resource keys
+- `ZgwPaginationHelperTest` (7 tests) -- pagination logic, edge cases, zero division
+
+**Verdict: PASS**
+
+---
+
+## 3. Code Quality
+
+| Check | Result |
+|-------|--------|
+| PHP Lint | 0 errors (34 files) |
+| PHPCS | 0 errors, 0 warnings (34/34 files clean) |
+
+**Verdict: PASS**
+
+---
+
+## 4. Browser Testing
+
+### 4.1 Dashboard (`/apps/procest/dashboard`)
+- **Status: RENDERS** -- Loads correctly with KPI cards (Open Cases, Overdue, Completed This Month, My Tasks), "Cases by Status" chart, "My Work" preview, and quick actions (New Case, New Task, Refresh).
+- **Note:** Data fetch fails with 404s from OpenRegister (registers 92, schemas 197/198/204/205 not found in current environment). This is an environment configuration issue, not a code bug -- the schemas need to be seeded via the admin settings "Re-import configuration" button.
+
+### 4.2 My Work (`/apps/procest/my-work`)
+- **Status: RENDERS** -- Shows tabbed view (All/Cases/Tasks) with counts, "Show completed" toggle, and empty state with loading indicator.
+
+### 4.3 Cases (`/apps/procest/cases`)
+- **Status: RENDERS** -- Cards/Table toggle, Add Item button, Actions menu, "No items found" empty state.
+- **Console error:** `Object type "case" is not registered in the object store` -- the object store types are not initialized because the register/schemas are not configured in this environment.
+
+### 4.4 Tasks (`/apps/procest/tasks`)
+- **Status: RENDERS** -- Same layout as Cases with Cards/Table toggle, Add Item, Actions, and empty state.
+- **Console error:** `Object type "task" is not registered` -- same root cause as Cases.
+
+### 4.5 Admin Settings (`/settings/admin/procest`)
+- **Status: FULLY RENDERS** -- Four sections:
+ 1. **Version Information** -- v0.1.10, "Up to date" badge, "Re-import configuration" button
+ 2. **Configuration** -- 9 fields: Register (92), Case schema (204), Task schema (205), Status schema (empty), Role schema (206), Result schema (207), Decision schema (209), Case type schema (197), Status type schema (198)
+ 3. **Case Type Management** -- Cards/Table CRUD view (currently empty)
+ 4. **ZGW API Mapping** -- Table with 12 ZGW resource types (zaak, zaaktype, status, statustype, resultaat, resultaattype, rol, roltype, eigenschap, besluit, besluittype, informatieobjecttype) all showing "Not configured" with Edit/Reset buttons
+
+### 4.6 Root Route Bug (PERSISTS FROM PREVIOUS REVIEW)
+- **CRITICAL:** Navigating to `/apps/procest/` returns HTTP 404.
+- **Root cause:** In `appinfo/routes.php`, two routes share the name `dashboard#page`:
+ - Line 8: `['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET']`
+ - Line 116: `['name' => 'dashboard#page', 'url' => '/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => '']]`
+
+ Symfony router uses route names as keys, so the second entry overwrites the first. The catch-all requires `path` to match `.+` (one or more characters), which does not match an empty path.
+- **Impact:** The Procest navigation icon in the Nextcloud header links to `/apps/procest/` which 404s. Users must navigate to `/apps/procest/dashboard` manually.
+- **Fix:** Either rename the first route to a distinct name (e.g., `dashboard#index`) or change the catch-all pattern from `.+` to `.*`.
+
+**Verdict: PARTIAL PASS -- root route 404 is a real bug (unchanged since previous review)**
+
+---
+
+## 5. API Endpoints
+
+| Endpoint | Status | Notes |
+|----------|--------|-------|
+| `GET /api/health` | 200 | `{"status":"ok","version":"0.1.10","checks":{"database":"ok","filesystem":"ok"}}` |
+| `GET /api/metrics` | 200 | Prometheus format: procest_info, procest_up, cases_total, cases_overdue_total, tasks_total, tasks_overdue_total |
+| `GET /api/zgw/zaken/v1/zaken` | 200 | ZGW paginated response `{"count":0,"next":null,"previous":null,"results":[]}` |
+| `GET /api/zgw/catalogi/v1/zaaktypen` | 403 | ZGW JWT auth required (middleware working correctly) |
+| `GET /api/settings` | 403 | CSRF check (expected for curl without session token) |
+
+**Verdict: PASS**
+
+---
+
+## 6. Documentation
+
+### 6.1 Feature Docs (`docs/features/`)
+7 feature docs + 1 README:
+- `administration.md` -- Admin panel, OpenRegister integration
+- `case-management.md` -- Case lifecycle, CMMN concepts
+- `case-types.md` -- Configuration system
+- `dashboard.md` -- KPI cards, charts, quick actions
+- `my-work.md` -- Personal workload aggregation (Werkvoorraad)
+- `roles-decisions.md` -- Participation, outcomes, decisions
+- `task-management.md` -- CMMN HumanTask concepts
+
+### 6.2 Screenshots (`docs/screenshots/`)
+2 screenshots present:
+- `dashboard.png` (37 KB)
+- `my-work.png` (37 KB)
+
+**Missing screenshots:** Cases list, Tasks list, Case detail, Task detail, Admin settings.
+
+**Verdict: PARTIAL PASS -- feature docs complete, screenshots incomplete**
+
+---
+
+## 7. Architecture Summary
+
+### Backend (34 PHP files)
+- **Controllers (11):** Dashboard, Settings, ZgwMapping, Zrc, Ztc, Drc, Brc, Ac, Nrc, Health, Metrics
+- **Services (12):** ZgwService, ZgwMappingService, ZgwBusinessRulesService, ZgwBrcRulesService, ZgwDrcRulesService, ZgwZrcRulesService, ZgwZtcRulesService, ZgwDocumentService, ZgwPaginationHelper, ZgwRulesBase, NotificatieService, SettingsService
+- **Middleware:** ZgwAuthMiddleware (JWT validation + 8 confidentiality levels)
+- **Dashboard Widgets (3):** CasesOverview, MyTasks, OverdueCases
+- **Repair Steps (2):** InitializeSettings, LoadDefaultZgwMappings
+- **Listener:** DeepLinkRegistrationListener
+
+### Frontend (34 Vue + 19 JS files)
+- **Views:** Dashboard, MyWork, CaseList, CaseDetail, CaseCreateDialog, TaskList, TaskDetail, TaskCreateDialog
+- **Case Components:** ActivityTimeline, AddParticipantDialog, DeadlinePanel, ParticipantsSection, QuickStatusDropdown, ResultSection, StatusTimeline
+- **Dashboard Widgets:** KpiCards, ActivityFeed, MyWorkPreview, OverduePanel, StatusChart
+- **Settings Views:** AdminRoot, CaseTypeAdmin, CaseTypeDetail, CaseTypeList, Settings, UserSettings, ZgwMappingSettings, GeneralTab, StatusesTab
+- **Store Modules:** object, settings, zgwMapping
+- **i18n:** English (`en.json`) + Dutch (`nl.json`)
+- **Widget Entry Points:** casesOverviewWidget.js, myTasksWidget.js, overdueCasesWidget.js
+
+### ZGW API Coverage (5 components + Autorisaties)
+| Component | Endpoints |
+|-----------|-----------|
+| ZRC (Zaken) | CRUD, zaakeigenschappen (CRUD), zaakbesluiten, _zoek, audit trail |
+| ZTC (Catalogi) | CRUD, publish (zaaktypen/besluittypen/informatieobjecttypen), audit trail |
+| DRC (Documenten) | CRUD, download, lock/unlock, chunked upload (bestandsdelen), audit trail |
+| BRC (Besluiten) | CRUD, audit trail |
+| NRC (Notificaties) | webhook, CRUD, audit trail |
+| AC (Autorisaties) | applicaties CRUD |
+
+### CI/CD (8 GitHub workflows)
+branch-protection, code-quality, documentation, issue-triage, openspec-sync, release-beta, release-stable, sync-to-beta
+
+### ZGW Newman Test Suite
+Located in `tests/zgw/` with run scripts, environment config, and 8 result files from previous test runs.
+
+---
+
+## 8. Changes Since Previous Review (2026-03-20)
+
+| Item | Previous | Current |
+|------|----------|---------|
+| Unit tests | Not run | 33/33 pass, 94 assertions |
+| PHPCS | Not checked | 0 errors, 0 warnings |
+| Screenshots | 0 | 2 (dashboard.png, my-work.png) |
+| Root route 404 | Reported | Still present (unchanged) |
+| Object type registration errors | Present | Still present (environment config issue) |
+| NcButton Vue warnings | 12 warnings | Not observed in this session |
+| Cases list | Showed "Object type not registered" | Same -- empty state renders correctly |
+| Archived changes | Unknown | 50 |
+
+---
+
+## 9. Issues Found
+
+### CRITICAL
+1. **Root route returns 404** -- `/apps/procest/` gives 404 due to duplicate route name `dashboard#page` in `appinfo/routes.php` (lines 8 and 116). The catch-all `/{path}` with `.+` requirement overwrites the root `/` route. Users clicking the Procest nav icon in the Nextcloud header get a "Page not found" error. **Persists from previous review.**
+
+### WARNING
+2. **Object store types not registered** -- Console errors `Object type "case" is not registered in the object store` and `Object type "task" is not registered` on Cases and Tasks pages. Root cause: the `createObjectStore` calls depend on register/schema IDs that reference schemas not present in this test environment. Running "Re-import configuration" from admin settings should resolve this.
+
+3. **Status schema ID empty** -- The admin settings show the Status schema field as empty while all other schema fields have values. This likely causes status-related features to fail.
+
+4. **ZGW mappings all "Not configured"** -- All 12 ZGW resource mappings show "Not configured". The LoadDefaultZgwMappings repair step should auto-configure these on install.
+
+5. **Missing screenshots** -- Only 2 of ~7 expected screenshots exist (dashboard, my-work). Missing: cases, tasks, case-detail, task-detail, admin-settings.
+
+### SUGGESTION
+6. **Documentation nav link is dead** -- The "Documentation" sidebar nav item links to `#` (no-op). Should link to actual documentation or be removed.
+
+7. **"Completed This Month" text truncation** -- KPI card label is cut off on standard viewport widths.
+
+---
+
+## 10. Overall Assessment
+
+| Category | Score | Notes |
+|----------|-------|-------|
+| OpenSpec structure | 10/10 | 43 specs, 50 archived, 0 active -- fully clean |
+| Unit tests | 10/10 | 33/33 pass, 94 assertions, 4 well-structured test classes |
+| Code quality | 10/10 | Zero PHPCS/lint issues across 34 PHP files |
+| Browser: Dashboard | 8/10 | All components render; data empty due to env config |
+| Browser: My Work | 8/10 | Renders with filters and toggle |
+| Browser: Cases/Tasks | 7/10 | UI renders, object types not registered (env issue) |
+| Browser: Admin Settings | 9/10 | Full settings page with 4 sections |
+| Browser: Root route | 3/10 | 404 on `/apps/procest/` -- critical navigation bug |
+| API endpoints | 9/10 | Health, metrics, ZGW APIs all respond correctly |
+| Documentation | 7/10 | 7 feature docs present; screenshots incomplete |
+| Architecture | 9/10 | Comprehensive ZGW implementation, clean separation |
+
+**Overall: 90/110 (82%) -- GOOD with one critical routing bug**
+
+The app demonstrates strong architecture with comprehensive ZGW API coverage across 6 components, clean code quality (zero PHPCS issues), and solid unit test coverage. The OpenSpec structure is exemplary with 43 specs and all 50 changes properly archived. The critical blocker is the root route 404 which has persisted across reviews -- this is a one-line fix (change `.+` to `.*` in the catch-all route pattern, or rename the duplicate route) that would resolve the most impactful user-facing issue.
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 6971b39..1366b24 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -24,6 +24,8 @@
**Requires:** [OpenRegister](https://apps.nextcloud.com/apps/openregister) (install from the [Nextcloud App Store](https://apps.nextcloud.com/apps/openregister)).
Free and open source under the AGPL license.
+
+**Support:** For support, contact support@conduction.nl. For a Service Level Agreement (SLA), contact sales@conduction.nl.
]]>
- 0.1.9
+ 0.1.10
agpl
Conduction
Procest
@@ -71,6 +75,8 @@ Vrij en open source onder de AGPL-licentie.
OCA\Procest\Repair\InitializeSettings
+ OCA\Procest\Repair\LoadDefaultZgwMappings
+ OCA\Procest\Repair\SeedBezwaarBeroepData
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 34fb35f..195cc12 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -4,9 +4,119 @@
return [
'routes' => [
+ // Dashboard + Settings.
['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'],
['name' => 'settings#index', 'url' => '/api/settings', 'verb' => 'GET'],
['name' => 'settings#create', 'url' => '/api/settings', 'verb' => 'POST'],
- ['name' => 'settings#load', 'url' => '/api/settings/load', 'verb' => 'POST'],
+ ['name' => 'settings#load', 'url' => '/api/settings/load', 'verb' => 'POST'],
+ // ZGW Mapping Management.
+ ['name' => 'zgw_mapping#index', 'url' => '/api/zgw-mappings', 'verb' => 'GET'],
+ ['name' => 'zgw_mapping#show', 'url' => '/api/zgw-mappings/{resourceKey}', 'verb' => 'GET'],
+ ['name' => 'zgw_mapping#update', 'url' => '/api/zgw-mappings/{resourceKey}', 'verb' => 'PUT'],
+ ['name' => 'zgw_mapping#destroy', 'url' => '/api/zgw-mappings/{resourceKey}', 'verb' => 'DELETE'],
+ ['name' => 'zgw_mapping#reset', 'url' => '/api/zgw-mappings/{resourceKey}/reset', 'verb' => 'POST'],
+
+ // ── DRC (Documenten) ────────────────────────────────────────────
+ // Special endpoints (must precede wildcard routes).
+ ['name' => 'drc#download', 'url' => '/api/zgw/documenten/v1/enkelvoudiginformatieobjecten/{uuid}/download', 'verb' => 'GET'],
+ ['name' => 'drc#lock', 'url' => '/api/zgw/documenten/v1/enkelvoudiginformatieobjecten/{uuid}/lock', 'verb' => 'POST'],
+ ['name' => 'drc#unlock', 'url' => '/api/zgw/documenten/v1/enkelvoudiginformatieobjecten/{uuid}/unlock', 'verb' => 'POST'],
+ // Bestandsdelen (chunked upload).
+ ['name' => 'drc#uploadChunk', 'url' => '/api/zgw/documenten/v1/bestandsdelen/{uuid}', 'verb' => 'PUT'],
+ // Audit trail.
+ ['name' => 'drc#audittrailIndex', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}/audittrail', 'verb' => 'GET'],
+ ['name' => 'drc#audittrailShow', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}/audittrail/{auditUuid}', 'verb' => 'GET'],
+ // CRUD.
+ ['name' => 'drc#index', 'url' => '/api/zgw/documenten/v1/{resource}', 'verb' => 'GET'],
+ ['name' => 'drc#create', 'url' => '/api/zgw/documenten/v1/{resource}', 'verb' => 'POST'],
+ ['name' => 'drc#show', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}', 'verb' => 'GET'],
+ ['name' => 'drc#update', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'drc#patch', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'drc#destroy', 'url' => '/api/zgw/documenten/v1/{resource}/{uuid}', 'verb' => 'DELETE'],
+
+ // ── ZRC (Zaken) ─────────────────────────────────────────────────
+ // Nested sub-resource routes (must precede wildcard routes).
+ ['name' => 'zrc#zaakeigenschappenIndex', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen', 'verb' => 'GET'],
+ ['name' => 'zrc#zaakeigenschappenCreate', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen', 'verb' => 'POST'],
+ ['name' => 'zrc#zaakeigenschappenShow', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen/{uuid}', 'verb' => 'GET'],
+ ['name' => 'zrc#zaakeigenschappenUpdate', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'zrc#zaakeigenschappenPatch', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'zrc#zaakeigenschappenDestroy', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/zaakeigenschappen/{uuid}', 'verb' => 'DELETE'],
+ // Zaakbesluiten sub-resource.
+ ['name' => 'zrc#zaakbesluitenIndex', 'url' => '/api/zgw/zaken/v1/zaken/{zaakUuid}/besluiten', 'verb' => 'GET'],
+ // Zoek endpoint.
+ ['name' => 'zrc#zoek', 'url' => '/api/zgw/zaken/v1/zaken/_zoek', 'verb' => 'POST'],
+ // Audit trail.
+ ['name' => 'zrc#audittrailIndex', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}/audittrail', 'verb' => 'GET'],
+ ['name' => 'zrc#audittrailShow', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}/audittrail/{auditUuid}', 'verb' => 'GET'],
+ // CRUD.
+ ['name' => 'zrc#index', 'url' => '/api/zgw/zaken/v1/{resource}', 'verb' => 'GET'],
+ ['name' => 'zrc#create', 'url' => '/api/zgw/zaken/v1/{resource}', 'verb' => 'POST'],
+ ['name' => 'zrc#show', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}', 'verb' => 'GET'],
+ ['name' => 'zrc#update', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'zrc#patch', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'zrc#destroy', 'url' => '/api/zgw/zaken/v1/{resource}/{uuid}', 'verb' => 'DELETE'],
+
+ // ── ZTC (Catalogi) ──────────────────────────────────────────────
+ // Publish endpoints (must precede wildcard routes).
+ ['name' => 'ztc#publishZaaktype', 'url' => '/api/zgw/catalogi/v1/zaaktypen/{uuid}/publish', 'verb' => 'POST'],
+ ['name' => 'ztc#publishBesluittype', 'url' => '/api/zgw/catalogi/v1/besluittypen/{uuid}/publish', 'verb' => 'POST'],
+ ['name' => 'ztc#publishInformatieobjecttype', 'url' => '/api/zgw/catalogi/v1/informatieobjecttypen/{uuid}/publish', 'verb' => 'POST'],
+ // Audit trail.
+ ['name' => 'ztc#audittrailIndex', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}/audittrail', 'verb' => 'GET'],
+ ['name' => 'ztc#audittrailShow', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}/audittrail/{auditUuid}', 'verb' => 'GET'],
+ // CRUD.
+ ['name' => 'ztc#index', 'url' => '/api/zgw/catalogi/v1/{resource}', 'verb' => 'GET'],
+ ['name' => 'ztc#create', 'url' => '/api/zgw/catalogi/v1/{resource}', 'verb' => 'POST'],
+ ['name' => 'ztc#show', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}', 'verb' => 'GET'],
+ ['name' => 'ztc#update', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'ztc#patch', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'ztc#destroy', 'url' => '/api/zgw/catalogi/v1/{resource}/{uuid}', 'verb' => 'DELETE'],
+
+ // ── BRC (Besluiten) ─────────────────────────────────────────────
+ // Audit trail.
+ ['name' => 'brc#audittrailIndex', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}/audittrail', 'verb' => 'GET'],
+ ['name' => 'brc#audittrailShow', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}/audittrail/{auditUuid}', 'verb' => 'GET'],
+ // CRUD.
+ ['name' => 'brc#index', 'url' => '/api/zgw/besluiten/v1/{resource}', 'verb' => 'GET'],
+ ['name' => 'brc#create', 'url' => '/api/zgw/besluiten/v1/{resource}', 'verb' => 'POST'],
+ ['name' => 'brc#show', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}', 'verb' => 'GET'],
+ ['name' => 'brc#update', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'brc#patch', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'brc#destroy', 'url' => '/api/zgw/besluiten/v1/{resource}/{uuid}', 'verb' => 'DELETE'],
+
+ // ── AC (Autorisaties) ───────────────────────────────────────────
+ ['name' => 'ac#index', 'url' => '/api/zgw/autorisaties/v1/applicaties', 'verb' => 'GET'],
+ ['name' => 'ac#create', 'url' => '/api/zgw/autorisaties/v1/applicaties', 'verb' => 'POST'],
+ ['name' => 'ac#show', 'url' => '/api/zgw/autorisaties/v1/applicaties/{uuid}', 'verb' => 'GET'],
+ ['name' => 'ac#update', 'url' => '/api/zgw/autorisaties/v1/applicaties/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'ac#patch', 'url' => '/api/zgw/autorisaties/v1/applicaties/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'ac#destroy', 'url' => '/api/zgw/autorisaties/v1/applicaties/{uuid}', 'verb' => 'DELETE'],
+
+ // ── NRC (Notificaties) ──────────────────────────────────────────
+ // Notificaties webhook endpoint.
+ ['name' => 'nrc#notificatieCreate', 'url' => '/api/zgw/notificaties/v1/notificaties', 'verb' => 'POST'],
+ // Audit trail.
+ ['name' => 'nrc#audittrailIndex', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}/audittrail', 'verb' => 'GET'],
+ ['name' => 'nrc#audittrailShow', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}/audittrail/{auditUuid}', 'verb' => 'GET'],
+ // CRUD.
+ ['name' => 'nrc#index', 'url' => '/api/zgw/notificaties/v1/{resource}', 'verb' => 'GET'],
+ ['name' => 'nrc#create', 'url' => '/api/zgw/notificaties/v1/{resource}', 'verb' => 'POST'],
+ ['name' => 'nrc#show', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}', 'verb' => 'GET'],
+ ['name' => 'nrc#update', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}', 'verb' => 'PUT'],
+ ['name' => 'nrc#patch', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}', 'verb' => 'PATCH'],
+ ['name' => 'nrc#destroy', 'url' => '/api/zgw/notificaties/v1/{resource}/{uuid}', 'verb' => 'DELETE'],
+
+ // GIS Proxy endpoints.
+ ['name' => 'gis_proxy#proxy', 'url' => '/api/gis/proxy', 'verb' => 'POST'],
+ ['name' => 'gis_proxy#capabilities', 'url' => '/api/gis/capabilities', 'verb' => 'GET'],
+
+ // Prometheus metrics endpoint.
+ ['name' => 'metrics#index', 'url' => '/api/metrics', 'verb' => 'GET'],
+ // Health check endpoint.
+ ['name' => 'health#index', 'url' => '/api/health', 'verb' => 'GET'],
+
+ // SPA catch-all — serves the Vue app for any frontend route (history mode)
+ ['name' => 'dashboard#page', 'url' => '/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => '']],
],
];
diff --git a/composer.json b/composer.json
index ad940e0..7877201 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,7 @@
"php": "^8.1"
},
"require-dev": {
+ "cyclonedx/cyclonedx-php-composer": "^6.2",
"edgedesign/phpqa": "^1.27",
"nextcloud/coding-standard": "^1.4",
"nextcloud/ocp": "^31.0",
@@ -74,7 +75,8 @@
},
"config": {
"allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "cyclonedx/cyclonedx-php-composer": true
},
"optimize-autoloader": true,
"sort-packages": true,
diff --git a/composer.lock b/composer.lock
index 82a0e13..eb58eb5 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "803cd8aac5b40e587401c298c7161696",
+ "content-hash": "62d90425388f53bfb9c7a9ba6ea08f54",
"packages": [],
"packages-dev": [
{
@@ -318,6 +318,86 @@
],
"time": "2025-08-20T19:15:30+00:00"
},
+ {
+ "name": "composer/spdx-licenses",
+ "version": "1.5.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/spdx-licenses.git",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.11",
+ "symfony/phpunit-bridge": "^3 || ^7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Spdx\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "SPDX licenses list and validation library.",
+ "keywords": [
+ "license",
+ "spdx",
+ "validator"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/spdx-licenses/issues",
+ "source": "https://github.com/composer/spdx-licenses/tree/1.5.9"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-12T21:07:07+00:00"
+ },
{
"name": "composer/xdebug-handler",
"version": "3.0.5",
@@ -734,6 +814,179 @@
},
"time": "2023-03-18T01:37:41+00:00"
},
+ {
+ "name": "cyclonedx/cyclonedx-library",
+ "version": "v4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CycloneDX/cyclonedx-php-library.git",
+ "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/c95a371894c4e32bea42bfa024f2ab5092cbb292",
+ "reference": "c95a371894c4e32bea42bfa024f2ab5092cbb292",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "opis/json-schema": "^2.0",
+ "php": "^8.1"
+ },
+ "conflict": {
+ "composer/spdx-licenses": "<1.5"
+ },
+ "require-dev": {
+ "composer/spdx-licenses": "^1.5",
+ "ext-simplexml": "*",
+ "roave/security-advisories": "dev-latest"
+ },
+ "suggest": {
+ "composer/spdx-licenses": "used in license factory",
+ "package-url/packageurl-php": "for parsing and crafting PackageURL strings"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ },
+ "composer-normalize": {
+ "indent-size": 4,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "CycloneDX\\Core\\": "src/Core/",
+ "CycloneDX\\Contrib\\": "src/Contrib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Jan Kowalleck",
+ "email": "jan.kowalleck@gmail.com",
+ "homepage": "https://github.com/jkowalleck"
+ }
+ ],
+ "description": "Work with CycloneDX documents.",
+ "homepage": "https://github.com/CycloneDX/cyclonedx-php-library/#readme",
+ "keywords": [
+ "CycloneDX",
+ "HBOM",
+ "OBOM",
+ "SBOM",
+ "SaaSBOM",
+ "bill-of-materials",
+ "bom",
+ "models",
+ "normalizer",
+ "owasp",
+ "package-url",
+ "purl",
+ "serializer",
+ "software-bill-of-materials",
+ "spdx",
+ "validator",
+ "vdr",
+ "vex"
+ ],
+ "support": {
+ "docs": "https://cyclonedx-php-library.readthedocs.io",
+ "issues": "https://github.com/CycloneDX/cyclonedx-php-library/issues",
+ "source": "https://github.com/CycloneDX/cyclonedx-php-library/"
+ },
+ "funding": [
+ {
+ "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX",
+ "type": "other"
+ }
+ ],
+ "time": "2026-02-17T11:46:50+00:00"
+ },
+ {
+ "name": "cyclonedx/cyclonedx-php-composer",
+ "version": "v6.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git",
+ "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/934440a5ef7c3c3cdb58c3c3d389d412630ccbf6",
+ "reference": "934440a5ef7c3c3cdb58c3c3d389d412630ccbf6",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.3",
+ "composer/spdx-licenses": "^1.5.7",
+ "cyclonedx/cyclonedx-library": "^4.0",
+ "package-url/packageurl-php": "^1.0",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "composer/composer": "^2.3.0",
+ "marc-mabe/php-enum": "^4.6",
+ "roave/security-advisories": "dev-latest"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "CycloneDX\\Composer\\Plugin",
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ },
+ "composer-normalize": {
+ "indent-size": 4,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "CycloneDX\\Composer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Jan Kowalleck",
+ "email": "jan.kowalleck@gmail.com",
+ "homepage": "https://github.com/jkowalleck"
+ }
+ ],
+ "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from PHP Composer projects",
+ "homepage": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme",
+ "keywords": [
+ "CycloneDX",
+ "SBOM",
+ "bill-of-materials",
+ "bom",
+ "composer",
+ "package-url",
+ "purl",
+ "software-bill-of-materials",
+ "spdx"
+ ],
+ "support": {
+ "issues": "https://github.com/CycloneDX/cyclonedx-php-composer/issues",
+ "source": "https://github.com/CycloneDX/cyclonedx-php-composer/"
+ },
+ "funding": [
+ {
+ "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX",
+ "type": "other"
+ }
+ ],
+ "time": "2026-02-17T13:23:10+00:00"
+ },
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
"version": "v1.2.0",
@@ -1669,6 +1922,262 @@
},
"time": "2025-12-06T11:45:25+00:00"
},
+ {
+ "name": "opis/json-schema",
+ "version": "2.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/opis/json-schema.git",
+ "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/opis/json-schema/zipball/8458763e0dd0b6baa310e04f1829fc73da4e8c8a",
+ "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "opis/string": "^2.1",
+ "opis/uri": "^1.0",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "ext-bcmath": "*",
+ "ext-intl": "*",
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Opis\\JsonSchema\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Sorin Sarca",
+ "email": "sarca_sorin@hotmail.com"
+ },
+ {
+ "name": "Marius Sarca",
+ "email": "marius.sarca@gmail.com"
+ }
+ ],
+ "description": "Json Schema Validator for PHP",
+ "homepage": "https://opis.io/json-schema",
+ "keywords": [
+ "json",
+ "json-schema",
+ "schema",
+ "validation",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/opis/json-schema/issues",
+ "source": "https://github.com/opis/json-schema/tree/2.6.0"
+ },
+ "time": "2025-10-17T12:46:48+00:00"
+ },
+ {
+ "name": "opis/string",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/opis/string.git",
+ "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/opis/string/zipball/3e4d2aaff518ac518530b89bb26ed40f4503635e",
+ "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "ext-json": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Opis\\String\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Marius Sarca",
+ "email": "marius.sarca@gmail.com"
+ },
+ {
+ "name": "Sorin Sarca",
+ "email": "sarca_sorin@hotmail.com"
+ }
+ ],
+ "description": "Multibyte strings as objects",
+ "homepage": "https://opis.io/string",
+ "keywords": [
+ "multi-byte",
+ "opis",
+ "string",
+ "string manipulation",
+ "utf-8"
+ ],
+ "support": {
+ "issues": "https://github.com/opis/string/issues",
+ "source": "https://github.com/opis/string/tree/2.1.0"
+ },
+ "time": "2025-10-17T12:38:41+00:00"
+ },
+ {
+ "name": "opis/uri",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/opis/uri.git",
+ "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a",
+ "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a",
+ "shasum": ""
+ },
+ "require": {
+ "opis/string": "^2.0",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Opis\\Uri\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Marius Sarca",
+ "email": "marius.sarca@gmail.com"
+ },
+ {
+ "name": "Sorin Sarca",
+ "email": "sarca_sorin@hotmail.com"
+ }
+ ],
+ "description": "Build, parse and validate URIs and URI-templates",
+ "homepage": "https://opis.io",
+ "keywords": [
+ "URI Template",
+ "parse url",
+ "punycode",
+ "uri",
+ "uri components",
+ "url",
+ "validate uri"
+ ],
+ "support": {
+ "issues": "https://github.com/opis/uri/issues",
+ "source": "https://github.com/opis/uri/tree/1.1.0"
+ },
+ "time": "2021-05-22T15:57:08+00:00"
+ },
+ {
+ "name": "package-url/packageurl-php",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/package-url/packageurl-php.git",
+ "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/package-url/packageurl-php/zipball/32058ad61f0d8b457fa26e7860bbd8b903196d3f",
+ "reference": "32058ad61f0d8b457fa26e7860bbd8b903196d3f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3 || ^8.0"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "phpunit/phpunit": "9.6.16",
+ "roave/security-advisories": "dev-latest"
+ },
+ "type": "library",
+ "extra": {
+ "composer-normalize": {
+ "indent-size": 4,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PackageUrl\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Kowalleck",
+ "email": "jan.kowalleck@gmail.com",
+ "homepage": "https://github.com/jkowalleck"
+ }
+ ],
+ "description": "Builder and parser based on the package URL (purl) specification.",
+ "homepage": "https://github.com/package-url/packageurl-php#readme",
+ "keywords": [
+ "package",
+ "package-url",
+ "packageurl",
+ "purl",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/package-url/packageurl-php/issues",
+ "source": "https://github.com/package-url/packageurl-php/tree/1.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/jkowalleck",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-05T11:20:07+00:00"
+ },
{
"name": "pdepend/pdepend",
"version": "2.16.2",
diff --git a/coverage-report/clover.xml b/coverage-report/clover.xml
new file mode 100644
index 0000000..e99e06a
--- /dev/null
+++ b/coverage-report/clover.xml
@@ -0,0 +1,8687 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/css/dashboardWidgets.css b/css/dashboardWidgets.css
new file mode 100644
index 0000000..6953895
--- /dev/null
+++ b/css/dashboardWidgets.css
@@ -0,0 +1,8 @@
+.icon-procest-widget {
+ background-image: url("../img/app-dark.svg");
+ filter: var(--background-invert-if-dark);
+}
+
+body.theme--dark .icon-procest-widget {
+ background-image: url("../img/app.svg");
+}
diff --git a/data/ZGW OAS tests.postman_collection.json b/data/ZGW OAS tests.postman_collection.json
new file mode 100644
index 0000000..36f12dd
--- /dev/null
+++ b/data/ZGW OAS tests.postman_collection.json
@@ -0,0 +1,18401 @@
+{
+ "info": {
+ "_postman_id": "70a0aa28-c2a0-41c2-a18f-1da2dbf84479",
+ "name": "ZGW OAS tests",
+ "description": "Dit is de Postman test suite voor de OAS specificaties van de ZGW API's, elke folder bevat tests die verifieren of de API's voldoen aan de OAS specificaties van die API's. Het gaat om de volgende OAS schema's:\n\n- Zaken API: https://zaken-api.vng.cloud/api/v1/schema/\n- Documenten API: https://documenten-api.vng.cloud/api/v1/schema/\n- Catalogi API: https://catalogi-api.vng.cloud/api/v1/schema/\n- Besluiten API: https://besluiten-api.vng.cloud/api/v1/schema/\n- Notificaties API: https://notificaties-api.vng.cloud/api/v1/schema/\n- Autorisaties API: https://autorisaties-api.vng.cloud/api/v1/schema/\n\n**LET OP:** voor deze tests zijn de environment variabelen `client_id` en `secret` nodig, dit moeten credentials zijn die geregistreerd zijn binnen de Autorisaties API, en hiervoor moet dus ook een bestaande applicatie met superuser rechten aanwezig zijn in het systeem. Aan de hand van deze credentials wordt binnen de testsuite een JSON Web Token gegenereerd, die vervolgens gebruikt wordt in de Authorization headers van iedere API call",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "8783588",
+ "_collection_link": "https://go.postman.co/collection/9365542-70a0aa28-c2a0-41c2-a18f-1da2dbf84479?source=collection_link"
+ },
+ "item": [
+ {
+ "name": "setUp",
+ "item": [
+ {
+ "name": "Delete Applicatie for AC tests",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "var results = pm.response.json().results;",
+ "if(results.length > 0) {",
+ " for(i=0; i\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"omschrijvingGeneriek\": \"\",\n \"toelichting\": \"\",\n \"servicenorm\": \"\",\n \"verlengingstermijn\": \"\",\n \"trefwoorden\": [\n \"\",\n \"\"\n ],\n \"publicatietekst\": \"\",\n \"verantwoordingsrelatie\": [\n \"\",\n \"\"\n ],\n \"selectielijstProcestype\": \"\",\n \"eindeGeldigheid\": \"\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/zaaktypen/",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "zaaktypen",
+ ""
+ ]
+ }
+ },
+ "status": "Gone",
+ "code": 410,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "API-version",
+ "value": "",
+ "description": "Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld: 1.2.1."
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"code\": \"\",\n \"title\": \"\",\n \"status\": \"\",\n \"detail\": \"\",\n \"instance\": \"\",\n \"type\": \"\"\n}"
+ },
+ {
+ "name": "Forbidden",
+ "originalRequest": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"identificatie\": \"\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"omschrijvingGeneriek\": \"\",\n \"toelichting\": \"\",\n \"servicenorm\": \"\",\n \"verlengingstermijn\": \"\",\n \"trefwoorden\": [\n \"\",\n \"\"\n ],\n \"publicatietekst\": \"\",\n \"verantwoordingsrelatie\": [\n \"\",\n \"\"\n ],\n \"selectielijstProcestype\": \"\",\n \"eindeGeldigheid\": \"\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/zaaktypen/",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "zaaktypen",
+ ""
+ ]
+ }
+ },
+ "status": "Forbidden",
+ "code": 403,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "API-version",
+ "value": "",
+ "description": "Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld: 1.2.1."
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"code\": \"\",\n \"title\": \"\",\n \"status\": \"\",\n \"detail\": \"\",\n \"instance\": \"\",\n \"type\": \"\"\n}"
+ },
+ {
+ "name": "OK",
+ "originalRequest": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"identificatie\": \"\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"omschrijvingGeneriek\": \"\",\n \"toelichting\": \"\",\n \"servicenorm\": \"\",\n \"verlengingstermijn\": \"\",\n \"trefwoorden\": [\n \"\",\n \"\"\n ],\n \"publicatietekst\": \"\",\n \"verantwoordingsrelatie\": [\n \"\",\n \"\"\n ],\n \"selectielijstProcestype\": \"\",\n \"eindeGeldigheid\": \"\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/zaaktypen/",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "zaaktypen",
+ ""
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "API-version",
+ "value": "",
+ "description": "Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld: 1.2.1."
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"identificatie\": \"\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"url\": \"\",\n \"omschrijvingGeneriek\": \"\",\n \"toelichting\": \"\",\n \"servicenorm\": \"\",\n \"verlengingstermijn\": \"\",\n \"trefwoorden\": [\n \"\",\n \"\"\n ],\n \"publicatietekst\": \"\",\n \"verantwoordingsrelatie\": [\n \"\",\n \"\"\n ],\n \"selectielijstProcestype\": \"\",\n \"statustypen\": [\n \"\"\n ],\n \"resultaattypen\": [\n \"\"\n ],\n \"eigenschappen\": [\n \"\"\n ],\n \"informatieobjecttypes\": [\n \"\"\n ],\n \"roltypen\": [\n \"\"\n ],\n \"eindeGeldigheid\": \"\",\n \"concept\": \"\"\n}"
+ },
+ {
+ "name": "Not acceptable",
+ "originalRequest": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"identificatie\": \"\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"omschrijvingGeneriek\": \"\",\n \"toelichting\": \"\",\n \"servicenorm\": \"\",\n \"verlengingstermijn\": \"\",\n \"trefwoorden\": [\n \"\",\n \"\"\n ],\n \"publicatietekst\": \"\",\n \"verantwoordingsrelatie\": [\n \"\",\n \"\"\n ],\n \"selectielijstProcestype\": \"\",\n \"eindeGeldigheid\": \"\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/zaaktypen/",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "zaaktypen",
+ ""
+ ]
+ }
+ },
+ "status": "Not Acceptable",
+ "code": 406,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "API-version",
+ "value": "",
+ "description": "Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld: 1.2.1."
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"code\": \"\",\n \"title\": \"\",\n \"status\": \"\",\n \"detail\": \"\",\n \"instance\": \"\",\n \"type\": \"\"\n}"
+ },
+ {
+ "name": "Internal server error",
+ "originalRequest": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"identificatie\": \"\",\n \"omschrijving\": \"\",\n \"vertrouwelijkheidaanduiding\": \"\",\n \"doel\": \"\",\n \"aanleiding\": \"\",\n \"indicatieInternOfExtern\": \"\",\n \"handelingInitiator\": \"\",\n \"onderwerp\": \"\",\n \"handelingBehandelaar\": \"\",\n \"doorlooptijd\": \"\",\n \"opschortingEnAanhoudingMogelijk\": \"\",\n \"verlengingMogelijk\": \"\",\n \"publicatieIndicatie\": \"\",\n \"productenOfDiensten\": [\n \"\",\n \"\"\n ],\n \"referentieproces\": {\n \"naam\": \"\",\n \"link\": \"\"\n },\n \"catalogus\": \"\",\n \"besluittypen\": [\n \"\"\n ],\n \"gerelateerdeZaaktypen\": [\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n },\n {\n \"zaaktype\": \"\",\n \"aardRelatie\": \"\",\n \"toelichting\": \"\"\n }\n ],\n \"beginGeldigheid\": \"\",\n \"versiedatum\": \"\",\n \"omschrijvingGeneriek\": \"