Skip to content

Data Model

This document describes the custom post types, taxonomies, and ACF field groups that make up the Rondo Club data model.

The system uses nine custom post types, registered in includes/class-post-types.php. Six are the primary data types documented below; there are also three internal types (calendar_event, rondo_feedback, rondo_todo) covered briefly at the end.

Represents individual contacts in the CRM.

PropertyValue
REST Base/wp/v2/people
Menu Icondashicons-groups
Supportstitle, thumbnail, comments, author
PublicNo (private, accessed via REST API)

ACF Fields (from acf-json/group_person_fields.json):

FieldKeyTypeDescription
First Namefirst_nametextRequired. Person’s first name
InfixinfixtextTussenvoegsel (e.g., van, de, van der). Read-only, synced from Sportlink
Last Namelast_nametextPerson’s last name
NicknamenicknametextInformal name or alias
GendergenderselectOptions: male, female, non_binary, other, prefer_not_to_say
Photo Galleryphoto_gallerygalleryUp to 50 photos, first is profile photo
Favoriteis_favoritetrue_falseMark as favorite contact
Contact Infocontact_inforepeaterContact methods (see below)
AddressesaddressesrepeaterPhysical addresses (see below)
Work Historywork_historyrepeaterEmployment history (see below)
RelationshipsrelationshipsrepeaterConnections to other people (see below)

Sportlink-Synced Fields (from acf-json/group_person_fields.json, Sportlink tab):

FieldKeyTypeDescription
KNVB IDknvb-idtextSportlink member number
isParentisparenttrue_falseWhether person is registered as parent in Sportlink
Type lidtype-lidtextMembership type (e.g., “Junior”, “Senior”, “Donateur”)
LeeftijdsgroepleeftijdsgroeptextSportlink age class (e.g., “Onder 9”, “Onder 18”, “Senioren”)
Lid sindslid-sindsdate_pickerMembership registration date
Datum fotodatum-fotodate_pickerDate of last photo in Sportlink
Datum VOGdatum-vogdate_pickerDate of last VOG certificate
Huidig Vrijwilligerhuidig-vrijwilligertrue_falseCurrent volunteer status (auto-calculated, see below)
Financiële blokkadefinanciele-blokkadetrue_falseFinancial block flag from Sportlink
FreeScout IDfreescout-idnumberLinked FreeScout customer ID

Membership Status Fields (from acf-json/group_person_fields.json, Basic Information tab):

FieldKeyTypeDescription
Oud-lidformer_membertrue_falseWhether person is a former member
Lid totlid-totdate_pickerDate membership ended
Datum overlijdendatum-overlijdendate_pickerDate of death

Werkfuncties (synced from Sportlink via Rondo Sync, stored as serialized array in post meta):

The werkfuncties field contains an array of Sportlink function strings (e.g., ["Donateur"], ["Trainer", "Bestuurslid"]). Used by the fee calculation system for werkfunctie-based category matching.

NIKKI Contributie Fields (from acf-json/group_person_fields.json, Contributie tab):

Per-year contribution tracking synced from NIKKI. Fields follow the pattern _nikki_{YEAR}_{field}:

Field PatternTypeDescription
_nikki_{YEAR}_totalnumberTotal contribution amount for that year
_nikki_{YEAR}_saldonumberOutstanding balance for that year
_nikki_{YEAR}_statustextPayment status for that year

Years currently tracked: 2022, 2023, 2024, 2025.

VOG Tracking Meta (registered in class-post-types.php via register_post_meta):

FieldTypeDescription
vog_email_sent_datestringDate VOG request email was sent
vog_justis_submitted_datestringDate VOG was submitted to Justis
vog_reminder_sent_datestringDate VOG reminder email was sent

Contributie Exclusion Meta (registered in class-post-types.php via register_post_meta):

FieldTypeDescription
_exclude_from_contributiebooleanExclude person from fee calculations (requires financieel capability to write)

Computed Fields (read-only, auto-calculated):

FieldKeyTypeDescription
Huidig Vrijwilligerhuidig-vrijwilligertrue_falseCurrent volunteer status. Auto-calculated from work history: 1 if the person has an active staff/commissie position, 0 otherwise. See class-volunteer-status.php.
is_deceasedbooleanComputed in REST response from datum-overlijden field. true if death date is set. See class-rest-people.php.
birth_yearintegerComputed in REST response by extracting year from birthdate field. See class-rest-people.php.

Contact Info Sub-fields:

FieldKeyTypeOptions
Typecontact_typeselectemail, phone, mobile, website, calendar, linkedin, twitter, instagram, facebook, other
Labelcontact_labeltexte.g., “Work”, “Personal”
Valuecontact_valuetextThe actual contact value

Addresses Sub-fields:

FieldKeyTypeDescription
Labeladdress_labeltexte.g., “Home”, “Work”
StreetstreettextStreet address
Postal Codepostal_codetextZIP or postal code
CitycitytextCity name
State/ProvincestatetextState or province
CountrycountrytextCountry name

Work History Sub-fields:

FieldKeyTypeDescription
Teamteampost_objectLink to Team post
Job Titlejob_titletextPosition title
DescriptiondescriptiontextareaRole description
Start Datestart_datedate_pickerEmployment start (Y-m-d)
End Dateend_datedate_pickerEmployment end (Y-m-d)
Currentis_currenttrue_falseCurrently employed here

Relationships Sub-fields:

FieldKeyTypeDescription
Personrelated_personpost_objectLink to related Person
Typerelationship_typetaxonomyFrom relationship_type taxonomy
Custom Labelrelationship_labeltextOverride label (e.g., “Brother-in-law”)

Represents teams where contacts work. Note: The post type slug remains team for backward compatibility, but the user-facing label is “Team”.

PropertyValue
REST Base/wp/v2/teams
Menu Icondashicons-building
Supportstitle, editor, thumbnail, author
PublicNo (private, accessed via REST API)

ACF Fields (from acf-json/group_team_fields.json):

FieldKeyTypeDescription
WebsitewebsiteurlTeam website URL
IndustryindustrytextIndustry or sector
Contact Infocontact_inforepeaterTeam contact methods
InvestorsinvestorsrelationshipPeople or teams that have invested in this team

Contact Info Sub-fields:

FieldKeyTypeOptions
Typecontact_typeselectphone, email, address, other
Labelcontact_labeltexte.g., “HQ”, “Support”
Valuecontact_valuetextThe actual contact value

Represents committees, synced from Sportlink. Supports hierarchical parent-child relationships.

PropertyValue
REST Base/wp/v2/commissies
Menu Icondashicons-businessperson
Supportstitle, editor, thumbnail, author, page-attributes
PublicNo (private, accessed via REST API)
HierarchicalYes

ACF Fields (from acf-json/group_commissie_fields.json):

FieldKeyTypeDescription
WebsitewebsiteurlCommittee website URL
Contact Infocontact_inforepeaterCommittee contact methods (same sub-fields as Team)

Tracks sports disciplinary actions, synced from Sportlink. Each case is linked to a person and includes match details, charges, and sanctions.

PropertyValue
REST Base/wp/v2/discipline-cases
Menu Icondashicons-warning
Supportstitle, author
PublicNo (private, accessed via REST API)
Taxonomiesseizoen

ACF Fields (from acf-json/group_discipline_case_fields.json):

FieldKeyTypeDescription
Dossier IDdossier_idtextSportlink dossier identifier
Persoonpersonpost_objectLink to Person post
Wedstrijddatummatch_datedate_pickerDate of the match
Verwerkingsdatumprocessing_datedate_pickerDate case was processed
Wedstrijdomschrijvingmatch_descriptiontextDescription of the match
Teamnaamteam_nametextTeam name (text, not linked)
Thuisteamhome_teampost_objectLink to home Team post
Uitteamaway_teampost_objectLink to away Team post
Artikelnummercharge_codestextCharge article codes
Artikelomschrijvingcharge_descriptiontextareaCharge description
Strafbeschrijvingsanction_descriptiontextareaSanction description
Administratiekostenadministrative_feenumberAdministrative fee amount
Doorbelastis_chargedselectWhether costs are charged to the member

Tracks financial invoices for membership fees or discipline case charges. Introduced in v27.

PropertyValue
REST Base/wp/v2/invoices
Menu Icondashicons-media-text
Supportstitle, author
PublicNo (private, accessed via REST API)

Custom Post Statuses:

StatusLabelDescription
rondo_draftConceptInvoice created but not yet sent
rondo_sentVerstuurdInvoice sent to member
rondo_paidBetaaldInvoice fully paid
rondo_overdueVerlopenInvoice past due date

ACF Fields (from acf-json/group_invoice_fields.json):

FieldKeyTypeDescription
Factuurtypeinvoice_typeselectType: membership or discipline
Factuurnummerinvoice_numbertextGenerated invoice number (C prefix for contributie)
Persoonpersonpost_objectLink to Person post
StatusstatusselectInvoice status
Regelitemsline_itemsrepeaterInvoice line items (see below)
Totaalbedragtotal_amountnumberTotal invoice amount
Betaallinkpayment_linkurlMollie payment URL
PDF padpdf_pathtextServer path to generated PDF
QR code padqr_code_pathtextServer path to generated QR code
Verzenddatumsent_datedate_pickerDate invoice was sent
Vervaldatumdue_datedate_pickerPayment due date

Line Items Sub-fields:

FieldKeyTypeDescription
Tuchtzaakdiscipline_casepost_objectLink to Discipline Case (for discipline invoices)
OmschrijvingdescriptiontextLine item description
BedragamountnumberLine item amount

Installment Meta (stored as post meta via FinanceConfig, not ACF):

Invoices with installment plans store per-installment data using numbered meta keys:

Meta Key PatternTypeDescription
_installment_countintTotal number of installments
_installment_planstringPlan type: full, quarterly_3, or monthly_8
_installment_N_amountfloatAmount for installment N
_installment_N_admin_feefloatAdmin fee portion for installment N
_installment_N_statusstringStatus: pending, sent, paid, overdue
_installment_N_due_datestringDue date (Y-m-d)
_installment_N_sent_atstringDateTime email was sent
_installment_N_paid_atstringDateTime payment received
_installment_N_mollie_payment_idstringMollie payment identifier
_installment_N_payment_linkstringMollie checkout URL

These post types are used internally and have limited or no direct REST API exposure:

Task tracking linked to people. Uses custom post statuses for state management.

PropertyValue
REST Base/wp/v2/todos
Supportstitle, editor, author

Custom Post Statuses: rondo_open, rondo_awaiting, rondo_completed

ACF Fields (from acf-json/group_todo_fields.json):

FieldKeyTypeDescription
Related Peoplerelated_personspost_objectLinked Person posts
NotesnoteswysiwygAdditional notes
Awaiting Sinceawaiting_sincedate_time_pickerWhen status changed to awaiting
Due Datedue_datedate_pickerTask due date

Cached events synced from external calendars (Google, CalDAV). Not exposed via standard REST API — uses custom endpoints only. No admin UI.

User feedback (bug reports, feature requests). Global per installation, not workspace-scoped.

PropertyValue
REST Base/wp/v2/feedback
Supportstitle, editor, author

The CRM uses two custom taxonomies, registered in includes/class-taxonomies.php.

Defines the types of relationships between people.

PropertyValue
HierarchicalYes
Attached Toperson
REST EnabledYes

ACF Fields (from acf-json/group_relationship_type_fields.json):

FieldKeyTypeDescription
Inverse Typeinverse_relationship_typetaxonomyThe reciprocal relationship type
Gender Dependentis_gender_dependenttrue_falseVaries by gender (e.g., aunt/uncle)
Gender Groupgender_dependent_grouptextGroup name for gender variants

Default Relationship Types:

The system creates these relationship types on activation:

CategoryTypes
Basicpartner, spouse, friend, colleague, acquaintance, ex
Immediate Familyparent, child, sibling
Extended Familygrandparent, grandchild, uncle, aunt, nephew, niece, cousin
Step/In-lawstepparent, stepchild, stepsibling, inlaw
Other Familygodparent, godchild
Professionalboss, subordinate, mentor, mentee

Inverse Mappings:

  • Symmetric: spouse↔spouse, friend↔friend, colleague↔colleague, sibling↔sibling, cousin↔cousin, partner↔partner
  • Asymmetric: parent↔child, grandparent↔grandchild, stepparent↔stepchild, godparent↔godchild, boss↔subordinate, mentor↔mentee
  • Gender-dependent: aunt/uncle↔niece/nephew (resolves based on related person’s gender)

For more details, see Relationship Types and Relationships.


Season classification for discipline cases.

PropertyValue
HierarchicalNo (tag-like)
Attached Todiscipline_case
REST EnabledYes

Example values: 2024-2025, 2025-2026


Both main post types (Person, Team) include visibility settings.

ACF Fields (from acf-json/group_visibility_settings.json):

FieldKeyTypeDescription
Visibility_visibilityselectControl who can see this record

Visibility Options:

ValueDescription
privateOnly the post author can see (default)
workspaceVisible to workspace members
sharedShared with specific users

Shared With Post Meta:

For shared visibility, the _shared_with post meta stores sharing details:

[
{
"user_id": 5,
"permission": "edit",
"shared_by": 1,
"shared_at": "2026-01-15T10:30:00Z"
},
{
"user_id": 8,
"permission": "view",
"shared_by": 1,
"shared_at": "2026-01-16T14:00:00Z"
}
]

Helper Class: Rondo\Core\Visibility provides static methods for managing visibility:

MethodDescription
get_visibility($post_id)Get visibility value (returns ‘private’ if not set)
set_visibility($post_id, $visibility)Set visibility value
get_shares($post_id)Get array of share objects
add_share($post_id, $user_id, $permission, $shared_by)Add or update a share
remove_share($post_id, $user_id)Remove a user’s share
user_has_share($post_id, $user_id)Check if user has share access
get_share_permission($post_id, $user_id)Get permission level (‘view’, ‘edit’, or false)

ACF field groups are version-controlled in acf-json/:

FilePurpose
group_person_fields.jsonPerson post type fields
group_team_fields.jsonTeam post type fields
group_relationship_type_fields.jsonRelationship type taxonomy fields
group_visibility_settings.jsonVisibility settings for all main post types

How it works:

  1. When WP_DEBUG is true, field changes in WordPress admin auto-save to these JSON files
  2. Production loads field definitions from JSON (faster than database)
  3. Changes sync via Git, keeping all environments consistent

All data is subject to row-level access control:

  • Users can only see posts they created themselves
  • Administrators are restricted on the frontend but have full access in WordPress admin area
  • Access filtering is applied at both WP_Query level and REST API responses

For details, see Access Control.