106 lines
4.9 KiB
JavaScript
106 lines
4.9 KiB
JavaScript
/**
|
|
* 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,
|
|
},
|
|
}
|
|
}
|