Skip to content

Standardize Database Table and Field Naming Conventions With Their Backend Application Mappings

Problem Description

Please Note: Several tasks to prune unused tables and columns from the database are already under way. SMS/Notification features will be scrubbed in those issues.

The current database schema suffers from inconsistent naming conventions that create maintenance challenges and reduce code clarity.

"The following comes from experience in the project and is not a simple suggestion from ai. Cleaning this up would significantly reduce long-term technical debt. I struggled very much the first 6-9 months trying to roughly map all this in my mind." @ThomasAFink

Hier schreiben.

Hier schreiben.

Hier schreiben.

Investment: Standardize schema and update queries
Return:
- Faster developer onboarding (saves months per new dev)
- Reduced debugging time (saves hours per issue)
- Improved development velocity (saves time on every backend feature)
- Reduced bug introduction (prevents costly production issues and hotfixes)
- Faster refactoring of other parts later
- Easier test writing


The issues include:

1. Mixed Language Usage (German ↔ English)

  • Table names: oeffnungszeit (German) vs availability (English concept)
  • Table names: standort (German) vs scope (English concept)
  • Table names: buerger (German) vs citizen (English concept)
  • Table names: feiertage (German) vs holidays (English concept)

2. Inconsistent Naming Conventions

  • Mix of camelCase and snake_case: StandortID vs scope_id
  • Mixed conventions within same table: contact__name vs contact__email vs StandortID

3. Conceptual Inconsistencies

  • Same concept with different names: availability/oeffnungszeit, scope/standort
  • Query classes use English names but map to German table names

Current Examples

Availability Query Class:

php
const TABLE = 'oeffnungszeit';  // German table name
// But maps to English field names:
'id' => 'availability.OeffnungszeitID',
'scope__id' => 'availability.StandortID',

Scope Query Class:

php
const TABLE = 'standort';  // German table name
// Maps to mixed naming:
'id' => 'scope.StandortID',
'contact__name' => 'scopeprovider.name',

Proposed Solution

Standardization Rules:

  1. Language: Convert all table and column names to English
  2. Database Convention: Use snake_case for all table and column names
  3. Mapping Convention: Use camelCase for all mapping variables in Query classes
  4. Consistency: Align concept naming across the entire schema

Migration Plan:

  1. Phase 1: Table Renaming

    • oeffnungszeitavailability
    • standortscope
    • buergercitizen
    • feiertageholidays
    • gesamtkalendercalendar
  2. Phase 2: Column Standardization

    • Convert all column names to snake_case
    • Standardize foreign key naming: StandortIDscope_id
    • Align field naming: OeffnungszeitIDavailability_id
  3. Phase 3: Query Class Updates

    • Update all Zmsdb/Query/* classes to use new table/column names
    • Ensure variable mapping uses camelCase: scopeIdavailability.scope_id
    • Update entity mapping methods

Expected Outcomes

  • Maintainability: Consistent naming reduces cognitive load
  • Clarity: English names improve international collaboration
  • Alignment: Query classes match actual database schema
  • Future-proofing: Standardized conventions for new development
  • Testability: Easier to write and maintain tests with consistent naming

Implementation Notes

  • Create comprehensive migration scripts for each table
  • Update all affected query classes in zmsdb/src/Zmsdb/Query/
  • Ensure backward compatibility during transition period
  • Update documentation and API references

Example After Standardization

Availability Query Class (After):

php
const TABLE = 'availability';  // English snake_case table name
// Maps to camelCase variables:
'id' => 'availability.availability_id',
'scopeId' => 'availability.scope_id',
'startDate' => 'availability.start_date',

Scope Query Class (After):

php
const TABLE = 'scope';  // English snake_case table name
// Maps to camelCase variables:
'id' => 'scope.scope_id',
'contactName' => 'scopeprovider.name',

This approach ensures:

  • Database: All snake_case (standard SQL convention)
  • Query Mappings: All camelCase (standard PHP convention)
  • Consistency: No exceptions, everything follows the same pattern

Four phases:

  1. Complete Table Renaming Plan - All English, All snake_case easy
  • 47 tables converted from German to English
  • All table names standardized to snake_case
  • Organized by business priority (Core → User → System → Technical)
  • Clear migration path with examples
  1. Complete Column Renaming Plan - All English, All snake_case easy-medium
  • Hundreds of columns converted from German to English
  • All column names standardized to snake_case
  • Foreign key naming standardized across all tables
  • Common patterns identified and standardized
  1. Complete PHP Variable Mapping Renaming Plan - All English, All camelCase hard
  • All Query class mappings converted to camelCase
  • Double underscore patterns eliminated
  • Nested object patterns standardized
  • Reference mappings updated
  1. Long-Term Schema Vision (Beyond Renaming) strategic
  • Structural splits (buerger, queue_number_statistics, preferences, DLDB data columns)
  • Table disposition audit (drop, verify, or redesign)
  • Migration naming hygiene and asset storage (S3 vs image_data)

Sections 1–3 focus on naming consistency. Section 4 captures larger architectural changes that may follow or run in parallel once prerequisites are clear.

Column mappings in section 2 cover all tables in the ZMS schema (see .resources/zms.sql). PHP variable mappings in section 3 are still being expanded table by table.

1. Complete Table Renaming Plan - All English, All snake_case

Phase 1: Core Business Tables (High Priority)

CurrentNew (snake_case)Reason
oeffnungszeitavailabilityOpening hours/availability
standortscopeLocation/scope
buergercitizenCitizen
feiertageholidaysHolidays
gesamtkalendercalendarCalendar
behoerdedepartmentGovernment department
organisationorganizationOrganization

Phase 2: User & Process Tables

CurrentNew (snake_case)Reason
buergeranliegencitizen_requestsCitizen requests/issues
buergerarchivcitizen_archiveArchived citizen data
buergerarchivtoday— (delete)Redundant snapshot table; merge into citizen_archive (see section 4)
nutzeruserSystem users
nutzerzuordnunguser_assignmentUser assignments
kundejurisdictionOwner/jurisdiction (entity/API today: owner)
kundenlinks— (delete)Unused; drop table and related code

Note: The kunde table and the owner entity/API will be renamed to jurisdiction (not customer). The jurisdiction permission (ZMSKVR-1345) already introduces this naming in the permission model; the database rename is planned as part of this refactor.

Note: The kundenlinks table (Favoriten/bookmark links) is no longer used. Planned cleanup: drop the table and remove related code (e.g. Link entity, DB query, admin “Favoriten” UI).

Phase 3: System & Configuration Tables

CurrentNew (snake_case)Reason
abrechnung— (delete)Unused; drop table (see section 4)
ipausnahmenip_exceptionsIP exceptions; verify usage (see section 4)
kioskkioskKiosk (universal term)
wartenrstatistikqueue_number_statisticsQueue statistics; normalize into smaller tables (see section 4)
standortclusterlocation_clusterLocation clustering
statistikstatisticsStatistics
roleroleRBAC roles (already snake_case)
permissionpermissionRBAC permissions (already snake_case)
role_permissionrole_permissionRole–permission mapping (already snake_case)
user_roleuser_roleUser–role mapping (already snake_case)

Phase 4: API & Technical Tables

CurrentNew (snake_case)Reason
apiclientapi_clientAPI client
apikeyapi_keyAPI key; verify production usage (see section 4)
apiquotaapi_quotaAPI quota; verify production usage (see section 4)

Phase 5: Communication Tables

CurrentNew (snake_case)Reason
emailemailEmail (already snake_case)
smssmsSMS (already snake_case)
mailpartmail_partMail part
mailqueuemail_queueMail queue
mailtemplatemail_templateMail template
notificationqueue— (delete)Verify unused; drop table (see section 4)

Phase 6: Data & Process Tables

CurrentNew (snake_case)Reason
closuresclosuresAlready snake_case
configconfigAlready snake_case
eventlogevent_logEvent log; verify scope of use (see section 4)
imagedataimage_dataImage data; move assets to S3 (see section 4)
loglogSplit data JSON for searchability (see section 4)
migrationsmigrationsAlready snake_case
preferencesscope_preferences / splitScope + system settings; rename and split (see section 4)
process_sequenceprocess_sequenceAlready snake_case
sessiondatasession_dataSession data
sourcesourceAlready snake_case
overview_calendaroverview_calendarOverview calendar (already snake_case)

Phase 7: Service & Provider Tables

CurrentNew (snake_case)Reason
provideroffice (candidate)DLDB location; align with zmscitizenapi office (see section 4)
requestservice (candidate)DLDB service; align with zmscitizenapi service (see section 4)
request_provideroffice_service (candidate)Office–service relation; split data JSON (see section 4)
request_variantservice_variant (candidate)Service variant; review naming with citizen API (see section 4)

Phase 8: Slot System Tables

CurrentNew (snake_case)Reason
slotslotAlready snake_case
slot_hieraslot_hierarchySlot hierarchy
slot_processslot_processAlready snake_case
slot_sequenceslot_sequenceAlready snake_case

Phase 9: Assignment & Clustering Tables

CurrentNew (snake_case)Reason
clusterzuordnungcluster_assignmentCluster assignment

2. Complete Column Renaming Plan - All English, All snake_case

Phase 1: Core Business Tables (High Priority)

availability (formerly oeffnungszeit)

Current ColumnNew Column (snake_case)Reason
OeffnungszeitIDavailability_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
Startdatumstart_dateStandardize naming
Endedatumend_dateStandardize naming
allexWochenevery_x_weeksStandardize naming
jedexteWocheevery_other_weekStandardize naming
WochentagweekdayStandardize naming
Anfangszeitstart_timeStandardize naming
Terminanfangszeitappointment_start_timeStandardize naming
Endzeitend_timeStandardize naming
Terminendzeitappointment_end_timeStandardize naming
Timeslottime_slotStandardize naming
Anzahlarbeitsplaetzeworkstation_countStandardize naming
Anzahlterminarbeitsplaetzeappointment_workstation_countStandardize naming
kommentarcommentStandardize naming
reduktionTermineImInternetinternet_reductionStandardize naming
erlaubemehrfachslotsmultiple_slots_allowedStandardize naming
reduktionTermineCallcentercallcenter_reductionStandardize naming
Offen_abopen_from_daysStandardize naming
Offen_bisopen_until_daysStandardize naming
updateTimestampupdated_atTimestamp

scope (formerly standort)

Current ColumnNew Column (snake_case)Reason
StandortIDscope_idPrimary/foreign key
BehoerdenIDdepartment_idPrimary/foreign key
InfoDienstleisterIDinfo_provider_idPrimary/foreign key
HinweishintStandardize naming
BezeichnungnameStandardize naming
AdresseaddressStandardize naming
Stadtplanlinkcity_map_linkStandardize naming
Bearbeitungszeitprocessing_timeStandardize naming
KennungidentifierStandardize naming
Termine_abappointments_from_daysStandardize naming
Termine_bisappointments_until_daysStandardize naming
smswarteschlangesms_queueStandardize naming
smswmsbestaetigungsms_wms_confirmationStandardize naming
smsbenachrichtigungsfristsms_notification_deadlineStandardize naming
smsbenachrichtigungstextsms_notification_textStandardize naming
smsbestaetigungstextsms_confirmation_textStandardize naming
wartenrsperrequeue_number_lockedStandardize naming
wartenrhinweisqueue_hintStandardize naming
notruffunktionemergency_functionStandardize naming
notrufausgeloestemergency_triggeredStandardize naming
notrufinitiierungemergency_initiationStandardize naming
notrufantwortemergency_responseStandardize naming
emailPflichtfeldemail_requiredStandardize naming
anmerkungPflichtfeldcomment_requiredStandardize naming
anmerkungLabelcomment_labelStandardize naming
telefonPflichtfeldphone_requiredStandardize naming
standortinfozeilelocation_info_lineStandardize naming
standortkuerzelshort_nameStandardize naming
aufrufanzeigetextdisplay_textStandardize naming
reservierungsdauerreservation_durationStandardize naming
anzahlwiederaufrufrecall_countStandardize naming
startwartenrfirst_queue_numberStandardize naming
endwartenrlast_queue_number_limitStandardize naming
letztewartenrlast_queue_numberStandardize naming
wartenrdatumqueue_number_dateStandardize naming
mehrfachterminemultiple_appointmentsStandardize naming
schreibschutzwrite_protectionStandardize naming
ohnestatistikwithout_statisticsStandardize naming
smskioskangebotsfristsms_kiosk_offer_deadlineStandardize naming
emailstandortadminadmin_emailStandardize naming
wartenummernkontingentqueue_number_contingentStandardize naming
vergebenewartenummernassigned_queue_numbersStandardize naming
kundenbefragungcustomer_surveyStandardize naming
kundenbef_labelcustomer_survey_labelStandardize naming
kundenbef_emailtextcustomer_survey_email_textStandardize naming
telefonaktiviertphone_enabledStandardize naming
virtuellesachbearbeiterzahlvirtual_processor_countStandardize naming
datumvirtuellesachbearbeiterzahlvirtual_processor_count_dateStandardize naming
smsnachtragsms_additionStandardize naming
loeschdauerdeletion_durationStandardize naming
updateTimestampupdated_atTimestamp
sourcesourceAlready snake_case
custom_text_field_labelcustom_text_field_labelAlready snake_case
custom_text_field_activecustom_text_field_activeAlready snake_case
custom_text_field_requiredcustom_text_field_requiredAlready snake_case
admin_mail_on_appointmentadmin_mail_on_appointmentAlready snake_case
admin_mail_on_deletedadmin_mail_on_deletedAlready snake_case
admin_mail_on_updatedadmin_mail_on_updatedAlready snake_case
admin_mail_on_mail_sentadmin_mail_on_mail_sentAlready snake_case
appointments_per_mailappointments_per_mailAlready snake_case
whitelisted_mailswhitelisted_mailsAlready snake_case
slots_per_appointmentslots_per_appointmentAlready snake_case
info_for_appointmentinfo_for_appointmentAlready snake_case
aktivierungsdaueractivation_durationStandardize naming
captcha_activated_requiredcaptcha_activated_requiredAlready snake_case
email_confirmation_activatedemail_confirmation_activatedAlready snake_case
custom_text_field2_labelcustom_text_field2_labelAlready snake_case
custom_text_field2_activecustom_text_field2_activeAlready snake_case
custom_text_field2_requiredcustom_text_field2_requiredAlready snake_case
info_for_all_appointmentsinfo_for_all_appointmentsAlready snake_case
last_display_numberlast_display_numberAlready snake_case
max_display_numbermax_display_numberAlready snake_case
display_number_prefixdisplay_number_prefixAlready snake_case

process (formerly buerger)

Current ColumnNew Column (snake_case)Reason
BuergerIDprocess_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
DatumdateStandardize naming
UhrzeittimeStandardize naming
NamenameStandardize naming
AnmerkungcommentStandardize naming
TelefonnummerphoneStandardize naming
EMailemailStandardize naming
EMailverschicktemail_sent_countStandardize naming
Erinnerungszeitpunktreminder_timestampTimestamp
SMSverschicktsms_sent_countStandardize naming
AnzahlAufrufecall_countStandardize naming
TimestamptimestampTimestamp
IPAdresseip_addressStandardize naming
IPTimeStampip_timestampTimestamp
NutzerIDuser_idPrimary/foreign key
aufruferfolgreichcall_successfulStandardize naming
wsm_aufnahmezeitticket_printer_capture_timeStandardize naming
aufrufzeitcall_timeStandardize naming
nicht_erschienendid_not_appearStandardize naming
Abholerpickup_personStandardize naming
AbholortIDpickup_scope_idPrimary/foreign key
wartenummerqueue_numberStandardize naming
vorlaeufigeBuchungprovisional_bookingStandardize naming
hatFolgeterminefollow_up_appointment_countStandardize naming
istFolgeterminvonfollow_up_of_process_idPrimary/foreign key
zustimmung_kundenbefragungsurvey_acceptedStandardize naming
telefonnummer_fuer_rueckfragencallback_phoneStandardize naming
absagecodeauth_keyStandardize naming
AnzahlPersonenperson_countStandardize naming
updateTimestampupdated_atTimestamp
apiClientIDapi_client_idPrimary/foreign key
custom_text_fieldcustom_text_fieldAlready snake_case
showUpTimeshow_up_timeStandardize naming
finishTimefinish_timeStandardize naming
timeoutTimetimeout_timeStandardize naming
way_timeway_timeAlready snake_case
parkedparkedAlready snake_case
processing_timeprocessing_timeAlready snake_case
bestaetigtconfirmedStandardize naming
waiting_timewaiting_timeAlready snake_case
wasMissedwas_missedStandardize naming
custom_text_field2custom_text_field2Already snake_case
statusstatusAlready snake_case
prioritypriorityAlready snake_case
external_user_idexternal_user_idAlready snake_case
displayNumberdisplay_numberStandardize naming
Dereference payload in Anmerkung / custom text fields (technical debt)

When a process is finished or soft-deleted, Process::writeBlockedEntity() runs QUERY_DEREFERENCED (zmsdb/src/Zmsdb/Query/Process.php). That update clears PII and sets StandortID = 0, Name = 'dereferenced', and status = 'blocked'. Because the row no longer has a usable scope_id, the original scope and metadata are serialized into free-text columns using PHP var_export():

ColumnWritten byPayload shape
AnmerkungProcess::toDerefencedAmendment()BuergerID, StandortID, Anmerkung, IPTimeStamp, LastChange
custom_text_fieldProcess::toDerefencedCustomTextfield()same pattern with CustomTextfield
custom_text_field2Process::toDerefencedCustomTextfield2()same pattern with CustomTextfield2

Example (what remains in Anmerkung after dereference):

array (
  'BuergerID' => 100000,
  'StandortID' => 1,
  'Anmerkung' => NULL,
  'IPTimeStamp' => 0,
  'LastChange' => '1970-01-01T01:00:00+01:00',
)

Where this payload is read back (string parsing, not typed columns):

  • CalculateDailyWaitingStatisticByCron::extractScopeFromAnmerkung() — regex on all three columns when StandortID = 0 (zmsdb/src/Zmsdb/Helper/CalculateDailyWaitingStatisticByCron.php)
  • Ad-hoc SQL in maintenance migrations (e.g. SUBSTRING_INDEX / LIKE on 'StandortID' => in Anmerkung and custom text fields)
  • Any code path that must resolve scope on a dereferenced shell row before the cron deletes it

Why this is bad practice and must not be copied in new schema:

  • Wrong column semantics: Anmerkung / custom text fields are user-facing comment fields, not an archive or audit store.
  • Fragile parsing: Scope and IDs are recovered with regex/SUBSTRING_INDEX on var_export output; a NULL or overwritten StandortID in the string breaks downstream jobs (e.g. archive cron inserting NULL into buergerarchivtoday.StandortID).
  • Duplicated payload: The same structural array is written to three unrelated columns.
  • No schema enforcement: Nothing prevents post-finish updates from corrupting the string or flipping status while StandortID stays 0.

Target direction (when refactoring process / archive):

  • Persist dereference metadata in typed columns or a dedicated process_dereference / audit table (process_id, scope_id, archived_at, …).
  • Stop writing var_export arrays into comment / custom text fields.
  • Remove regex-based scope recovery from cron and statistics paths once shells expose a real FK or archive link.

holidays (formerly feiertage)

Current ColumnNew Column (snake_case)Reason
FeiertagIDholiday_idPrimary/foreign key
DatumdateStandardize naming
FeiertagnameStandardize naming
BehoerdenIDdepartment_idPrimary/foreign key
updateTimestampupdated_atTimestamp

calendar (formerly gesamtkalender)

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
scope_idscope_idAlready snake_case
availability_idavailability_idAlready snake_case
timetimeAlready snake_case
seatseatAlready snake_case
process_idprocess_idAlready snake_case
slotsslotsAlready snake_case
statusstatusAlready snake_case
updated_atupdated_atAlready snake_case

department (formerly behoerde)

Current ColumnNew Column (snake_case)Reason
BehoerdenIDdepartment_idPrimary/foreign key
OrganisationsIDorganization_idPrimary/foreign key
KundenIDjurisdiction_idPrimary/foreign key
NamenameStandardize naming
AdresseaddressStandardize naming
Ansprechpartnercontact_personStandardize naming
IPProtectZeitip_protection_timeStandardize naming

organization (formerly organisation)

Current ColumnNew Column (snake_case)Reason
OrganisationsIDorganization_idPrimary/foreign key
InfoBezirkIDinfo_district_idPrimary/foreign key
KundenIDjurisdiction_idPrimary/foreign key
OrganisationsnamenameStandardize naming
AnschriftaddressStandardize naming
kioskpasswortschutzkiosk_password_protectionStandardize naming

Phase 2: User & Process Tables (Medium Priority)

citizen_requests (formerly buergeranliegen)

Current ColumnNew Column (snake_case)Reason
BuergeranliegenIDcitizen_request_idPrimary/foreign key
BuergerIDprocess_idPrimary/foreign key
BuergerarchivIDcitizen_archive_idPrimary/foreign key
AnliegenIDrequest_idPrimary/foreign key
sourcesourceAlready snake_case

citizen_archive (formerly buergerarchiv)

Current ColumnNew Column (snake_case)Reason
BuergerarchivIDcitizen_archive_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
DatumdateStandardize naming
mitTerminwith_appointmentStandardize naming
nicht_erschienendid_not_appearStandardize naming
TimestamptimestampTimestamp
waiting_timewaiting_timeAlready snake_case
AnzahlPersonenperson_countStandardize naming
processing_timeprocessing_timeAlready snake_case
namenameAlready snake_case
dienstleistungenservicesStandardize naming
way_timeway_timeAlready snake_case

citizen_archive_today (formerly buergerarchivtoday)

Current ColumnNew Column (snake_case)Reason
BuergerarchivIDcitizen_archive_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
DatumdateStandardize naming
mitTerminwith_appointmentStandardize naming
nicht_erschienendid_not_appearStandardize naming
TimestamptimestampTimestamp
waiting_timewaiting_timeAlready snake_case
AnzahlPersonenperson_countStandardize naming
processing_timeprocessing_timeAlready snake_case
namenameAlready snake_case
dienstleistungenservicesStandardize naming
way_timeway_timeAlready snake_case

user (formerly nutzer)

Current ColumnNew Column (snake_case)Reason
NutzerIDuser_idPrimary/foreign key
NamenameStandardize naming
Passworthashpassword_hashStandardize naming
Fragesecurity_questionStandardize naming
Antworthashanswer_hashStandardize naming
Berechtigungpermission_levelStandardize naming
KundenIDjurisdiction_idPrimary/foreign key
BehoerdenIDdepartment_idPrimary/foreign key
SessionIDsession_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
Arbeitsplatznrworkstation_numberStandardize naming
DatumdateStandardize naming
Kalenderansichtcalendar_viewStandardize naming
clusteransichtcluster_viewStandardize naming
notrufinitiierungemergency_initiationStandardize naming
notrufantwortemergency_responseStandardize naming
aufrufzusatzcall_suffixStandardize naming
lastUpdatelast_updateStandardize naming
sessionExpirysession_expiryStandardize naming

user_assignment (formerly nutzerzuordnung)

Current ColumnNew Column (snake_case)Reason
nutzeriduser_idPrimary/foreign key
behoerdeniddepartment_idPrimary/foreign key

jurisdiction (formerly kunde)

Current ColumnNew Column (snake_case)Reason
KundenIDjurisdiction_idPrimary/foreign key
KundennamenameStandardize naming
AnschriftaddressStandardize naming
ModulemodulesStandardize naming
Startkennungstart_identifierStandardize naming
Anzahlkennungenidentifier_countStandardize naming
TerminURLappointment_urlStandardize naming
Current ColumnNew Column (snake_case)Reason
linkidlink_idPrimary/foreign key
kundenidjurisdiction_idPrimary/foreign key
organisationsidorganization_idPrimary/foreign key
behoerdeniddepartment_idPrimary/foreign key
beschreibungdescriptionStandardize naming
linklinkAlready snake_case
oeffentlichpublicStandardize naming
neuerFramenew_frameStandardize naming

Phase 3: System & Configuration Tables (Lower Priority)

billing (formerly abrechnung)

Current ColumnNew Column (snake_case)Reason
AbrechnungsIDbilling_idPrimary/foreign key
StandortIDscope_idPrimary/foreign key
TelefonnummerphoneStandardize naming
DatumdateStandardize naming
gesendetsentStandardize naming

ip_exceptions (formerly ipausnahmen)

Current ColumnNew Column (snake_case)Reason
IPIDip_exception_idPrimary/foreign key
BehoerdenIDdepartment_idPrimary/foreign key
IPAdresseip_addressStandardize naming

kiosk

Current ColumnNew Column (snake_case)Reason
kioskidkiosk_idPrimary/foreign key
kundenidjurisdiction_idPrimary/foreign key
organisationsidorganization_idPrimary/foreign key
timestamptimestampAlready snake_case
cookiecodecookie_codeStandardize naming
namenameAlready snake_case
zugelassenallowedStandardize naming

queue_number_statistics (formerly wartenrstatistik)

Current ColumnNew Column (snake_case)Reason
datumdateDate
standortidscope_idForeign key to scope
wartenrstatistikidqueue_number_statistics_idPrimary key
hour_##_waiting_time_spontaneoushour_##_waiting_time_spontaneousPer-hour actual waiting time (spontaneous); ## = 00–23
hour_##_waiting_time_appointmenthour_##_waiting_time_appointmentPer-hour actual waiting time (appointment); ## = 00–23
hour_##_way_time_spontaneoushour_##_way_time_spontaneousPer-hour way time (spontaneous); ## = 00–23
hour_##_way_time_appointmenthour_##_way_time_appointmentPer-hour way time (appointment); ## = 00–23
hour_##_estimated_waiting_time_spontaneoushour_##_estimated_waiting_time_spontaneousPer-hour estimated waiting time (spontaneous); ## = 00–23
hour_##_estimated_waiting_time_appointmenthour_##_estimated_waiting_time_appointmentPer-hour estimated waiting time (appointment); ## = 00–23
hour_##_waiting_count_spontaneoushour_##_waiting_count_spontaneousPer-hour waiting count (spontaneous); ## = 00–23
hour_##_waiting_count_appointmenthour_##_waiting_count_appointmentPer-hour waiting count (appointment); ## = 00–23

Legacy columns echte_zeit_ab_##_*, wegezeit_ab_##_*, zeit_ab_##_*, and wartende_ab_##_* map to the hour_##_* names above (see migration 91775568666-rename-waiting-way-processing-columns.sql).

location_cluster (formerly standortcluster)

Current ColumnNew Column (snake_case)Reason
clusterIDcluster_idPrimary/foreign key
namenameAlready snake_case
clusterinfozeile1cluster_info_line_1Standardize naming
clusterinfozeile2cluster_info_line_2Standardize naming
stadtplanlinkcity_map_linkStandardize naming
aufrufanzeigetextdisplay_textStandardize naming
standortkuerzelanzeigenshow_scope_short_nameStandardize naming

statistics (formerly statistik)

Current ColumnNew Column (snake_case)Reason
statistikidstatistics_idPrimary/foreign key
kundenidjurisdiction_idPrimary/foreign key
organisationsidorganization_idPrimary/foreign key
behoerdeniddepartment_idPrimary/foreign key
clusteridcluster_idPrimary/foreign key
standortidscope_idPrimary/foreign key
anliegenidrequest_idPrimary/foreign key
datumdatumAlready snake_case
lastbuergerarchividlast_citizen_archive_idPrimary/foreign key
terminwith_appointmentStandardize naming
info_dl_idinfo_provider_idPrimary/foreign key
processing_timeprocessing_timeAlready snake_case

api_client (formerly apiclient)

Current ColumnNew Column (snake_case)Reason
apiClientIDapi_client_idPrimary/foreign key
clientKeyclient_keyStandardize naming
shortnameshort_nameStandardize naming
accesslevelaccess_levelStandardize naming
updateTimestampupdated_atTimestamp

api_key (formerly apikey)

Current ColumnNew Column (snake_case)Reason
keykeyAlready snake_case
createIPcreate_ipStandardize naming
tstsAlready snake_case
apiClientIDapi_client_idPrimary/foreign key

api_quota (formerly apiquota)

Current ColumnNew Column (snake_case)Reason
quotaidquota_idPrimary/foreign key
keykeyAlready snake_case
routerouteAlready snake_case
periodperiodAlready snake_case
requestsrequestsAlready snake_case
tstsAlready snake_case

role

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
namenameAlready snake_case
descriptiondescriptionAlready snake_case

permission

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
namenameAlready snake_case
descriptiondescriptionAlready snake_case

role_permission

Current ColumnNew Column (snake_case)Reason
role_idrole_idAlready snake_case
permission_idpermission_idAlready snake_case

user_role

Current ColumnNew Column (snake_case)Reason
user_iduser_idAlready snake_case
role_idrole_idAlready snake_case

Phase 4: Communication Tables

email

Current ColumnNew Column (snake_case)Reason
emailIDemail_idPrimary/foreign key
BehoerdenIDdepartment_idPrimary/foreign key
serveradresseserver_addressStandardize naming
authenticationauthenticationAlready snake_case
usernameusernameAlready snake_case
passwordpasswordAlready snake_case
ssl_codingssl_encodingStandardize naming
absenderadressesender_addressStandardize naming
send_remindersend_reminderAlready snake_case
send_reminder_minutes_beforesend_reminder_minutes_beforeAlready snake_case

sms

Current ColumnNew Column (snake_case)Reason
smsIDsms_idPrimary/foreign key
BehoerdenIDdepartment_idPrimary/foreign key
enabledenabledAlready snake_case
AbsendersenderStandardize naming
interneterinnerunginternet_reminderStandardize naming
internetbestaetigunginternet_confirmationStandardize naming

mail_part (formerly mailpart)

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
queueIdqueue_idPrimary/foreign key
mimemimeAlready snake_case
contentcontentAlready snake_case
base64base64Already snake_case

mail_queue (formerly mailqueue)

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
processIDprocess_idPrimary/foreign key
departmentIDdepartment_idPrimary/foreign key
createIPcreate_ipStandardize naming
createTimestampcreated_atTimestamp
subjectsubjectAlready snake_case
clientFamilyNameclient_family_nameStandardize naming
clientEmailclient_emailStandardize naming

mail_template (formerly mailtemplate)

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
namenameAlready snake_case
valuevalueAlready snake_case
providerproviderAlready snake_case
changeTimestampchanged_atTimestamp

notification_queue (formerly notificationqueue)

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
processIDprocess_idPrimary/foreign key
departmentIDdepartment_idPrimary/foreign key
createIPcreate_ipStandardize naming
createTimestampcreated_atTimestamp
messagemessageAlready snake_case
clientFamilyNameclient_family_nameStandardize naming
clientTelephoneclient_phoneStandardize naming
scopeIDscope_idPrimary/foreign key

Phase 5: Data & Process Tables

closures

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
yearyearAlready snake_case
monthmonthAlready snake_case
daydayAlready snake_case
StandortIDscope_idPrimary/foreign key
updateTimestampupdated_atTimestamp

config

Current ColumnNew Column (snake_case)Reason
namenameAlready snake_case
valuevalueAlready snake_case
changeTimestampchanged_atTimestamp

event_log (formerly eventlog)

Current ColumnNew Column (snake_case)Reason
eventIdevent_idPrimary/foreign key
eventNameevent_nameStandardize naming
originoriginAlready snake_case
referenceTypereference_typeStandardize naming
referencereferenceAlready snake_case
sessionidsession_idPrimary/foreign key
contextjsoncontext_jsonStandardize naming
creationDateTimecreated_atTimestamp
expirationDateTimeexpires_atTimestamp

image_data (formerly imagedata)

Current ColumnNew Column (snake_case)Reason
imagenameimage_nameStandardize naming
imagecontentimage_contentStandardize naming
tstsAlready snake_case

log

Current ColumnNew Column (snake_case)Reason
log_idlog_idAlready snake_case
typetypeAlready snake_case
reference_idreference_idAlready snake_case
tstsAlready snake_case
messagemessageAlready snake_case
scope_idscope_idAlready snake_case
datadataAlready snake_case
user_iduser_idAlready snake_case

migrations

Current ColumnNew Column (snake_case)Reason
filenamefilenameAlready snake_case
changeTimestampchanged_atTimestamp

preferences

Current ColumnNew Column (snake_case)Reason
entityentityAlready snake_case
ididAlready snake_case
groupNamegroup_nameStandardize naming
namenameAlready snake_case
valuevalueAlready snake_case
updateTimestampupdated_atTimestamp

process_sequence

Current ColumnNew Column (snake_case)Reason
processIdprocess_idPrimary/foreign key

session_data (formerly sessiondata)

Current ColumnNew Column (snake_case)Reason
sessionidsession_idPrimary/foreign key
sessionnamesession_nameStandardize naming
sessioncontentsession_contentStandardize naming
tstsAlready snake_case

source

Current ColumnNew Column (snake_case)Reason
sourcesourceAlready snake_case
labellabelAlready snake_case
editableeditableAlready snake_case
contact__namecontact__nameAlready snake_case
contact__emailcontact__emailAlready snake_case
lastChangelast_changeStandardize naming

overview_calendar

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
scope_idscope_idAlready snake_case
process_idprocess_idAlready snake_case
statusstatusAlready snake_case
starts_atstarts_atAlready snake_case
ends_atends_atAlready snake_case
updated_atupdated_atAlready snake_case

Phase 6: Service & Provider Tables

provider

Current ColumnNew Column (snake_case)Reason
sourcesourceAlready snake_case
ididAlready snake_case
namenameAlready snake_case
contact__citycontact__cityAlready snake_case
contact__countrycontact__countryAlready snake_case
contact__latcontact__latAlready snake_case
contact__loncontact__lonAlready snake_case
contact__postalCodecontact__postalCodeAlready snake_case
contact__regioncontact__regionAlready snake_case
contact__streetcontact__streetAlready snake_case
contact__streetNumbercontact__streetNumberAlready snake_case
linklinkAlready snake_case
datadataAlready snake_case
display_namedisplay_nameAlready snake_case
parent_idparent_idAlready snake_case

request

Current ColumnNew Column (snake_case)Reason
sourcesourceAlready snake_case
ididAlready snake_case
namenameAlready snake_case
linklinkAlready snake_case
groupgroupAlready snake_case
datadataAlready snake_case
parent_idparent_idAlready snake_case
variant_idvariant_idAlready snake_case

request_provider

Current ColumnNew Column (snake_case)Reason
sourcesourceAlready snake_case
request__idrequest__idAlready snake_case
provider__idprovider__idAlready snake_case
slotsslotsAlready snake_case
bookablebookableAlready snake_case
max_quantitymax_quantityAlready snake_case
public_visibilitypublic_visibilityAlready snake_case

request_variant

Current ColumnNew Column (snake_case)Reason
ididAlready snake_case
namenameAlready snake_case

Phase 7: Slot System Tables

slot

Current ColumnNew Column (snake_case)Reason
slotIDslot_idPrimary/foreign key
scopeIDscope_idPrimary/foreign key
yearyearAlready snake_case
monthmonthAlready snake_case
daydayAlready snake_case
timetimeAlready snake_case
availabilityIDavailability_idPrimary/foreign key
publicpublicAlready snake_case
callcentercallcenterAlready snake_case
interninternAlready snake_case
statusstatusAlready snake_case
slotTimeInMinutesslot_time_in_minutesStandardize naming
createTimestampcreated_atTimestamp
updateTimestampupdated_atTimestamp

slot_hierarchy (formerly slot_hiera)

Current ColumnNew Column (snake_case)Reason
slothieraIDslot_hierarchy_idPrimary/foreign key
slotIDslot_idPrimary/foreign key
ancestorIDancestor_idPrimary/foreign key
ancestorLevelancestor_levelStandardize naming

slot_process

Current ColumnNew Column (snake_case)Reason
slotIDslot_idPrimary/foreign key
processIDprocess_idPrimary/foreign key
updateTimestampupdated_atTimestamp

slot_sequence

Current ColumnNew Column (snake_case)Reason
slotsequenceslot_sequenceStandardize naming

Phase 8: Assignment & Clustering Tables

cluster_assignment (formerly clusterzuordnung)

Current ColumnNew Column (snake_case)Reason
clusterIDcluster_idPrimary/foreign key
standortIDscope_idPrimary/foreign key

Phase 9: Foreign Key Standardization

Current PatternNew PatternExample
StandortIDscope_idForeign key to scope table
BehoerdenIDdepartment_idForeign key to department table
BuergerIDprocess_idForeign key to process table (buerger)
NutzerIDuser_idForeign key to user table
KundenIDjurisdiction_idForeign key to jurisdiction table (kunde)
OrganisationsIDorganization_idForeign key to organization table
OeffnungszeitIDavailability_idForeign key to availability table
apiClientIDapi_client_idForeign key to API client table
clusterIDcluster_idForeign key to location cluster table

Column mappings are generated from .resources/zms.sql and recent migrations. Some columns (for example provider.contact__*) are JSON-path keys stored in relational columns and are listed as-is until the PHP mapping refactor (section 3) moves them to camelCase entity fields.

3. Complete PHP Variable Mapping Renaming Plan - All English, All camelCase

Step 1: Update Entity Mappings Replace all double underscore patterns with camelCase Update all German variable names to English Ensure consistent naming across all Query classes

Step 2: Update Method Names Update method names that reference old mappings Ensure parameter names match new mapping keys Update any hardcoded references

Step 3: Update Tests Update all test cases to use new mapping keys Ensure test data matches new naming conventions Update any mock data or fixtures

Step 4: Update Documentation Update API documentation with new field names Update any external references to old field names Create migration guide for API consumers This comprehensive plan ensures: Complete camelCase conversion for all PHP variable mappings Elimination of double underscores in favor of camelCase Consistent English naming throughout the codebase Maintainable structure with clear patterns

Phase 1: Core Entity Mappings (High Priority)

Availability Query Class

Current MappingNew Mapping (camelCase)Database Column (snake_case)
'id''id'availability.availability_id
'scope**id''scopeId'availability.scope_id
'bookable**startInDays''bookableStartInDays'availability.open_from_days
'bookable**endInDays''bookableEndInDays'availability.open_until_days
'description''description'availability.comment
'startDate''startDate'availability.start_date
'startTime''startTime'availability.start_time
'endDate''endDate'availability.end_date
'endTime''endTime'availability.end_time
'lastChange''lastChange'availability.updated_at
'multipleSlotsAllowed''multipleSlotsAllowed'availability.multiple_slots_allowed
'repeat**afterWeeks''repeatAfterWeeks'availability.every_x_weeks
'repeat**weekOfMonth''repeatWeekOfMonth'availability.every_other_week
'slotTimeInMinutes''slotTimeInMinutes'availability.time_slot
'type''type'availability.type
'weekday**monday''weekdayMonday'availability.weekday
'weekday**tuesday''weekdayTuesday'availability.weekday
'weekday**wednesday''weekdayWednesday'availability.weekday
'weekday**thursday''weekdayThursday'availability.weekday
'weekday**friday''weekdayFriday'availability.weekday
'weekday**saturday''weekdaySaturday'availability.weekday
'weekday**sunday''weekdaySunday'availability.weekday
'workstationCount**callcenter''workstationCountCallcenter'availability.workstation_count_callcenter
'workstationCount**intern''workstationCountIntern'availability.workstation_count_intern
'workstationCount__public''workstationCountPublic'availability.workstation_count_public

Scope Query Class

Current MappingNew Mapping (camelCase)Database Column (snake_case)
'hint''hint'scope.hint
'id''id'scope.scope_id
'contact**name''contactName'scopeprovider.name
'contact**street''contactStreet'scope.address
'contact**email''contactEmail'scope.admin_email
'contact**country''contactCountry'"Germany"
'lastChange''lastChange'scope.updated_at
'preferencesappointmentdeallocationDuration''preferencesAppointmentDeallocationDuration'scope.deletion_duration
'preferencesappointmentinfoForAppointment''preferencesAppointmentInfoForAppointment'scope.info_for_appointment
'preferencesappointmentinfoForAllAppointments''preferencesAppointmentInfoForAllAppointments'scope.info_for_all_appointments
'preferencesappointmentendInDaysDefault''preferencesAppointmentEndInDaysDefault'scope.appointments_until_days
'preferencesappointmentmultipleSlotsEnabled''preferencesAppointmentMultipleSlotsEnabled'scope.multiple_appointments
'preferencesappointmentreservationDuration''preferencesAppointmentReservationDuration'scope.reservation_duration
'preferencesappointmentactivationDuration''preferencesAppointmentActivationDuration'scope.activation_duration
'preferencesappointmentstartInDaysDefault''preferencesAppointmentStartInDaysDefault'scope.appointments_from_days
'preferencesappointmentnotificationConfirmationEnabled''preferencesAppointmentNotificationConfirmationEnabled'scope.sms_confirmation_enabled
'preferencesappointmentnotificationHeadsUpEnabled''preferencesAppointmentNotificationHeadsUpEnabled'scope.sms_notification_enabled
'preferencesclientalternateAppointmentUrl''preferencesClientAlternateAppointmentUrl'scope.qtv_url
'preferencesclientamendmentActivated''preferencesClientAmendmentActivated'scope.comment_required
'preferencesclientamendmentLabel''preferencesClientAmendmentLabel'scope.comment_label
'preferencesclientemailFrom''preferencesClientEmailFrom'scopemail.sender_address
'preferencesclientemailRequired''preferencesClientEmailRequired'scope.email_required
'preferencesclientemailConfirmationActivated''preferencesClientEmailConfirmationActivated'scope.email_confirmation_activated
'preferencesclienttelephoneActivated''preferencesClientTelephoneActivated'scope.phone_enabled
'preferencesclienttelephoneRequired''preferencesClientTelephoneRequired'scope.phone_required
'preferencesclientappointmentsPerMail''preferencesClientAppointmentsPerMail'scope.appointments_per_mail
'preferencesclientslotsPerAppointment''preferencesClientSlotsPerAppointment'scope.slots_per_appointment
'preferencesclientwhitelistedMails''preferencesClientWhitelistedMails'scope.whitelisted_mails
'preferencesclientcustomTextfieldActivated''preferencesClientCustomTextfieldActivated'scope.custom_text_field_active
'preferencesclientcustomTextfieldRequired''preferencesClientCustomTextfieldRequired'scope.custom_text_field_required
'preferencesclientcustomTextfieldLabel''preferencesClientCustomTextfieldLabel'scope.custom_text_field_label
'preferencesclientcustomTextfield2Activated''preferencesClientCustomTextfield2Activated'scope.custom_text_field2_active
'preferencesclientcustomTextfield2Required''preferencesClientCustomTextfield2Required'scope.custom_text_field2_required
'preferencesclientcustomTextfield2Label''preferencesClientCustomTextfield2Label'scope.custom_text_field2_label
'preferencesclientcaptchaActivatedRequired''preferencesClientCaptchaActivatedRequired'scope.captcha_activated_required
'preferencesclientadminMailOnAppointment''preferencesClientAdminMailOnAppointment'scope.admin_mail_on_appointment
'preferencesclientadminMailOnDeleted''preferencesClientAdminMailOnDeleted'scope.admin_mail_on_deleted
'preferencesclientadminMailOnUpdated''preferencesClientAdminMailOnUpdated'scope.admin_mail_on_updated
'preferencesclientadminMailOnMailSent''preferencesClientAdminMailOnMailSent'scope.admin_mail_on_mail_sent
'preferencesnotificationsconfirmationContent''preferencesNotificationsConfirmationContent'scope.sms_confirmation_text
'preferencesnotificationsheadsUpContent''preferencesNotificationsHeadsUpContent'scope.sms_notification_text
'preferencesnotificationsheadsUpTime''preferencesNotificationsHeadsUpTime'scope.sms_notification_deadline
'preferencespickupalternateName''preferencesPickupAlternateName'scope.pickup_counter_name
'preferencespickupisDefault''preferencesPickupIsDefault'scope.default_pickup_location
'preferencesqueuecallCountMax''preferencesQueueCallCountMax'scope.recall_count
'preferencesqueuecallDisplayText''preferencesQueueCallDisplayText'scope.display_text
'preferencesqueuefirstNumber''preferencesQueueFirstNumber'scope.first_queue_number
'preferencesqueuelastNumber''preferencesQueueLastNumber'scope.last_queue_number
'preferencesqueuemaxNumberContingent''preferencesQueueMaxNumberContingent'scope.queue_number_contingent
'preferencesqueueprocessingTimeAverage''preferencesQueueProcessingTimeAverage'scope.processing_time
'preferencesqueuepublishWaitingTimeEnabled''preferencesQueuePublishWaitingTimeEnabled'scope.publish_waiting_time
'preferencesqueuestatisticsEnabled''preferencesQueueStatisticsEnabled'scope.statistics_enabled
'preferencessurveyemailContent''preferencesSurveyEmailContent'scope.customer_survey_email_text
'preferencessurveyenabled''preferencesSurveyEnabled'scope.customer_survey
'preferencessurveylabel''preferencesSurveyLabel'scope.customer_survey_label
'preferencesticketprinterbuttonName''preferencesTicketprinterButtonName'scope.location_info_line
'preferencesticketprinterconfirmationEnabled''preferencesTicketprinterConfirmationEnabled'scope.sms_wms_confirmation
'preferencesticketprinterdeactivatedText''preferencesTicketprinterDeactivatedText'scope.queue_hint
'preferencesticketprinternotificationsAmendmentEnabled''preferencesTicketprinterNotificationsAmendmentEnabled'scope.sms_addition
'preferencesticketprinternotificationsEnabled''preferencesTicketprinterNotificationsEnabled'scope.sms_queue
'preferencesticketprinternotificationsDelay''preferencesTicketprinterNotificationsDelay'scope.sms_kiosk_offer_deadline
'preferencesworkstationemergencyEnabled''preferencesWorkstationEmergencyEnabled'scope.emergency_function
'preferencesworkstationemergencyRefreshInterval''preferencesWorkstationEmergencyRefreshInterval'scope.emergency_refresh_interval
'shortName''shortName'scope.short_name
'statusemergencyacceptedByWorkstation''statusEmergencyAcceptedByWorkstation'scope.emergency_response
'statusemergencyactivated''statusEmergencyActivated'scope.emergency_triggered
'statusemergencycalledByWorkstation''statusEmergencyCalledByWorkstation'scope.emergency_initiation
'statusqueueghostWorkstationCount''statusQueueGhostWorkstationCount'scope.virtual_processor_count
'statusqueuegivenNumberCount''statusQueueGivenNumberCount'scope.assigned_queue_numbers
'statusqueuelastGivenNumber''statusQueueLastGivenNumber'scope.last_queue_number
'statusqueuelastGivenNumberTimestamp''statusQueueLastGivenNumberTimestamp'scope.queue_number_date

Phase 2: Process & Citizen Mappings (Medium Priority)

Process Query Class

Current MappingNew Mapping (camelCase)Database Column (snake_case)
'amendment''amendment'process.comment
'id''id'process.citizen_id
'appointments0date''appointments0Date'process.appointment_datetime
'scope**id''scopeId'process.scope_id
'appointments0scope**id''appointments0ScopeId'process.scope_id

Citizen Query Class

Current MappingNew Mapping (camelCase)Database Column (snake_case)
'id''id'citizen.citizen_id
'scopeId''scopeId'citizen.scope_id
'pickupLocationId''pickupLocationId'citizen.pickup_location_id
'userId''userId'citizen.user_id
'name''name'citizen.name
'email''email'citizen.email
'phone''phone'citizen.phone
'comment''comment'citizen.comment
'provisionalBooking''provisionalBooking'citizen.provisional_booking
'confirmed''confirmed'citizen.confirmed
'callSuccessful''callSuccessful'citizen.call_successful
'callTime''callTime'citizen.call_time
'pickupPerson''pickupPerson'citizen.pickup_person
'queueNumber''queueNumber'citizen.queue_number
'queueNumberDate''queueNumberDate'citizen.queue_number_date
'waitingTime''waitingTime'citizen.waiting_time
'processingTime''processingTime'citizen.processing_time
'parked''parked'citizen.parked
'wasMissed''wasMissed'citizen.was_missed
'apiClientId''apiClientId'citizen.api_client_id
'source''source'citizen.source
'lastChange''lastChange'citizen.updated_at

Phase 3: Provider & Request Mappings (Lower Priority)

Provider Query Class

Current MappingNew Mapping (camelCase)Database Column (snake_case)
'contact**city''contactCity'provider.contact_city
'contact**country''contactCountry'provider.contact_country
'contact**name''contactName'provider.name
'contact**postalCode''contactPostalCode'provider.contact_postal_code
'contact**region''contactRegion'provider.contact_region
'contact**street''contactStreet'provider.contact_street
'contact__streetNumber''contactStreetNumber'provider.contact_street_number
'id''id'provider.id
'link''link'provider.link
'name''name'provider.name
'displayName''displayName'provider.display_name
'source''source'provider.source
'data''data'provider.data

4. Long-Term Schema Vision (Beyond Renaming)

Sections 1–3 standardize names. This section records structural changes under consideration for a healthier long-term schema. These are not committed timelines; they inform design discussions and ticket breakdown.

4.1 Major structural refactors

Split buerger into citizen and process (or citizen and appointment)

Today the buerger table is the physical store for the process entity. It mixes concerns that evolved together over years:

  • Client / citizen PII (Name, EMail, Telefonnummer, custom fields)
  • Appointment scheduling (Datum, Uhrzeit, scope references, slot linkage)
  • Queue runtime state (wartenummer, status, waiting_time, call metadata)
  • Archival / statistics inputs

Candidate target models:

OptionTablesFits when
Acitizen + processQueue-first workflows; process is the unit of work; citizen is reusable across visits
Bcitizen + appointmentAppointment-first workflows; clearer split between booking and queue handling

Near-term renaming in section 1 may still map buergerprocess for minimal disruption. The split in this section is a later migration once API, statistics, and archive paths are untangled.

Drop buergerarchivtoday / citizen_archive_today

buergerarchivtoday is a daily snapshot of buergerarchiv. It duplicates data and adds sync/cron complexity. Prefer:

  • Query citizen_archive with a date predicate and proper indexes, or
  • A database view / materialized view if performance requires it

Remove the table once query plans and dashboards are validated without it.

Normalize queue_number_statistics (wartenrstatistik)

The table is extremely wide: per-hour columns for estimated waiting time, actual waiting time, way time, and waiting counts, each split by spontaneous vs appointment (96+ columns per family). Interim renames exist in migration 91775568666-rename-waiting-way-processing-columns.sql.

Long-term direction: fact-style tables, for example:

  • queue_statistics_hourly(scope_id, date, hour, metric, channel, value) where channel is spontaneous | appointment and metric is waiting_time | way_time | waiting_count | estimated_waiting_time
  • Or separate narrow tables per metric family if query patterns differ

Benefits: simpler migrations, easier aggregation, room for new metrics without ALTER TABLE on hundreds of columns.

Normalize log.data (JSON)

The log table stores a JSON data blob alongside indexed columns (type, scope_id, user_id, reference_id). Searching inside JSON is slow and awkward.

Direction: promote frequently filtered fields to typed columns; keep data only for optional debug payload or drop it once structured columns cover admin search needs.

Split and rename preferences

preferences is keyed by (entity, id, groupName, name) but usage is mixed:

  • Scope settings — migrated from standort columns; entity scope
  • System / admin config — edited via zmsadmin config area; not truly “scope preferences”

Direction:

  • scope_preference (or scope_setting) for per-scope values
  • system_setting (or keep config for key-value and migrate overlapping rows)
  • Avoid the generic name preferences for scope-only data

DLDB tables: provider, request, request_provider, request_variant

These tables are DLDB-synced. Several store a data JSON column with nested contact and metadata. zmscitizenapi already speaks in terms of offices and services (officeId, serviceId, OfficeServiceRelation).

Naming candidates:

CurrentCitizen API termCandidate table name
providerofficeoffice
requestserviceservice
request_provideroffice–service linkoffice_service
request_variantservice variantservice_variant

Structural direction: parse data into relational columns where queried; keep JSON only for rare DLDB fields or use a metadata column with a documented schema version.

4.2 Table disposition audit

Table (current)Planned dispositionNotes
abrechnungDropBilling/SMS accounting; no active use. Migration 91772633097-drop-abrechnung.sql already drops the table.
ipausnahmenVerify → likely dropNo references in PHP codebase at time of writing; confirm no external dependency before removal.
apikeyVerifyRoutes exist in zmsapi; confirm whether any deployment still issues or validates API keys.
apiquotaVerifyTied to apikey; same audit as above.
notificationqueueDropPart of SMS/notification removal. Migration 91772633137-drop-notifcationqueue.sql already drops the table.
eventlogVerifyStill used (e.g. ProcessListSummaryMail); clarify whether to keep, replace with log, or consolidate.
imagedataRedesignStore object URL in DB; binary in RefArch S3 bucket via zmsadmin. Calldisplay/cluster image upload paths exist, but the admin header logo is static at terminvereinbarung/admin/_css/images/muc_logo_head2.png — DB logo upload may already be unused or broken.
kundenlinksDropAlready marked unused in section 1.
buergerarchivtodayDropSee §4.1; redundant with buergerarchiv.

4.3 Assets, migrations, and operations

image_data → object storage

  • Upload logos and calldisplay images through zmsadmin into a RefArch S3 bucket (or compatible object store).
  • Persist only bucket, object_key, and/or HTTPS URL in image_data (or a renamed asset table).
  • Remove imagecontent BLOB/TEXT columns after migration.
  • Audit all consumers: FileUploader, calldisplay routes, cluster scope images.

Migration file naming

Many migration files use a leading 917… numeric prefix (legacy ticket/timestamp encoding), which makes chronological ordering and code review harder.

Target convention (proposal):

{YYYYMMDD}-{HHMMSS}-{ticket-or-short-description}.sql

Examples: 20260302-143000-ZMS-1234-split-buerger.sql. Apply to new migrations; optionally rename historical files only when the cost is justified.

migrations table

The migrations table itself is fine as snake_case. The improvement target is the filename convention on disk, not the table name.