feat: split name into first_name + last_name across users, persons, and companies

Cross-cutting migration affecting the entire stack:
- Database: 3 migrations splitting name columns with data migration
- Models: first_name/last_name on User, Person; contact_first_name/contact_last_name on Company; backward-compatible name accessors
- API: all resources return first_name, last_name, full_name; assignablePersons endpoint updated
- Requests: validation rules updated for all person/user/company forms
- Services: VolunteerRegistrationService, ShiftAssignmentService, InvitationService updated
- Frontend: TypeScript types, Zod schemas, all forms split into Voornaam/Achternaam fields
- Display: all person/user name references use full_name; initials use first_name[0]+last_name[0]
- Tests: all 371 tests passing
- Docs: SCHEMA.md and API.md updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 23:04:55 +02:00
parent bd4297f891
commit d2f282eb4c
66 changed files with 654 additions and 220 deletions

View File

@@ -21,7 +21,8 @@ const { data: companies } = useCompanies(orgIdRef)
const form = ref({
crowd_type_id: '',
name: '',
first_name: '',
last_name: '',
email: '',
phone: '',
company_id: '',
@@ -69,7 +70,8 @@ const statusOptions: { title: string; value: PersonStatus }[] = [
function resetForm() {
form.value = {
crowd_type_id: '',
name: '',
first_name: '',
last_name: '',
email: '',
phone: '',
company_id: '',
@@ -87,7 +89,8 @@ function onSubmit() {
const payload = {
crowd_type_id: form.value.crowd_type_id,
name: form.value.name,
first_name: form.value.first_name,
last_name: form.value.last_name,
email: form.value.email,
...(form.value.phone ? { phone: form.value.phone } : {}),
...(form.value.company_id ? { company_id: form.value.company_id } : {}),
@@ -96,7 +99,7 @@ function onSubmit() {
createPerson(payload, {
onSuccess: () => {
successName.value = form.value.name
successName.value = `${form.value.first_name} ${form.value.last_name}`.trim()
modelValue.value = false
showSuccess.value = true
},
@@ -108,7 +111,7 @@ function onSubmit() {
)
}
else if (data?.message) {
errors.value = { name: data.message }
errors.value = { first_name: data.message }
}
},
})
@@ -138,15 +141,29 @@ function onSubmit() {
:error-messages="errors.crowd_type_id"
/>
</VCol>
<VCol cols="12">
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.name"
label="Naam"
v-model="form.first_name"
label="Voornaam"
:rules="[requiredValidator]"
:error-messages="errors.name"
:error-messages="errors.first_name"
autofocus
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.last_name"
label="Achternaam"
:rules="[requiredValidator]"
:error-messages="errors.last_name"
/>
</VCol>
<VCol cols="12">
<AppTextField
v-model="form.email"

View File

@@ -23,7 +23,8 @@ const { mutate: updatePerson, isPending } = useUpdatePerson(eventIdRef)
const form = ref({
crowd_type_id: '',
name: '',
first_name: '',
last_name: '',
email: '',
phone: '',
company_id: '',
@@ -39,7 +40,8 @@ const showSuccess = ref(false)
watch(() => props.person, (p) => {
form.value = {
crowd_type_id: p.crowd_type?.id ?? '',
name: p.name,
first_name: p.first_name,
last_name: p.last_name,
email: p.email,
phone: p.phone ?? '',
company_id: p.company?.id ?? '',
@@ -92,7 +94,8 @@ function onSubmit() {
updatePerson({
id: props.person.id,
crowd_type_id: form.value.crowd_type_id,
name: form.value.name,
first_name: form.value.first_name,
last_name: form.value.last_name,
email: form.value.email,
phone: form.value.phone || undefined,
company_id: form.value.company_id || undefined,
@@ -112,7 +115,7 @@ function onSubmit() {
)
}
else if (data?.message) {
errors.value = { name: data.message }
errors.value = { first_name: data.message }
}
},
})
@@ -141,15 +144,29 @@ function onSubmit() {
:error-messages="errors.crowd_type_id"
/>
</VCol>
<VCol cols="12">
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.name"
label="Naam"
v-model="form.first_name"
label="Voornaam"
:rules="[requiredValidator]"
:error-messages="errors.name"
:error-messages="errors.first_name"
autofocus
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.last_name"
label="Achternaam"
:rules="[requiredValidator]"
:error-messages="errors.last_name"
/>
</VCol>
<VCol cols="12">
<AppTextField
v-model="form.email"

View File

@@ -103,11 +103,11 @@ function onBlacklistToggle(val: boolean | null) {
color="primary"
variant="tonal"
>
<span class="text-h6">{{ getInitials(person.name) }}</span>
<span class="text-h6">{{ getInitials(person.full_name) }}</span>
</VAvatar>
<div>
<h5 class="text-h5 mb-1">
{{ person.name }}
{{ person.full_name }}
</h5>
<p class="text-body-2 text-disabled mb-2">
{{ person.email }}