From 06166221ecca0044f4aa9329e3f37ce8dfa57e03 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 13:55:05 +0000 Subject: [PATCH] =?UTF-8?q?Funktionen:=20Abrechnung=20der=20Einrichtung=20?= =?UTF-8?q?w=C3=A4hlen,=20Abo-Fehler=20anzeigen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../customer/CustomerFunktionen.jsx | 27 ++++++++++++++++--- src/lib/teamApi.js | 5 ++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/customer/CustomerFunktionen.jsx b/src/components/customer/CustomerFunktionen.jsx index 704539a..95c4382 100644 --- a/src/components/customer/CustomerFunktionen.jsx +++ b/src/components/customer/CustomerFunktionen.jsx @@ -7,6 +7,7 @@ import { FUNKTIONEN, ZUSTAND, AKTION_LABEL, funktionOf, aktionen, mergeFunktion, siteKeyOf, abgeschaltetText, hindernisText, panelModell, anleitungOffen, textformNoetig, textformAnsicht, textformBereit, speicherStatus, anfrageZeilen, anfrageStatus, stripeText, stripeTon, shopHatEintrag, begruendungOk, BEGRUENDUNG_MAX, entscheidungStatus, + ABRECHNUNG_OPTIONEN, ABRECHNUNG_HINWEIS, fragtAbrechnung, abrechnungBereit, featureFehlerText, } from '../../lib/funktionenView' import { FUNKTION_CHANNELS, channelLabel } from '../../lib/analyseView' import { berlinYmd, fmtDateTime } from '../../lib/format' @@ -264,6 +265,22 @@ function UnterkunftPanel({ customerId, eintrag, onDecided }) { ) } +// Wahl beim Aktivieren, wohin die Einrichtungszeit geht (V9): Segment-Schalter ohne Vorauswahl, dahinter die Erklaerung (ohne Preis) +function AbrechnungWahl({ value, onChange, disabled }) { + return ( + <> +
+ {ABRECHNUNG_OPTIONEN.map((o) => ( + + ))} +
+ {ABRECHNUNG_HINWEIS} + + ) +} + const LEERE_TEXTFORM = { confirmed: false, date: '', channel: '' } export default function CustomerFunktionen({ customerId }) { @@ -273,6 +290,7 @@ export default function CustomerFunktionen({ customerId }) { const [error, setError] = useState(null) const [confirm, setConfirm] = useState(null) // { projectId, key, action } const [note, setNote] = useState('') + const [abrechnung, setAbrechnung] = useState('') // Wahl beim Aktivieren: '' (noch keine) | 'abo' | 'projekt'; nie vorbelegt const [busy, setBusy] = useState(false) const [status, setStatus] = useState(null) // { ok, text } const [panel, setPanel] = useState({}) // projectId -> true/false (Knopf "Anleitung"); ohne Eintrag entscheidet die Adresse (?anleitung=) @@ -309,9 +327,11 @@ export default function CustomerFunktionen({ customerId }) { setStatus(null) try { const mitTextform = textformNoetig(key, action, felder[key]) + const mitAbrechnung = fragtAbrechnung(key, action, funktionOf(projects?.find((p) => p.projectId === projectId), key)) const antwort = await setFeature(customerId, key, { projectId, action, note: note.trim(), ...(mitTextform ? { textformDate: textform.date, textformChannel: textform.channel } : {}), + ...(mitAbrechnung ? { abrechnung } : {}), }) // Die Antwort enthaelt das ganze Projekt; uebernommen wird nur die geaenderte Funktion const updated = antwort?.projects?.[0] @@ -323,7 +343,7 @@ export default function CustomerFunktionen({ customerId }) { setStatus(key === 'zeiten' && action === 'disable' ? abschaltMeldung(antwort?.ersatzTicket) : speicherStatus(antwort, key)) } catch (err) { setConfirm(null) - setStatus({ ok: false, text: err.message }) + setStatus({ ok: false, text: featureFehlerText(err, key) }) } finally { setBusy(false) // Version und Hindernis der Anleitung haengen vom Zustand ab: Panel dieser Zeile neu laden @@ -334,7 +354,7 @@ export default function CustomerFunktionen({ customerId }) { } const teamGespeichert = (projectId, antwort) => setProjects((list) => mergeTeam(list, projectId, antwort)) - const ask = (projectId, key, action) => { setNote(''); setTextform(LEERE_TEXTFORM); setConfirm({ projectId, key, action }) } + const ask = (projectId, key, action) => { setNote(''); setTextform(LEERE_TEXTFORM); setAbrechnung(''); setConfirm({ projectId, key, action }) } const heute = berlinYmd(new Date()) const panelOffen = (projectId) => (Object.prototype.hasOwnProperty.call(panel, projectId) ? panel[projectId] : anleitungOffen(window.location.search, projectId)) const togglePanel = (projectId) => setPanel((p) => ({ ...p, [projectId]: !panelOffen(projectId) })) @@ -417,8 +437,9 @@ export default function CustomerFunktionen({ customerId }) {
{f.bestaetigen[confirm.action]} + {fragtAbrechnung(f.key, confirm.action, feature) && } setNote(e.target.value)} /> - +
diff --git a/src/lib/teamApi.js b/src/lib/teamApi.js index 1e9ea2f..9c3bf71 100644 --- a/src/lib/teamApi.js +++ b/src/lib/teamApi.js @@ -98,9 +98,10 @@ export const getFunktionen = (customerId) => request(`/funktionen/${enc(customer /** * Aktivieren (ohne Zahlung) oder Abschalten einer Funktion (key: news | termine | shop | zeiten | unterkunft); Antwort { projects: [das eine Projekt] }. * Terminplaner und Webshop im Modus on: beim Aktivieren zusaetzlich textformDate (JJJJ-MM-TT) und textformChannel (chat | email). + * abrechnung ('abo' | 'projekt', nur beim Aktivieren): wohin die Einrichtungszeit geht; 409 abo_fehlt bringt err.code und err.grund mit (featureFehlerText). */ -export const setFeature = (customerId, key, { projectId, action, note, textformDate, textformChannel }) => - request(`/funktionen/${enc(customerId)}/${enc(key)}`, { method: 'POST', body: featureBody({ projectId, action, note, textformDate, textformChannel }) }) +export const setFeature = (customerId, key, { projectId, action, note, textformDate, textformChannel, abrechnung }) => + request(`/funktionen/${enc(customerId)}/${enc(key)}`, { method: 'POST', body: featureBody({ projectId, action, note, textformDate, textformChannel, abrechnung }) }) export const setNewsFeature = (customerId, body) => setFeature(customerId, 'news', body) /** Einbauanleitung des Terminplaners: { stand, warnungen, abschnitte, text, state, ticketWoid, ticketVersion, hindernis } (Aufbereitung: panelModell). */ export const getTermineAnleitung = (customerId, projectId) => request(anleitungPfad(customerId, projectId))