BCF Configuration
This guide is for administrators rolling out the BCF (Btw-compensatiefonds /
VAT Compensation Fund) claim feature for one or more public-sector
administrations. End-user instructions live in
docs/user-guide/bookkeeping/bcf-vat-compensation.md.
The feature is declarative-by-default per ADR-031 — the schema fragment
lib/Settings/register.d/bookkeeping-bcf-vat-compensation.json declares the
data model, lifecycle, aggregation, approval-chain, and settlement webhook;
the repair step InitializeSettings.php registers the quarterly
ScheduledWorkflow; and the pure-logic engine
(BcfClaimService + BcfClaimGuard + BcfCompensationCalculator) computes
the compensable total server-side. No DigiKoppeling client ships in Shillinq
itself.
Prerequisites
- Shillinq is installed and the OpenRegister + OpenConnector apps are enabled on the Nextcloud instance.
- The instance is reachable from the OpenConnector outbound network (DigiKoppeling endpoints are TLS-only and certificate-pinned).
- Your administration's
administrationTypeisgemeente,provincie, orwaterschap.
RBAC setup
The feature ships three claim-specific roles. Wire them up before operators start drafting claims.
| Role | Permissions |
|---|---|
bcf-viewer | Read-only on the BCF-claims index and detail pages. Cannot create, edit, or transition. Suitable for auditors and stakeholders. |
bcf-operator | All bcf-viewer permissions, plus create/edit draft claims, attach files, edit operator notes, submit a draft for approval. Cannot approve their own submissions. |
bcf-administrator | All bcf-operator permissions, plus approve/reject submissions, perform manual accepted → settled fallback transitions, and edit administration-wide BCF settings. |
Wire roles up from Nextcloud → Settings → Users → Groups. Each role maps to a group; assign the group to a user to grant the role. The implicit fourth role is global admin, which can do everything.
A typical small gemeente has 1-2 bcf-administrator users (the
controller and a backup) and 2-5 bcf-operator users (the finance
team).
The role membership is enforced server-side via
AdministrationContextService::canAccess() — cross-administration
calls are masked as 404 (ADR-005 IDOR-safe) so a bcf-operator for
administration A cannot see or modify claims for administration B.
Quarterly schedule
The repair step InitializeSettings::registerBcfQuarterlyDigikoppelingWorkflow()
registers the workflow on every install/upgrade:
| Field | Default |
|---|---|
| Slug | shillinq-bcf-quarterly-digikoppeling-submission |
| Engine | openconnector |
| Workflow id | digikoppeling-bcf |
| Interval | 7776000s (90 days — quarterly) |
| Cron equivalent | 0 0 1 */3 * (first day of each quarter) |
| Target schema | BcfClaim |
administrationType filter | gemeente, provincie, waterschap |
Adjust interval or target from the OpenRegister → ScheduledWorkflows admin UI when Belastingdienst deadlines shift. Registration is idempotent: the repair step matches by slug, so re-runs never duplicate the workflow.
How a quarterly run works
- The cron fires on the first day of the quarter at 09:00.
- The workflow walks every
BcfClaimin statesubmittedfor the previous closed quarter. - For each, it invokes the OpenConnector
digikoppeling-bcfsource with the claim payload (administrationId,claimQuarter,totalCompensableAmount,breakdown,attachmentUri). - On success the source stamps
submittedOnon the claim. - On a transient failure (network, certificate) the source retries with exponential backoff (its own concern, not Shillinq's).
- On a permanent failure the operator is notified and the claim
stays in
submittedstate until the next scheduled run.
DigiKoppeling source configuration
The digikoppeling-bcf OpenConnector source is owned by the
OpenConnector team — Shillinq references it symbolically. The
expected contract is:
- Input: a
BcfClaimobject payload (perlib/Settings/register.d/bookkeeping-bcf-vat-compensation.json). - Auth: TLS client certificate pinned to your administration's DigiKoppeling identity. Renew before the certificate expires — expired certificates surface in the workflow output as a permanent failure.
- Output (success): HTTP 2xx; the source confirms reception. The source must asynchronously deliver the settlement webhook.
- Output (settlement webhook): a CloudEvent of type
nl.conduction.bcf-claim-settledposted to OpenRegister's generic webhook handler. Payload:
{
"type": "nl.conduction.bcf-claim-settled",
"objectId": "<BcfClaim.id>",
"data": {
"state": "settled",
"settledAmount": 120000,
"settledDate": "2026-02-15"
}
}
Verify the source is registered in OpenConnector before going live:
OpenConnector → Sources → digikoppeling-bcf. If the source is
absent, the workflow logs digikoppeling-bcf source not found on every
run.
Settlement webhook routing
The webhook routing is declared on the BcfClaim schema fragment
under x-openregister-webhooks.bcf-claim-settled. OR's generic
webhook handler:
- Receives the inbound CloudEvent (auth verified by OR — this app ships no webhook controller).
- Resolves the
BcfClaimbyobjectId(administration scope enforced via the audit-trail context). - Applies the bound
settletransition. - Updates
state ← data.state,settledOn ← data.settledDate,settledAmount ← data.settledAmount. - Records
webhook.received+webhook.appliedin the audit trail with the event id, timestamp, and payload hash.
A lost webhook does not block. The operator can manually
transition accepted → settled from the detail page (logged as
source: operator in the audit trail), and the claim reconciles when
the webhook lands late.
Audit trail export
The audit trail is immutable per ADR-022 and the Archiefwet retention policy. Export it for court or auditor review:
- Sign in as
bcf-administrator(or global admin). - Open the claim detail page.
- From the Audit Trail sidebar tab click Exporteer audit-trail (CSV).
- The export contains every state change with: timestamp, actor (user
id and display name; or
webhook:digikoppeling-bcf), action, before-state, after-state, payload hash, and any associated approval/rejection comment.
For administration-wide exports use OpenRegister → Audit Trails
with objectTypes=BcfClaim and the administration scope.
Rollback procedures
If you need to disable the feature:
- Disable the scheduled workflow: OpenRegister → ScheduledWorkflows → shillinq-bcf-quarterly-digikoppeling-submission → Disable. In-flight claims stop submitting; no new payloads reach Belastingdienst.
- Hide the Overheid → BCF-claims menu entry: remove
bcfCompensableadministrations from the visibility predicate, or uninstall Shillinq. - Existing
BcfClaimrecords remain queryable — registers are non-destructive. Operators can export them as PDF/CSV for archival. - To re-enable, re-enable the workflow and surface the menu entry again. Submitted but unsent claims will be sent on the next scheduled run.
To remove the feature entirely (uninstall pathway):
- Revert the implementing PR.
- Run the repair step in down-direction: deletes the
ScheduledWorkflowand reverts theBbvAccountMappingextension. BcfClaimrecords remain — registers are non-destructive.- Export claims for archival before downgrade.
Operational checklist
Before going live on a fresh installation:
- OpenRegister + OpenConnector apps enabled.
-
digikoppeling-bcfsource registered in OpenConnector. - DigiKoppeling TLS client certificate uploaded and pinned.
-
bcf-administrator,bcf-operator,bcf-viewergroups exist and at least one user is assigned to each. - BBV mapping is complete for the administration (every
compensable RGS account is flagged
bcfCompensable: true). - T2 period-close is run for every quarter you intend to claim.
- The scheduled workflow appears in OpenRegister →
ScheduledWorkflows with the slug
shillinq-bcf-quarterly-digikoppeling-submissionand is enabled. - A dry-run claim is created in
draft, approved, and submitted so the integration is verified end-to-end before a real claim window opens.
Where this fits
- Capability spec:
openspec/changes/bookkeeping-bcf-vat-compensation/specs.md - Schema fragment:
lib/Settings/register.d/bookkeeping-bcf-vat-compensation.json - Repair step:
lib/Repair/InitializeSettings.php - Server engine:
lib/Service/BcfClaimService.php+lib/Lifecycle/BcfClaimGuard.php+lib/Service/BcfCompensationCalculator.php - HTTP API:
GET /apps/shillinq/api/bcf/compensation(BcfClaimController::compensation) - User guide:
docs/user-guide/bookkeeping/bcf-vat-compensation.md