Dossiq Leverancier Zaakportaal — Deployment Guide
This guide covers the supplier-portal (leverancier-zaakportaal-*) chain
shipped in the Dossiq app. The portal layers on top of the existing
Dossiq case-management surface and reuses the OpenRegister schemas
declared in chain member 01.
Prerequisites
- Nextcloud ≥ 30
openregisterapp installed and enabled- PostgreSQL backend (the schema-per-tenant primitives in chain member 03 are Postgres-specific)
- OpenConnector app for the eHerkenning broker
- (Optional) Shillinq backend for invoicing — only required for the
parent
tenant-zaaksysteem-saaschain
Repair-step bootstrap
The 7 supplier schemas + 4 supplier case types ship as seed objects in
lib/Settings/dossiq_register.json. They land via the existing
Dossiq\Repair\InitializeSettings repair step on app enable /
upgrade — no separate migration needed.
# Re-import register seed (idempotent).
occ maintenance:repair
App-config keys
Set these via occ config:app:set dossiq <key> --value '<value>':
| Key | Default | Description |
|---|---|---|
jwt_signing_secret | NC system secret | HMAC HS256 signing secret for the supplier-portal session JWT (TenantJwtService) |
eherkenning_broker_url | unset | OpenConnector eHerkenning broker base URL |
eherkenning_client_id | unset | OAuth client ID for the eHerkenning broker |
eherkenning_client_secret | unset | OAuth client secret (use NC secret vault — never commit) |
kvk_api_url | unset | KvK API base URL (used during supplier validation) |
Tenant invoices need no setting. Dossiq asks shillinq on the same server to draft each month's invoice, for the shillinq customer whose external reference is the tenant id. The settings shillinq_base_url and shillinq_api_key are no longer read.
Routes
The supplier-portal endpoints are declared in
docs/openapi/leverancier-zaakportaal.yaml. The wiring shape is:
- All endpoints are admin-route under
/index.php/apps/dossiq/... SupplierAuthMiddleware(chain member 04) gates every supplier controller — it requires a bearer JWT issued bySupplierAuthService::issueSessionToken()and enforces a 100 req/min/IP rate limit- The generic OpenRegister manifest renderer at
/settings/<schema>serves CRUD on thesupplier*schemas for admin users (per ADR-022 apps-consume-or-abstractions)
Background jobs
| Job | Frequency | Description |
|---|---|---|
ResetMonthlyQuotasJob | Daily | Resets monthly + hourly tenant quotas after their window elapses (parent SaaS chain) |
ScanExpiringContractsJob (planned) | Nightly 03:00 | Flags supplier contracts within 90 days of expiry — ContractRenewalService::scanExpiring |
ExportBillingToShillinqJob (planned) | Daily 02:00 UTC | Exports unsettled tenant billing events into Shillinq invoices |
AggregateSupplierKpisJob (planned) | Nightly 02:00 | Computes per-month KPI snapshot per supplier |
RouteSupplierMessageJob (planned) | Real-time | Dispatches new supplier messages to handler inboxes + sends email notifications |
ResetMonthlyQuotasJob is registered in appinfo/info.xml; the
remaining jobs ship as planned wiring in chain member 16 once their
dependencies (Shillinq URL, mailer template, OpenConnector broker
URL) are configured.
Security checklist
- All endpoints behind
SupplierAuthMiddleware(bearer JWT + 100 req/min/IP rate-limit + IP-bucket fail counter on 5+ failures) SupplierScopeServicemasks IBAN / email / phone in audit logssupplierMessageschema is write-once (x-insert-only:true)- IBAN changes go through a 4-eyes Dossiq case
(
leverancier-iban-wijziging) — the supplier row is never directly mutated by the supplier user TenantAuditTrailService::emit()is called on every mutating service path (invite, role change, revoke, message send, mutation request, IBAN-change request, accreditation submit)
Troubleshooting
- "Onbekende leverancier" on login — the eHerkenning KvK number
did not match any
supplierrow. Seed the supplier or check the KvK number format (6-12 digits). - HTTP 429 — rate limit hit (100 req/min/IP); back off or shard traffic.
- HTTP 401 on dashboard — bearer JWT expired (2-hour TTL);
call
POST /auth/refreshor re-login. - Ending a tenancy is a status change, not a delete. A tenant is
an OpenRegister organisation, so you end it in OpenRegister, which
moves it to
retained(access ends, data kept) ordeprovisioning. Its data stays in place. Each status change writes one OpenRegister audit entry with the actionprocest.tenant.tenant.status_changed, and its resource readstenant:<id> <old>-><new>, followed by the number of billing events not yet invoiced when the tenancy ends. The same entry appears in the Nextcloud log as aDossiq AUDITline. No log line says that tenant data was deleted, because nothing deletes it.