/** * Sichten auf eine Auftragsbestätigung (Übersicht V3/V4): Kundensnapshot, früheste Leistung bei Privatpersonen, * Zahlungsplan- und Aufwandstext, Ansicht fürs Team und öffentliche Ansicht für Token- und Kundenrouten. * Alles rein (kein Appwrite, keine Konfiguration); die Wortlaute kommen aus texte.js. */ import { withdrawalDeadline } from '../deadlines.js' import { addDays, berlinDate } from '../finance/dates.js' import { euroDe } from '../finance/format.js' import { AGB_FASSUNG, KUNDENARTEN, LIMITS, STUNDENSATZ_STANDARD_CENTS } from './konstanten.js' import { AB_TEXTE, BESTAETIGUNG_TEXT, FRUEHBEGINN_TEXT } from './texte.js' // Kürzungen, damit der Snapshot samt JSON in die Spalte kundeJson (2000 Zeichen) passt const SNAPSHOT_FELDER = [ ['name', 'name', 120], ['firma', 'companyName', 120], ['strasse', 'street', 120], ['plz', 'postalCode', 20], ['ort', 'city', 80], ['land', 'country', 60], ['email', 'email', 200], ['kundennummer', 'code', 40], ] const text = (wert, max) => (typeof wert === 'string' ? wert.trim().slice(0, max).trim() : '') export function kundeSnapshot(customerDoc) { const doc = customerDoc || {} const snapshot = {} for (const [ziel, quelle, max] of SNAPSHOT_FELDER) snapshot[ziel] = text(doc[quelle], max) snapshot.kundenart = KUNDENARTEN.includes(doc.kundenart) ? doc.kundenart : '' // feste Reihenfolge der Schlüssel wie KundeSnapshot in V3 const { name, firma, strasse, plz, ort, land, email, kundenart, kundennummer } = snapshot return { name, firma, strasse, plz, ort, land, email, kundenart, kundennummer } } /** Früheste Leistung bei Privatpersonen ohne Frühbeginn: Tag nach Ablauf der Widerrufsfrist. */ export function fruehesterBeginn(auftrag) { if (auftrag.kunde?.kundenart !== 'privat' || auftrag.fruehbeginn || !auftrag.gesendetAm) return null const gesendet = new Date(auftrag.gesendetAm) if (Number.isNaN(gesendet.getTime())) return null return addDays(withdrawalDeadline(gesendet).lastDay, 1) } export function zahlungsplanText(auftrag) { const ziel = Number.isSafeInteger(auftrag.zahlungszielTage) ? auftrag.zahlungszielTage : LIMITS.standardZahlungszielTage const plan = auftrag.zahlungsplan || { art: 'nach_abnahme' } const bausteine = AB_TEXTE.zahlungsplan if (plan.art === 'monatlich') return bausteine.monatlich({ ziel }) if (plan.art === 'frei') return bausteine.frei({ text: plan.text || '', ziel }) if (plan.art === 'anzahlung' && Number.isSafeInteger(auftrag.summeCents) && typeof plan.prozent === 'number' && Number.isFinite(plan.prozent)) { const anzahlung = Math.round((auftrag.summeCents * plan.prozent) / 100) return bausteine.anzahlung({ prozent: plan.prozent, anzahlung: euroDe(anzahlung), rest: euroDe(auftrag.summeCents - anzahlung), ziel }) } return bausteine.nach_abnahme({ ziel }) } /** Stunden als Text mit Dezimalkomma ohne überflüssige Nullen: 600 → '10', 135 → '2,25', 90 → '1,5'. */ const stundenText = (minuten) => String(Math.round((minuten / 60) * 100) / 100).replace('.', ',') export function aufwandText(auftrag) { if (auftrag.art !== 'aufwand') return null const minuten = auftrag.schaetzungMinuten return AB_TEXTE.aufwand({ satz: euroDe(auftrag.stundensatzCents ?? STUNDENSATZ_STANDARD_CENTS), schaetzung: Number.isSafeInteger(minuten) && minuten > 0 ? stundenText(minuten) : null, }) } /** Auftrag ohne tokenHash, pdfDatei und bestaetigtPdfSha256, plus die abgeleiteten Felder. */ export function auftragFuerTeam(auftrag) { const { tokenHash, pdfDatei, bestaetigtPdfSha256, ...rest } = auftrag return { ...rest, pdfVorhanden: Boolean(pdfDatei), fruehesterBeginn: fruehesterBeginn(auftrag), zahlungsplanText: zahlungsplanText(auftrag), aufwandText: aufwandText(auftrag), } } /** Nur für Token- und Kundenrouten: keine Kundendaten, kein Token, keine Pfade. */ export function oeffentlicheAnsicht(auftrag) { const kundenart = auftrag.kunde?.kundenart || '' return { nummer: auftrag.nummer, datum: auftrag.gesendetAm ? berlinDate(new Date(auftrag.gesendetAm)) : null, titel: auftrag.titel, art: auftrag.art, positionen: auftrag.positionen.map((p) => ({ beschreibung: p.beschreibung, menge: p.menge, einzelpreisCents: p.einzelpreisCents, summeCents: p.menge * p.einzelpreisCents, })), summeCents: auftrag.summeCents, stundensatzCents: auftrag.stundensatzCents, schaetzungMinuten: auftrag.schaetzungMinuten, aufwandText: aufwandText(auftrag), zahlungsplanText: zahlungsplanText(auftrag), startAm: auftrag.startAm, fertigBis: auftrag.fertigBis, kundenart, status: auftrag.status, bestaetigtAm: auftrag.bestaetigtAm, fruehbeginn: auftrag.fruehbeginn, fruehesterBeginn: fruehesterBeginn(auftrag), agbUrl: `/rechtliches/agb-${auftrag.agbFassung || AGB_FASSUNG}.pdf`, texte: { bestaetigung: BESTAETIGUNG_TEXT, fruehbeginn: kundenart === 'privat' ? FRUEHBEGINN_TEXT : null, kleinunternehmer: AB_TEXTE.kleinunternehmer, }, } }