Kurzanmeldungen

This commit is contained in:
2026-09-10 11:11:20 +02:00
parent 6301c342e4
commit 70a57d10f5
31 changed files with 1753 additions and 184 deletions
@@ -0,0 +1,37 @@
// Schrittreihenfolge der Kurzanmeldung. Zwei Schritte sind optional: die Teilnahmeoptionen entfallen, wenn für
// die Veranstaltung keine definiert sind, die Zahlungsart entfällt bei Betrag 0 oder wenn es ohnehin nur eine
// Zahlungsart gibt (die wird dann vorausgewählt).
export const SHORT_STEP_PERSON = 1
export const SHORT_STEP_PARTICIPATION_OPTIONS = 2
export const SHORT_STEP_PHOTO = 3
export const SHORT_STEP_PAYMENT = 4
export const SHORT_STEP_SUMMARY = 5
export function stepVisible(event, step, amountValue) {
if (step === SHORT_STEP_PARTICIPATION_OPTIONS) {
return (event.participationOptions?.length ?? 0) > 0
}
if (step === SHORT_STEP_PAYMENT) {
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
return amountValue > 0 && activeMethods.length > 1
}
return true
}
export function nextVisibleFrom(event, step, amountValue) {
let candidate = step + 1
while (candidate < SHORT_STEP_SUMMARY && !stepVisible(event, candidate, amountValue)) {
candidate++
}
return candidate
}
export function prevVisibleFrom(event, step, amountValue) {
let candidate = step - 1
while (candidate > SHORT_STEP_PERSON && !stepVisible(event, candidate, amountValue)) {
candidate--
}
return candidate
}
@@ -0,0 +1,155 @@
import {computed, reactive, ref} from 'vue'
import axios from 'axios'
import {differenceInYears, parseISO} from 'date-fns'
import {SHORT_STEP_PERSON, nextVisibleFrom, prevVisibleFrom} from './shortStepFlow.js'
export function useShortSignupForm(event, participantData) {
const currentStep = ref(SHORT_STEP_PERSON)
const submitting = ref(false)
const summaryLoading = ref(false)
const submitResult = ref(null) // null | { status: 'success'|'exists', participant: {} }
const submitError = ref('')
// Gibt es nur eine Zahlungsart, wird sie vorausgewählt und der Zahlungsschritt entfällt.
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
// Bei genau einem teilnehmenden Stamm wird dieser gesetzt, ohne danach zu fragen; erst ab zwei
// Stämmen erscheint das Auswahlfeld.
const localGroups = event.contributingLocalGroups ?? []
const needsLocalGroup = localGroups.length > 1
const formData = reactive({
userId: participantData.id,
vorname: participantData.firstname ?? '',
nachname: participantData.lastname ?? '',
pfadiname: participantData.nickname ?? '',
geburtsdatum: participantData.birthday ?? '',
email_1: participantData.email ?? '',
telefon_1: participantData.phone ?? '',
localGroup: localGroups.length === 1 ? localGroups[0].id : (participantData.localGroup ?? '-1'),
ansprechpartner: '',
email_2: '',
allergien: participantData.allergies ?? '',
intolerances: participantData.intolerances ?? '',
first_aid: '-1',
badeerlaubnis: '-1',
// Antworten auf die event-spezifischen Teilnahmeoptionen: { [question.key]: option.value }
participationOptions: {},
foto: {socialmedia: false, print: false, webseite: false, partner: false, intern: false},
paymentMethod: activeMethods.length === 1 ? activeMethods[0].slug : null,
paymentOptions: {},
summary_information_correct: false,
summary_accept_terms: false,
legal_accepted: false,
payment: false,
})
// Minderjährigkeit steuert Kontaktperson, Erweiterte Erste Hilfe und Badeerlaubnis. Die Grenze von 18 Jahren
// entspricht Age::isfullAged() im Backend.
const isMinor = computed(() => {
if (!formData.geburtsdatum) {
return false
}
return differenceInYears(new Date(), parseISO(formData.geburtsdatum)) < 18
})
const summaryAmount = ref('')
const summaryAmountValue = ref(0)
const amountLoaded = ref(false)
// Der Betrag steht für die ganze Anmeldung fest (feste Teilnahmegruppe, fester Zeitraum) und wird deshalb
// genau einmal geholt. Er entscheidet, ob der Zahlungsschritt überhaupt vorkommt.
const ensureAmount = async () => {
if (amountLoaded.value) {
return
}
summaryLoading.value = true
try {
const res = await axios.post('/api/v1/event/' + event.id + '/short-calculate-amount')
summaryAmount.value = res.data.amount
summaryAmountValue.value = Number(res.data.amountValue ?? 0)
amountLoaded.value = true
if (summaryAmountValue.value <= 0) {
formData.paymentMethod = null
formData.paymentOptions = {}
}
} finally {
summaryLoading.value = false
}
}
const goToStep = (step) => {
currentStep.value = step
}
const goNext = async () => {
await ensureAmount()
goToStep(nextVisibleFrom(event, currentStep.value, summaryAmountValue.value))
}
const goBack = async () => {
await ensureAmount()
goToStep(prevVisibleFrom(event, currentStep.value, summaryAmountValue.value))
}
const submit = async () => {
if (!formData.summary_information_correct || !formData.summary_accept_terms || !formData.legal_accepted) {
return
}
submitting.value = true
submitError.value = ''
try {
const res = await axios.post('/api/v1/event/' + event.id + '/short-signup', {
firstname: formData.vorname,
lastname: formData.nachname,
nickname: formData.pfadiname || null,
birthday: formData.geburtsdatum,
email: formData.email_1,
phone: formData.telefon_1,
localGroup: formData.localGroup !== '-1' ? formData.localGroup : null,
contactPerson: isMinor.value ? formData.ansprechpartner : null,
contactEmail: isMinor.value ? formData.email_2 : null,
allergies: formData.allergien || null,
intolerances: formData.intolerances || null,
firstAidPermission: isMinor.value && formData.first_aid !== '-1' ? formData.first_aid : null,
swimmingPermission: isMinor.value && formData.badeerlaubnis !== '-1' ? formData.badeerlaubnis : null,
foto: formData.foto,
participationOptions: formData.participationOptions,
paymentMethod: formData.paymentMethod,
paymentOptions: formData.paymentOptions,
})
submitResult.value = {
status: res.data.status,
participant: res.data.participant,
}
} catch (error) {
submitError.value = error.response?.data?.message
?? 'Die Anmeldung konnte nicht gespeichert werden. Bitte versuche es später erneut.'
} finally {
submitting.value = false
}
}
return {
currentStep,
goToStep,
goNext,
goBack,
formData,
isMinor,
localGroups,
needsLocalGroup,
submit,
submitting,
submitResult,
submitError,
summaryLoading,
summaryAmount,
summaryAmountValue,
ensureAmount,
}
}