Ga naar hoofdinhoud

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
  • openregister app 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-saas chain

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>':

KeyDefaultDescription
jwt_signing_secretNC system secretHMAC HS256 signing secret for the supplier-portal session JWT (TenantJwtService)
eherkenning_broker_urlunsetOpenConnector eHerkenning broker base URL
eherkenning_client_idunsetOAuth client ID for the eHerkenning broker
eherkenning_client_secretunsetOAuth client secret (use NC secret vault — never commit)
kvk_api_urlunsetKvK 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 by SupplierAuthService::issueSessionToken() and enforces a 100 req/min/IP rate limit
  • The generic OpenRegister manifest renderer at /settings/<schema> serves CRUD on the supplier* schemas for admin users (per ADR-022 apps-consume-or-abstractions)

Background jobs​

JobFrequencyDescription
ResetMonthlyQuotasJobDailyResets monthly + hourly tenant quotas after their window elapses (parent SaaS chain)
ScanExpiringContractsJob (planned)Nightly 03:00Flags supplier contracts within 90 days of expiry — ContractRenewalService::scanExpiring
ExportBillingToShillinqJob (planned)Daily 02:00 UTCExports unsettled tenant billing events into Shillinq invoices
AggregateSupplierKpisJob (planned)Nightly 02:00Computes per-month KPI snapshot per supplier
RouteSupplierMessageJob (planned)Real-timeDispatches 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)
  • SupplierScopeService masks IBAN / email / phone in audit logs
  • supplierMessage schema 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 supplier row. 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/refresh or 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) or deprovisioning. Its data stays in place. Each status change writes one OpenRegister audit entry with the action procest.tenant.tenant.status_changed, and its resource reads tenant:<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 a Dossiq AUDIT line. No log line says that tenant data was deleted, because nothing deletes it.