Skip to content

Melding Formulier (Form 3493)

Form 3493 is het WPForms-formulier waarmee gebruikers meldingen aanmaken en bewerken. Een melding is een job custom post type dat een klantvraag of opdracht vertegenwoordigt voordat er een werkbon (Form 40) aan gekoppeld wordt.

Template: page-jobs-form.php Post type: job Server-side handler: functions/wpforms-update-job-post-submission.php

Het formulier kent twee modi:

ModusConditieGedrag
CREATEGeen job_id in URLMaakt een nieuw job post aan
EDIT?job_id=123 in URLWerkt bestaand job post bij

De template detecteert de modus via $_GET['job_id'] (of legacy $_GET['wpf3493_77']) en past de paginatitel aan: “Melding bewerken” bij edit, de WordPress-paginatitel bij create.


Alle veldmappings zijn gedefinieerd in functions/wpforms-field-mappings.php via de functie beam_get_form3493_field_map(). Velden met meta_key = null zijn invoervelden die niet direct naar de database schrijven (bijvoorbeeld Select2-bronvelden die synchroniseren naar de eindvelden).

Mapping KeyField IDMeta KeyTypeLabel
job_id77nullintMelding ID
Mapping KeyField IDMeta KeyTypeLabel
date49job-datestringDatum
Mapping KeyField IDMeta KeyTypeLabel
streetname17job-streetnamestringStraatnaam
number52job-numberstringHuisnummer
addition19job-additionstringToevoeging
zipcode20job-zipcodestringPostcode
city21job-citystringPlaats

Deze velden dienen als bron voor de adres-sync. Ze worden niet rechtstreeks naar post meta geschreven.

Mapping KeyField IDMeta KeyTypeLabel
street_select23nullstringStraat (Select2)
number_select24nullstringHuisnummer (Select2)
addition_select25nullstringToevoeging (Select2)
zipcode_auto26nullstringPostcode (auto)
city_auto27nullstringPlaats (auto)

Adres — custom invoervelden (handmatige invoer)

Section titled “Adres — custom invoervelden (handmatige invoer)”

Wanneer een adres niet in de CSV-dataset voorkomt, kan de gebruiker handmatig een adres invoeren via de “Nieuw adres”-checkbox.

Mapping KeyField IDMeta KeyTypeLabel
custom_address_toggle22nullarrayNieuw adres checkbox
street_custom2nullstringStraat (custom)
number_custom15nullstringHuisnummer (custom)
addition_custom14nullstringToevoeging (custom)
zipcode_custom13nullstringPostcode (custom)
city_custom4nullstringPlaats (custom)
Mapping KeyField IDMeta KeyTypeLabel
description28job-descriptionstringMelding
note59job-notestringNotitie
Mapping KeyField IDMeta KeyTypeLabel
company70job-companystringBedrijfsnaam
firstname67job-firstnamestringVoornaam
lastname68job-lastnamestringAchternaam
email69job-emailstringE-mailadres
phone83job-phonestringTelefoonnummer
Mapping KeyField IDMeta KeyTypeLabel
contact_id79job-contactintContact ID
Mapping KeyField IDMeta KeyTypeLabel
type76job-typestringType
status61job-statusstringStatus
employer10job-employerarrayMonteur

Het veld moneybird_id (field ID 82) is een hidden field dat niet in de field mapping staat. Het wordt client-side gesynchroniseerd via form-prefill-helpers.js vanuit de geselecteerde contact-optie (zie Moneybird ID Sync).


De submit-verwerking verloopt identiek aan Form 40 qua filterchain, maar wijkt af in de wpforms_process_complete handler.

De standaard WPForms filters worden doorlopen voor validatie en data-transformatie. Relevante custom filters (dezelfde als bij Form 40):

  1. wpforms_process_filter — validatie
  2. wpforms_process_format — data formatting
  3. wpforms_process_complete — post aanmaak/update (hieronder beschreven)

Handler: wpforms-update-job-post-submission.php

Section titled “Handler: wpforms-update-job-post-submission.php”

De handler luistert op wpforms_process_complete met prioriteit 10 en filtert op $form_id === 3493.

add_action('wpforms_process_complete', function ($fields, $entry, $form_data, $entry_id) {
$form_id = (int) ($form_data['id'] ?? 0);
if ($form_id !== 3493) return;
$map = beam_get_form3493_field_map();
$job_id = (int) beam_get_wpforms_field_value($fields, $map['job_id']['field_id']);
$is_update = $job_id > 0;
// ... CREATE of EDIT flow
}, 10, 4);

Wanneer field 77 (job_id) leeg is, maakt de handler een nieuw job post aan.

  1. Titel genereren: YYYYMMDD: Straat 10 A, Stad

    • Datum via wp_date('Ymd')
    • Adres samengesteld uit streetname, number, addition, city
    • Fallback: YYYYMMDD: Melding als geen adres beschikbaar
  2. Post aanmaken: wp_insert_post() met:

    [
    'post_type' => 'job',
    'post_status' => 'publish',
    'post_title' => $title,
    'post_author' => get_current_user_id(),
    ]
  3. Meta opslaan: beam_save_form_values_to_meta(3493, $job_id, $fields, ['job_id', 'contact_id', 'status'])

    • Slaat alle velden op behalve job_id, contact_id en status
    • Placeholder-waarden (..., Selecteer straat..., Selecteer...) worden automatisch gefilterd
  4. Default status: job-status wordt gezet op '0' (Openstaand) tenzij het formulier expliciet een andere waarde meestuurt

  5. Contact upsert + link: via hra_upsert_contact_from_wpforms_cached()

    • Maakt een nieuw contact aan of vindt een bestaand contact op basis van de formulierdata
    • Koppelt het contact aan de job via hra_set_relationship_field($job_id, 'job-contact', $contact_id)
  6. Cache clearing: clean_post_cache() en wp_cache_delete() voor post meta

  7. ACF trigger: do_action('acf/save_post', $job_id) — activeert webhooks en andere ACF-afhankelijke logica


Wanneer field 77 (job_id) een positief getal bevat, werkt de handler het bestaande post bij.

Voordat wijzigingen worden toegepast, worden drie checks uitgevoerd:

$post = get_post($job_id);
if (!$post || $post->post_type !== 'job') return; // Post moet bestaan en type 'job' zijn
if (!current_user_can('edit_post', $job_id)) return; // Gebruiker moet rechten hebben

Alle meta-velden worden bijgewerkt behalve:

  • job_id — post identificatie, wordt niet overschreven
  • contact_id — wordt apart afgehandeld via contact upsert
  • status — wordt apart afgehandeld via status logica (zie hieronder)

De statusbehandeling bij edit bevat een beschermingsmechanisme dat voorkomt dat een bestaande status onbedoeld wordt overschreven:

$date_val = beam_get_wpforms_field_value($fields, $map['date']['field_id']);
$status_val = beam_get_wpforms_field_value($fields, $map['status']['field_id']);
$current_status = get_post_meta($job_id, 'job-status', true);
if ($date_val !== '' && $current_status !== '' && $current_status !== false) {
if ($status_val === '' || $status_val === '0') {
$status_val = $current_status; // Behoud originele status
}
}
beam_update_post_meta($job_id, 'job-status', $status_val);

Logica samengevat:

Datum ingevuld?Originele status bestaat?Formulier statusResultaat
JaJaLeeg of 0Behoud originele status
JaJaAndere waardeGebruik formulier status
Neen.v.t.Elke waardeGebruik formulier status
JaNee (leeg/false)Elke waardeGebruik formulier status

Identiek aan het CREATE flow: hra_upsert_contact_from_wpforms_cached() gevolgd door hra_set_relationship_field(). Na een succesvolle update worden dezelfde cache clearing en ACF trigger stappen uitgevoerd.


Bestand: functions/wpforms-edit-job.php

Dit bestand luistert op de wpforms_frontend_form_data filter en past het formulier aan wanneer een job_id in de URL staat.

add_filter('wpforms_frontend_form_data', function ($form_data) {
if ((int) $form_data['id'] !== 3493) return $form_data;
$job_id = isset($_GET['job_id']) ? absint($_GET['job_id']) : 0;
if (!$job_id) {
$job_id = isset($_GET['wpf3493_77']) ? absint($_GET['wpf3493_77']) : 0;
}
if ($job_id > 0) {
$form_data['settings']['submit_text'] = 'Melding bewerken';
}
return $form_data;
});
AspectCREATE modusEDIT modus
PaginatitelWordPress-paginatitel”Melding bewerken”
Submit knopStandaard WPForms tekst”Melding bewerken”
VeldenLeegVoorgevuld met bestaande data
Hidden job_id fieldLeegBevat post ID

Wanneer het formulier in edit modus wordt geladen, worden de bestaande waarden van de melding ingevuld via een combinatie van PHP (server-side) en JavaScript (client-side).

Server-side prefill: beam_get_job_prefill_data()

Section titled “Server-side prefill: beam_get_job_prefill_data()”

Bestand: functions/form-prefill.php

Deze functie haalt alle relevante meta-velden op van het job post en converteert ze naar WPForms field IDs:

$values = beam_get_job_prefill_data($job_id, 3493);

De functie:

  1. Valideert dat $job_id een bestaand job post is
  2. Haalt alle ACF/meta-velden op (met fallback naar get_post_meta())
  3. Mapt de waarden naar de juiste WPForms field IDs
  4. Probeert het adres te resolven via beam_resolve_address_php() voor Select2-velden
  5. Slaat resolved adresdata op in _resolved_address voor JavaScript

Bestand: assets/js/modules/form-prefill-helpers.js

Drie client-side prefill-mechanismen zijn actief voor Form 3493:

Vult het toevoegingsveld (field 25 of 14) in na het laden van de Select2-opties:

  • Leest wpf3493_14 (custom) of wpf3493_25 (select) uit query parameters
  • Wacht via PrefillManager.waitForElement() tot het veld beschikbaar is
  • Bij select-velden: wacht tot minimaal 2 opties geladen zijn via PrefillManager.waitForSelectOptions()
  • MutationObserver bewaakt dynamisch geladen opties
  • Re-apply na wijzigingen in gerelateerde velden (straat, huisnummer)

Het datumveld (field 49) wordt via twee methoden voorgevuld:

MethodeTriggerBestand
PHP prefillBEAM_DATE_PREFILL constantewpforms-custom-date-field.php
JS legacy?file_date= query paramform-prefill-helpers.js

De JS-methode normaliseert datumformaten (Ymd, dd-mm-yyyy, dd/mm/yyyy) naar ISO 8601 en past de waarde toe via Flatpickr’s setDate() API. Er wordt tot 20 keer ge-retry (elke 200ms) totdat Flatpickr geïnitialiseerd is.

Synchroniseert het Moneybird ID (field 82) vanuit de geselecteerde contact-optie:

initMoneybirdFill(3493, 79, 82);
// 3493 = form ID
// 79 = wpContactIdFieldId (contact_id)
// 82 = moneybirdFieldId (moneybird_id hidden field)
  • Leest data-moneybird-id van de geselecteerde optie in de contact-zoekwidget
  • Luistert op change events van .field-search-contact select en #klant-zoek-select
  • Past de waarde toe op het hidden moneybird-veld via PrefillManager.setValue() met force: true

Form 3493 gebruikt dezelfde client-side modules als Form 40, met uitzondering van file-overview-sync (dat is specifiek voor werkbonnen).

Verschil in field IDs voor lock-mechanisme

Section titled “Verschil in field IDs voor lock-mechanisme”

De locked fields (velden die na contact-selectie niet meer handmatig gewijzigd mogen worden) verschillen per formulier:

ModuleForm 3493 (melding)Form 40 (werkbon)
contact_idField 79Field 78
companyField 70Field 68
firstnameField 67Field 69
lastnameField 68Field 70
emailField 69Field 71
phoneField 83Field 82

Bestand: partials/show-jobs.php (584 regels) URL: /meldingen/

De lijstpagina toont alle meldingen in een gefilterde, gepagineerde tabel met directe modal-interactie.

Het filterformulier biedt drie filtermogelijkheden:

FilterTypeOptiesDefault
StatusCheckboxes (meervoudige selectie)0 = Openstaand, 1 = In behandeling, 2 = AfgerondAlle (geen filter)
TypeSelect (enkelvoudige selectie)maasdelta, particulier, zakelijkAlle types
Per paginaSelect50, 100, 250, 50050

De status-filter ondersteunt meervoudige selectie. Elke combinatie genereert een dynamische paginatitel:

  • Geen selectie: “Alle meldingen”
  • Een status: “Openstaande meldingen”, “Meldingen in behandeling”, “Afgeronde meldingen”
  • Meerdere statussen: “Meldingen: openstaand + in behandeling”
$args = [
'post_type' => 'job',
'post_status' => 'publish',
'posts_per_page' => $per_page,
'paged' => $pagina,
'orderby' => [
'meta_value_num' => 'DESC',
'date' => 'DESC', // fallback voor jobs zonder inplandatum
],
'meta_query' => [
'relation' => 'AND',
// job-date mag leeg zijn (optioneel veld)
[
'relation' => 'OR',
['key' => 'job-date', 'compare' => 'EXISTS'],
['key' => 'job-date', 'compare' => 'NOT EXISTS'],
],
// Status filter (IN voor meervoudige selectie, = voor enkelvoudig)
'status_clause' => [
'key' => 'job-status',
'value' => $selected_statuses,
'compare' => count($selected_statuses) > 1 ? 'IN' : '=',
],
// Type filter (alleen als geselecteerd)
'type_clause' => [
'key' => 'job-type',
'value' => $selected_type,
'compare' => '=',
],
],
];

De primaire sortering is op meta_value_num (descending), wat in de praktijk sorteert op job-date. Jobs zonder inplandatum komen bovenaan dankzij de fallback-sortering op date (aanmaakdatum, descending).

KolomCSS-klasseInhoud
Statusmeldingen-column--statusGekleurde stip (status-0, status-1, status-2)
Meldingmeldingen-column--date-createdAanmaakdatum (dd-mm-yyyy) + dagnaam
Ingeplandmeldingen-column--date-scheduledInplandatum (dd-mm-yyyy) + dagnaam, of -
Adresmeldingen-column--streetnameStraat + nummer + toevoeging, stad
Omschrijvingmeldingen-column--descriptionEerste regel + “(en meer)” bij meerdere regels
Typemeldingen-column--typeLabel: Maasdelta, Particulier, Zakelijk
Monteurmeldingen-column--employerProfielfoto(‘s) met naam-tooltip
Actiesmeldingen-column--actionsKnop die de modal opent

De lijstpagina bevat drie bulkcache-optimalisaties die N+1 query-problemen voorkomen:

// 1. Bulk meta cache voor alle jobs
$job_ids = wp_list_pluck($jobs->posts, 'ID');
update_postmeta_cache($job_ids);
// 2. Bulk meta cache voor alle gekoppelde contacten
$contact_ids = [...]; // Verzameld uit job-contact meta
update_postmeta_cache(array_unique($contact_ids));
// 3. Bulk user cache voor auteurs
$author_ids = wp_list_pluck($jobs->posts, 'post_author');
cache_users(array_unique($author_ids));

Daarnaast worden werknemersgegevens eenmalig geladen via een transient-cached index:

$allEmployersIndex = get_transient('acs_employers_index');
// Vernieuwing: elke 12 uur

De tabel itereert direct over $jobs->posts in plaats van de WordPress Loop (the_post()) om overhead te vermijden.

De lijstpagina gebruikt pagina als URL-parameter (niet WordPress’ standaard paged):

/meldingen/?status[]=0&status[]=1&type=maasdelta&per_page=50&pagina=2

Paginatielinks worden gegenereerd met paginate_links() en behouden alle actieve filters.

Datumweergave gebruikt IntlDateFormatter met locale nl_NL en timezone Europe/Amsterdam:

$fmtLong = new IntlDateFormatter('nl_NL', ..., 'EEEE'); // "Maandag"
$fmtShort = new IntlDateFormatter('nl_NL', ..., 'EEE'); // "Ma"

Dagnamen worden altijd met hoofdletter weergegeven via ucfirst().

Alle $_GET parameters doorlopen strikte sanitization:

ParameterSanitization
status[]sanitize_text_field() + in_array() whitelist ('0', '1', '2')
typesanitize_key() + in_array() whitelist
per_page(int) cast + in_array() whitelist (50, 100, 250, 500)
pagina(int) cast + minimum 1

Elke tabelrij bevat een <button class="melding-open-modal"> met uitgebreide data-* attributen die alle meldinggegevens bevatten voor de modal. Dit voorkomt extra AJAX-verzoeken bij het openen van de modal.

Data-attributen per rij:

AttribuutBron
data-job-idPost ID
data-date, data-day, data-date-ymdInplandatum
data-firstname, data-lastname, data-email, data-phone, data-companyContactgegevens
data-street, data-number, data-addition, data-zipcode, data-cityAdresgegevens
data-description, data-noteInhoud
data-employers, data-employee-idsMonteurs (JSON + CSV)
data-status, data-status-label, data-type, data-type-labelStatus en type
data-created-date, data-created-time, data-created-userAanmaakgegevens
data-wp-contact-id, data-moneybird-idContact relaties

Bestand: assets/js/modules/melding-modal.js (820 regels)

De modal opent bij klik op een tabelrij en toont alle details van een melding. Vanuit de modal zijn vier acties beschikbaar: status wijzigen, melding verwijderen, werkbon invullen en melding bewerken.

De modal wordt geopend door openModal(btn) waarbij btn de geklikte <button> is. Alle data wordt gelezen uit de data-* attributen van de knop. De modal sluit via:

  • Klik op close-knop
  • Klik op backdrop
  • Escape-toets

Bij sluiten wordt alle modal-content gereset via resetModalContent() (met een 250ms delay).

De status-dropdown is een <select> element dat geïnitialiseerd wordt als Select2-widget:

$sel.select2({
width: 'resolve',
minimumResultsForSearch: Infinity, // Geen zoekbalk
dropdownParent: $(modal.querySelector('.melding-modal__dialog'))
});

Bij wijziging van de selectie verschijnt een “Opslaan” knop. De knop is disabled wanneer de gekozen status gelijk is aan de huidige status.

AJAX save flow:

  1. Verstuur POST naar admin-ajax.php met action update_job_status
  2. Parameters: nonce, post_id, status
  3. Bij succes:
    • Update data-currentStatus op het select-element
    • Update het status-label en de kleur in de modal
    • Update de kleurenstip in de tabelrij
    • Update de ingeplande datum in de tabel als deze via de response meekomt
    • Herbereken de “Werkbon invullen” link-zichtbaarheid
    • Herinitialiseer Select2 met de nieuwe waarde
  4. Toon “Opgeslagen.” feedback (verdwijnt na 2 seconden)

De verwijder-knop stuurt een AJAX POST met action delete_job:

  1. Bevestigingsdialoog: window.confirm('Weet je zeker dat je deze melding wilt verwijderen?')
  2. Parameters: nonce (uit data-delete-nonce), post_id
  3. Bij succes: modal sluiten + tabelrij verwijderen uit DOM
  4. Bij fout: alert() met foutmelding

De prefill-knop genereert een URL naar Form 40 (werkbon) met het job_id:

/werkbon-invullen?job_id=123

De PHP-backend haalt alle benodigde data direct uit de database op basis van het job_id.

Zichtbaarheidsregel: de knop is verborgen wanneer status === '2' (Afgerond). Er is geen werkbon meer nodig voor afgeronde meldingen.

De edit-knop genereert een URL terug naar Form 3493 in edit modus:

/melding-aanmaken/?job_id=123

De basis-URL wordt gelezen uit modal.dataset.editUrl (fallback: /melding-aanmaken/).

Telefoonnummers worden opgeslagen in E.164-formaat (+31612345678) en in de modal geconverteerd naar leesbaar Nederlands formaat:

E.164WeergaveType
+3161234567806-12345678Mobiel
+31101234567010-1234567Vast (2-cijferig netnummer)
+3118012345670180-1234567Vast (3-cijferig netnummer)

De functie formatNlPhoneFromE164() bevat een complete set van Nederlandse 2-cijferige netnummers voor correcte formattering. Bij ongeldige nummers (niet +31 + 9 cijfers) wordt een lege string geretourneerd en het nummer als platte tekst weergegeven.

Wanneer een geldig nummer gedetecteerd wordt, wordt het <span> element vervangen door een <a href="tel:..."> element zodat het nummer klikbaar is op mobiele apparaten.

Na een succesvolle statuswijziging via AJAX worden de volgende elementen in de lijsttabel direct bijgewerkt (zonder page refresh):

  1. Status-stip: CSS-klasse wordt gewijzigd (status-0 / status-1 / status-2)
  2. Data-attributen: data-status en data-status-label op de modal-trigger knop
  3. Ingeplande datum: als de AJAX-response job_date bevat, wordt de datumcel bijgewerkt inclusief dagnaam (berekend client-side uit job_date_ymd)

BestandLocatieFunctie
page-jobs-form.phpTheme rootTemplate voor het meldingformulier
wpforms-update-job-post-submission.phpfunctions/Server-side create/update handler
wpforms-edit-job.phpfunctions/Edit mode detection + submit button tekst
wpforms-field-mappings.phpfunctions/Centrale field mapping definities
form-prefill.phpfunctions/Server-side prefill data ophalen
form-prefill-helpers.jsassets/js/modules/Client-side prefill (addition, date, moneybird)
show-jobs.phppartials/Meldingen lijstpagina
melding-modal.jsassets/js/modules/Modal interactie en AJAX acties