import { withLocks } from '../keyedLock.js' import { heute } from '../termine/zeit.js' import { UkFehler } from './fehler.js' import { naechte } from './datum.js' import { ladeEinstellungen, ladeRechnung, ladeUnterkunft, legeRechnungAn, listeRechnungen as listeRe, speichereEinstellungen } from './speicher.js' export { ladeRechnung } export const listeRechnungen = (featureId) => listeRe(featureId) const AUSSTELLER = ['firma', 'inhaber', 'strasse', 'plz', 'ort', 'email', 'telefon', 'steuernummer', 'ustId', 'iban', 'bic', 'bank'] /** * Naechste lueckenlose Nummer '--' vergeben und fn(nummer) unter DERSELBEN Sperre ausfuehren (Zaehler-Erhoehung und * Dokument-Anlage sind eine Einheit). Scheitert fn, wird der Zaehler auf den alten Stand zurueckgesetzt, sofern er noch unveraendert ist. * Jahr = Berliner Kalenderjahr, Jahreswechsel setzt den Zaehler auf 1. */ export function mitNummer(featureId, now, fn) { return withLocks(['uk-rechnungsnr:' + featureId], async () => { const einst = await ladeEinstellungen(featureId) const jahr = Number(heute(now).slice(0, 4)) const z = einst?.rechnungZaehler const n = z && z.jahr === jahr && Number.isInteger(z.n) ? z.n + 1 : 1 await speichereEinstellungen(featureId, { rechnungZaehler: { jahr, n } }) const praefix = (einst?.rechnungsPraefix || '').trim() || 'RE' try { return await fn(`${praefix}-${jahr}-${String(n).padStart(4, '0')}`) } catch (err) { try { const jetzt = (await ladeEinstellungen(featureId))?.rechnungZaehler if (jetzt && jetzt.jahr === jahr && jetzt.n === n) await speichereEinstellungen(featureId, { rechnungZaehler: z ?? null }) } catch (err2) { console.warn(`[unterkunft] Rechnungszaehler nicht zurueckgesetzt: ${err2?.name ?? 'Fehler'}`) } throw err } }) } /** Naechste lueckenlose Nummer (ohne Dokument; fuer Tests und Vorschau). */ export const naechsteNummer = (featureId, now = new Date()) => mitNummer(featureId, now, (nummer) => nummer) const negiere = (summen) => ({ ...summen, bruttoCent: -summen.bruttoCent, nettoCent: -summen.nettoCent, ust: (summen.ust || []).map((u) => ({ ...u, nettoCent: -u.nettoCent, steuerCent: -u.steuerCent })), }) /** Rechnung oder Stornorechnung zu einer Anfrage ausstellen (Nummer, Dokument). Die Sperre der Unterkunft haelt der Aufrufer. */ export async function stelleAus(ctx, anfrage, { art = 'rechnung', bezugNummer, bezug, now = new Date() } = {}) { if (!Array.isArray(anfrage?.posten) || !anfrage.summen || typeof anfrage.summen.bruttoCent !== 'number') { throw new UkFehler(409, 'status_ungueltig', { posten: 'Posten oder Summen fehlen.' }) } const storno = art === 'storno' const e = ctx.einstellungen || {} // Stornorechnung: Aussteller und Empfaenger der Ursprungsrechnung (nicht die heutigen Einstellungen) const ursprung = storno && bezug?.inhalt && typeof bezug.inhalt === 'object' ? bezug.inhalt : null const aussteller = ursprung?.aussteller ?? Object.fromEntries(AUSSTELLER.map((k) => [k, e[k] ?? ''])) const unterkunft = await ladeUnterkunft(anfrage.unterkunftId) const posten = anfrage.posten.map((p) => (storno ? { ...p, einzelCent: -p.einzelCent } : { ...p })) const summen = storno ? negiere(anfrage.summen) : anfrage.summen const inhalt = { aussteller, empfaenger: ursprung ? (ursprung.empfaenger ?? null) : (anfrage.rechnungsadresse ?? null), unterkunft: { name: unterkunft?.name ?? '' }, von: anfrage.von, bis: anfrage.bis, naechte: naechte(anfrage.von, anfrage.bis), personen: anfrage.personen, posten, summen, steuerart: anfrage.summen.steuerart ?? ((anfrage.summen.ust || []).length === 0 ? 'kleinunternehmer' : 'regel'), zahlart: anfrage.zahlart || '', bezugNummer: bezugNummer ?? bezug?.nummer ?? '', anfrageNummer: anfrage.nummer, } return mitNummer(ctx.featureId, now, (nummer) => legeRechnungAn({ featureId: ctx.featureId, anfrageId: anfrage.$id, art, nummer, datum: heute(now), bruttoCent: summen.bruttoCent, inhalt, })) }