Kundenansicht P5: POST /api/funktionen/:key/einschalten (nur neue Ansicht)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:07:30 +00:00
1 parent f5b43c55a9
commit e30318a470
3 files changed
+431 -15

No files matched your search

+23 -13
View File
@@ -34,6 +34,14 @@ export const EINSCHALTEN_TEXTE = Object.freeze({
nichtPruefbar: 'Gerade nicht prüfbar – bitte versuch es gleich noch einmal.',
})
/** Berliner Kalendertag eines Zeitpunkts (ISO) als TT.MM.JJJJ; kein Zeitpunkt ist ein TypeError. */
export function datumDe(zeitpunkt) {
const ms = Date.parse(zeitpunkt)
if (Number.isNaN(ms)) throw new TypeError(`datumDe: kein Zeitpunkt: ${String(zeitpunkt)}`)
const [jahr, monat, tag] = berlinDay(new Date(ms)).split('-')
return `${tag}.${monat}.${jahr}`
}
/**
* Kundentext zu einem Abo-Grund. Bei beginnt_spaeter steht der Berliner Tag von `ab` (ISO) als TT.MM.JJJJ im Text,
* z. B. "Dein Abo beginnt am 03.11.2026. Ab dann kannst du einschalten."
@@ -41,11 +49,7 @@ export const EINSCHALTEN_TEXTE = Object.freeze({
export function aboFehlerText(grund, ab = null) {
if (!Object.hasOwn(EINSCHALTEN_TEXTE.abo, grund)) throw new TypeError(`aboFehlerText: unbekannter Grund ${String(grund)}`)
const text = EINSCHALTEN_TEXTE.abo[grund]
if (grund !== 'beginnt_spaeter') return text
const ms = Date.parse(ab)
if (Number.isNaN(ms)) throw new TypeError('aboFehlerText: beginnt_spaeter braucht ab als Zeitpunkt')
const [jahr, monat, tag] = berlinDay(new Date(ms)).split('-')
return text.replace('{datum}', `${tag}.${monat}.${jahr}`)
return grund === 'beginnt_spaeter' ? text.replace('{datum}', datumDe(ab)) : text
}
/** 409 abo_fehlt: body.grund, body.wechsel (Hosting-Kunde: Wechsel ins Wartungs-Abo anbieten) und bei beginnt_spaeter body.ab. */
@@ -54,8 +58,11 @@ const aboFehlt = (grund, ab) =>
const konflikt = (code, text) => bestellFehler(409, text, code)
/** Abo frisch lesen und gegen die Funktion pruefen (immer ohne Zwischenspeicher); Lesefehler sind 503, nie stillschweigend erlaubt. */
async function pruefeAbo(customerId, key, now) {
/**
* Abo frisch lesen und gegen die Funktion pruefen (immer ohne Zwischenspeicher): 409 abo_fehlt oder nichts. Lesefehler sind 503,
* nie stillschweigend erlaubt. Auch fuer die Vorpruefungen der Route.
*/
export async function pruefeAboDeckung(customerId, key, now = new Date()) {
let ergebnis
try {
ergebnis = await aboDeckt(customerId, key, { now })
@@ -69,8 +76,11 @@ async function pruefeAbo(customerId, key, now) {
/** setup ohne Ticket und durch Abo oder Projekt eingeschaltet: der Dienst heilt (nur das Ticket), es entsteht nichts Neues. */
const heilbar = (doc) => doc?.state === 'setup' && !doc.ticketId && ['abo', 'projekt'].includes(unlockedByVon(doc))
/** Zustand des Dokuments pruefen: wirft den Konflikt mit Code; Heilung und ein frisches oder ausgeschaltetes Dokument sind frei. */
function pruefeZustand(doc) {
/**
* Zustand des Dokuments pruefen: wirft den 409 mit Code (bestellung_laeuft, abgeschaltet, bereits_eingeschaltet); ein fehlendes oder
* ausgeschaltetes (off) Dokument und die Heilung (setup ohne Ticket, durch Abo oder Projekt) sind frei. Auch fuer die Vorpruefungen der Route.
*/
export function pruefeEinschaltZustand(doc) {
if (!doc || doc.state === 'off') return
if (doc.state === 'ordered') throw konflikt('bestellung_laeuft', EINSCHALTEN_TEXTE.laeuft)
if (doc.state === 'disabled') throw konflikt('abgeschaltet', EINSCHALTEN_TEXTE.abgeschaltet)
@@ -118,8 +128,8 @@ export async function einschalten({ customerId, projectId, key, by, abrechnung,
// liefert dieselben Codes wie unten (nur fuer den Kunden, das Team schaltet wie bisher)
if (key === 'zeiten') {
const vorpruefung = async () => {
await pruefeAbo(customerId, key, now)
pruefeZustand(await getFeatureDoc(customerId, projectId, key))
await pruefeAboDeckung(customerId, key, now)
pruefeEinschaltZustand(await getFeatureDoc(customerId, projectId, key))
}
const { doc } = await einschaltenZeiten({ customerId, projectId, by, staff, nachweis, now, abrechnung, vorpruefung })
return { doc, ticketWoid: await ticketWoid(doc.ticketId) }
@@ -127,11 +137,11 @@ export async function einschalten({ customerId, projectId, key, by, abrechnung,
const doc = await withLocks([`feature-order:${customerId}:${projectId}`], async () => {
// 1 Abo frisch (nie aus einem Zwischenspeicher); fuer das Team entfaellt es
if (by === 'kunde') await pruefeAbo(customerId, key, now)
if (by === 'kunde') await pruefeAboDeckung(customerId, key, now)
// 2 Zustand: Heilung (setup ohne Ticket, durch Abo oder Projekt) legt nur das Ticket an
const bisher = await getFeatureDoc(customerId, projectId, key)
pruefeZustand(bisher)
pruefeEinschaltZustand(bisher)
if (heilbar(bisher)) {
const gespeichert = Object.hasOwn(ABRECHNUNGS_ZEILEN, bisher.einrichtungAbrechnung) ? bisher.einrichtungAbrechnung : abrechnung
return legeTicketAn(bisher, { key, feature, project, customerId, abrechnung: gespeichert })