Ga naar hoofdinhoud

Parties on an object

A case has one requester and a dozen other people. The gemachtigde. The neighbour who files a zienswijze. The aannemer. The jurist at the omgevingsdienst. None of them is the aanvrager, and every one of them belongs on the record.

A property that points at a person answers one question. Every other question needs a second property. A case with two gemachtigden needs a third.

So a party is a row, not a property. The row names the object, the party, the role and the period the role runs.

What a party is

A party is an object. Its schema says so, with x-openregister-party:

{
"x-openregister-party": {
"kind": "person",
"nameProperty": "naam",
"addressesProperty": "adressen",
"indicatorsProperty": "indicatoren",
"parentProperty": "moederorganisatie",
"maxDepth": 10
}
}

Your app keeps its own property names. Open Register keeps one model.

A party needs no Nextcloud account. Most melders never sign in, and making them users is a permission surface nobody wants. The party carries its own name, its own addresses and its own indicators.

The declaration is checked when the schema is saved. A field naming a property you never declared is reported, not stored in silence.

Which parties a schema accepts

The object's schema declares what it takes:

{
"partyKinds": [
{ "key": "person", "label": "Persoon", "roles": ["aanvrager", "gemachtigde"] },
{ "key": "organisation", "label": "Organisatie" }
],
"linkRoles": [
{ "key": "aanvrager", "label": "Aanvrager" },
{ "key": "gemachtigde", "label": "Gemachtigde" }
]
}

A kind naming roles holds only those roles. A kind naming none holds any role the schema declares.

A write naming a kind the schema does not accept is refused with a 400. The message names the kind and lists what is accepted.

A schema that declares no partyKinds accepts every kind. Registers written before this feature keep behaving as they did.

Addresses

Addresses hang off the party, each with a kind:

{
"adressen": [
{ "kind": "correspondence", "type": "email", "value": "jan@example.org" },
{ "kind": "correspondence", "type": "email", "value": "j.jansen@example.org" },
{ "kind": "case", "type": "postal", "value": "Dorpsstraat 1" },
{ "kind": "location", "type": "geo", "value": "52.09,5.12" }
]
}

Outbound mail picks the correspondence address. Inbound mail from any address resolves to the same party, so a second record is never created.

A bare string still reads as a correspondence e-mail. A register holding one address per party keeps working.

Indicators

An indicator that only renders is an indicator somebody misses. Each one declares what it does:

EffectWhat happens
warnThe reader sees it. Nothing is blocked.
refuse-publicationPublishing a file on the object is refused.
refuse-sendAn outbound message to that party is refused.
{
"indicatoren": [
{ "key": "geheimhouding", "label": "Geheimhouding persoonsgegevens", "effect": "refuse-publication" }
]
}

The effect is evaluated where the act happens, not in the component that draws the chip. The refusal names the indicator and the party.

An indicator with no effect, or an effect this version does not know, warns. It never vanishes.

Set the indicator once, on the party. Every case that party holds a role on reads it. None of those cases is written.

The primary party

One link per object carries primaryParty. It is the party the case is filed against.

An intake filed on the wrong person is an AVG incident, not a typo. So replacing it writes one audit entry, action party.primary-replaced, naming the party that went, the party that came and the actor.

Merging two parties

Two records that turn out to be one person merge through the existing merge primitive. The preview, the atomic execution, the reversal window and the merge register are the ones mdm-merge already has.

The party vocabulary adds three things. Every role both parties held carries over onto the survivor. Their addresses union, so the surviving record is never less reachable than either original. A reversal inside the window puts both parties back, holding the roles they held before.

Nothing about the merge itself changes. A second merge beside it would be two merges that disagree about what happened.

The query cap

A person search that silently returns the first ten looks like a search that found ten.

Open Register counts first. Over the administered cap the query is refused with a 403 naming the cap, and the response holds no party at all. The refused attempt goes on the audit trail with the actor, the query and the cap.

Set the cap under party.queryCap in the app settings. The default is 50.

The API

CallWhat it does
GET /api/objects/{register}/{schema}/{id}/partiesThe parties, grouped by role, with the accepted kinds
POST /api/objects/{register}/{schema}/{id}/partiesGive a party a role
PUT /api/objects/{register}/{schema}/{id}/parties/primaryReplace the primary party
DELETE /api/objects/{register}/{schema}/{id}/parties/{partyUuid}Take a party off, all roles or one
GET /api/parties/{partyUuid}One party: kind, addresses, indicators, every object it is on
GET /api/parties/search?q=The capped search
GET /api/parties/resolve?address=The party holding an inbound address

A listing answers results, total, byRole, kinds, roles and primary. A picker reads one call.

What dossiq declares

Per case type, on the case schema: partyKinds with the roles each kind may hold, and linkRoles for the labels. Then render byRole from the listing.

Stop treating the initiator as the only party. The initiator is the link whose primaryParty is true.

What integriq writes into

The BRP and KvK adapters write into the party record, one per person, not one copy per app. Give the party schema x-openregister-party and point addressesProperty at the addresses the adapter fills.

An adapter that finds an existing party by address must call GET /api/parties/resolve first. That is what stops a second record per source.

Next

Declare partyKinds on one case type. Add a party in a role, then read GET .../parties and check the vocabulary comes back beside it.