Het Werkbon datamodel bestaat uit drie Custom Post Types (file, job, contact), vier ACF veldgroepen, een custom database tabel voor adressen, en zes postmeta indexes.
┌──────────────┐ file-job ┌──────────────┐
│ FILE │ ──────────────→ │ JOB │
│ (werkbon) │ │ (melding) │
└──────┬───────┘ └──────┬───────┘
│ file-contact │ job-contact
└──────────┐ ┌──────────────────┘
│ CONTACT │ ←── moneybird_id ──→ Moneybird
│ USER │ ←── file-employer / job-employer
register_post_type ( ' file ' , [
' publicly_queryable ' => false ,
' supports ' => [ ' title ' , ' custom-fields ' ],
' exclude_from_search ' => true ,
register_post_type ( ' job ' , [
' publicly_queryable ' => false ,
' supports ' => [ ' title ' , ' custom-fields ' ],
' taxonomies ' => [ ' category ' ],
' delete_with_user ' => false ,
register_post_type ( ' contact ' , [
' publicly_queryable ' => false ,
' supports ' => [ ' title ' , ' custom-fields ' ],
' delete_with_user ' => false ,
Veldnaam Type Format / Opties Toelichting file-date date_picker Display: d/m/Y, Return: Ymd Datum werkbon file-firstname text Voornaam klant file-lastname text Achternaam klant file-company text Bedrijfsnaam file-email email Email klant file-phone text Telefoon (E.164 na normalisatie) file-zipcode text Postcode (1234AB) file-streetname text Straatnaam file-number text Huisnummer file-addition text Toevoeging file-city text Stad file-description text Leesbare werkzaamheden file-description-json textarea JSON array werkzaamheden file-type select maasdelta, particulier, zakelijk Klanttype file-employer user (relationship) Multiple Monteur(s) file-material textarea Leesbaar: “qty x label” per regel file-material-json textarea JSON: [{id, label, qty}] file-note textarea Interne notitie file-contact post_object → contact CPT Gekoppeld contact file-job post_object → job CPT Gekoppelde melding
Veldnaam Type Format / Opties Toelichting job-status select 0=Openstaand, 1=In behandeling, 2=Afgerond Status job-type select maasdelta, particulier, zakelijk Klanttype job-date date_picker Display: d/m/Y, Return: Ymd Planningsdatum (optioneel) job-firstname text Voornaam job-lastname text Achternaam job-company text Bedrijfsnaam job-email email Email job-phone text Telefoon job-zipcode text Postcode job-streetname text Straatnaam job-number text Huisnummer job-addition text Toevoeging job-city text Stad job-description textarea Beschrijving job-note textarea Interne notitie job-employer user (relationship) Multiple Monteur(s) job-contact post_object → contact CPT Gekoppeld contact job-address-custom true/false Custom adres vlag
Veldnaam Type Format / Opties Toelichting contact-type select maasdelta, particulier, zakelijk Klanttype contact-firstname text Voornaam contact-lastname text Achternaam contact-company text Bedrijfsnaam contact-email email Email contact-phone text Telefoon contact-zipcode text Postcode contact-streetname text Straatnaam contact-number text Huisnummer contact-addition text Toevoeging contact-city text Stad contact-note textarea Notitie moneybird_id text Moneybird koppeling ID
Veldnaam Type Format / Opties Toelichting img-profile image Return: array Profielfoto employer true/false Monteur markering admin true/false Admin rechten (delete meldingen)
Zie Werkbon Formulier voor details per veld.
Key Field ID ACF Meta Key Type job_id 58 file-job hidden date 49 file-date date streetname 17 file-streetname hidden (synced) number 52 file-number hidden (synced) addition 19 file-addition hidden (synced) zipcode 20 file-zipcode hidden (synced) city 21 file-city hidden (synced) description 5 file-description textarea description_json 53 file-description-json hidden material 37 file-material textarea material_json 55 file-material-json hidden company 68 file-company text firstname 69 file-firstname text lastname 70 file-lastname text email 71 file-email email phone 82 file-phone text contact_id 78 file-contact hidden type 74 file-type select employer 10 file-employer checkbox note 28 file-note textarea
Zie Melding Formulier voor details per veld.
Key Field ID ACF Meta Key Type job_id 77 — hidden (update detect) date 49 job-date date streetname 17 job-streetname hidden (synced) number 52 job-number hidden (synced) addition 19 job-addition hidden (synced) zipcode 20 job-zipcode hidden (synced) city 21 job-city hidden (synced) description 28 job-description textarea company 70 job-company text firstname 67 job-firstname text lastname 68 job-lastname text email 69 job-email email phone 83 job-phone text contact_id 79 job-contact hidden type 76 job-type select status 61 job-status select employer 10 job-employer checkbox
Kolom Type Constraint Toelichting id bigint(20) PK, AUTO_INCREMENT streetname varchar(255) NOT NULL Straatnaam house_number varchar(20) NOT NULL Huisnummer addition varchar(20) DEFAULT ” Toevoeging zipcode varchar(10) NOT NULL Postcode (1234AB) city varchar(100) NOT NULL Stad is_maasdelta tinyint(1) DEFAULT 0 Maasdelta woningcorporatie
Naam Kolommen Type addr_unique (zipcode, house_number, addition) UNIQUE idx_zipcode (zipcode) INDEX idx_streetname (streetname(50)) INDEX idx_street_city_md (streetname(50), city(30), is_maasdelta) INDEX
Zie Adressysteem voor lookup functies.
Index Naam Meta Key Index Type idx_moneybird_id moneybird_id (meta_key, meta_value(100)) idx_contact_zipcode contact-zipcode (meta_key, meta_value(100)) idx_contact_streetname contact-streetname (meta_key, meta_value(100)) idx_contact_email contact-email (meta_key, meta_value(100)) idx_contact_type contact-type (meta_key, meta_value(100)) idx_contact_city contact-city (meta_key, meta_value(100))
Beheerbaar via Admin > Tools > DB Optimalisatie. Zie Admin Interface .
"main" : " Riool ontstopt met hogedruk " ,
"date_display" : " 05-04-2026 " ,
"extra_display" : " 1,5 uur " ,
"locations" : [ " Keuken " , " Badkamer " ],
"works" : [ " Hogedruk reinigen " ],
"extra_date" : " 2026-04-05 "
Keuken, Badkamer: Riool ontstopt met hogedruk (05-04-2026 - 1,5 uur)
{ "id" : " mat_001 " , "label" : " PVC Buis 110mm " , "qty" : 2 },
{ "id" : " mat_042 " , "label" : " Ontstopperveer 16mm " , "qty" : 1 }
Adresvelden in formulieren werken via een drielaags systeem:
Laag 1: Select2 dropdowns (zichtbaar voor gebruiker)
├─ Field 23: Straat select → AJAX search, min 2 chars
├─ Field 24: Nummer select → Gevuld na straat keuze
└─ Field 25: Toevoeging select → Gevuld na nummer keuze
Laag 2: Auto-filled readonly velden
├─ Field 26: Postcode → Auto na nummer/toevoeging
└─ Field 27: Stad → Auto na nummer/toevoeging
Laag 3: Hidden sync velden (opgeslagen naar ACF)
├─ Field 17 → file-streetname / job-streetname
├─ Field 52 → file-number / job-number
├─ Field 19 → file-addition / job-addition
├─ Field 20 → file-zipcode / job-zipcode
└─ Field 21 → file-city / job-city
Fallback: Custom address (field 22 toggle)
├─ Field 2 → Vrij straatveld
├─ Field 15 → Vrij nummerveld
├─ Field 14 → Vrij toevoegingveld
├─ Field 13 → Vrij postcodeveld
└─ Field 4 → Vrij stadsveld
De sync van Laag 1 → Laag 3 wordt afgehandeld door wpforms-werkbon.js. Zie Adressysteem voor de Select2 AJAX chain.
Beide formulieren genereren dezelfde titel structuur:
$date_str = wp_date ( ' Ymd ' );
$address_parts = array_filter ([$ street , $ number , $ addition ]);
$address = implode ( ' ' , $ address_parts );
if ( $city ) $address .= ' , ' . $city ;
? ( $date_str . ' : ' . $address ) // "20260405: Hoofdstraat 42 A, Rotterdam"
: ( $date_str . ' : Werkbon ' ); // "20260405: Werkbon" (fallback)
Centrale functie die form velden naar ACF meta schrijft:
function beam_save_form_values_to_meta (
$map = beam_get_field_map ($ form_id );
foreach ( $map as $key => $config ) {
if ( in_array ($ key , $ exclude , true )) continue ;
if ( $config [ ' meta_key ' ] === null ) continue ; // Geen meta key = niet opslaan
$value = beam_get_wpforms_field_value ($ fields , $ config [ ' field_id ' ]);
if ( $value === '' ) continue ;
beam_update_post_meta ($ post_id , $ config [ ' meta_key ' ], $ value );
Velden met meta_key: null (Select2 bronvelden, auto-filled velden, custom address velden) worden overgeslagen — alleen de hidden sync velden worden opgeslagen.
Mechanisme Doel Implementatie Contact upsert cache Voorkomt dubbele contacten in 1 request Static variabele per form:entry Webhook debounce Voorkomt dubbele Make.com calls Post meta timestamp (3 sec window) Employer array parsing Voorkomt string i.p.v. array Filter op wpforms_post_submissions_process_meta Placeholder filtering Voorkomt “Selecteer…” als waarde Filter op wpforms_process_filter (priority 5) Postcode normalisatie Consistente opslag strtoupper + spaties verwijderen Email normalisatie Consistente matching strtolower + trim
In de nieuwe Beam stack worden alle CPTs en ACF velden vervangen door Supabase tabellen (contacts, jobs, work_orders, activity_log). Belangrijke verbeteringen:
Geen data duplicatie: contact/adres data alleen op contact, rest via FK
Contact overerving: werkbon erft contact van melding, kan optioneel overschrijven
Adres overerving: melding erft adres van contact, kan optioneel overschrijven
Activity log: elke wijziging wordt gelogd (wie, wanneer, wat)
Uitgebreid status model: 7 statussen i.p.v. 3
Auto-save draft: werkbonnen als concept opslaan
Zie Migratie naar Beam Stack voor het volledige schema, RLS policies, en de contact/adres overerving patronen.
Bestand Locatie Functie posttypes.php functions/ CPT registratie wpforms-field-mappings.php functions/ Centrale field ID ↔ ACF key mapping wpforms-create-file-post.php functions/ FILE CPT aanmaken + save meta wpforms-update-job-post-submission.php functions/ JOB CPT aanmaken/updaten wpforms-add-or-update-contact.php functions/ Contact upsert + matching db-addresses.php functions/ Address tabel management db-optimize.php functions/ Postmeta indexes group_5c7027cd8e1d8.json acf-json/ Werkbonnen veldgroep group_693b783dd764b.json acf-json/ Meldingen veldgroep group_694e8176243b2.json acf-json/ Contact veldgroep group_5e14d7eb790f3.json acf-json/ Gebruiker veldgroep
Code Label Kleur Beschrijving 0 Openstaand Rood Nieuwe melding 1 In behandeling Oranje Toegewezen/in uitvoering 2 Afgerond Groen Werkbon ingevuld
Code Label Moneybird Sync Speciale Regels maasdelta Maasdelta Nee Auto-bepaald op adres particulier Particulier Ja Standaard zakelijk Zakelijk Ja Company veld gevuld