Files
Webklar-Kundenbereich/test/unterkunftPdf.test.js
T
2026-10-07 14:06:11 +00:00

111 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { PdfDoc } from '../server/services/unterkunft/pdf.js'
import { rechnungPdf } from '../server/services/unterkunft/rechnungPdf.js'
// Buchungskalender Task 19: PDF-Writer und Rechnungs-PDF
const latin = (b) => b.toString('latin1')
function inhalt(extra = {}) {
return {
aussteller: { firma: 'Haus Seeblick', inhaber: 'Anna Muster', strasse: 'Seestr. 1', plz: '88662', ort: 'Ueberlingen', email: 'a@x.de', telefon: '0123', steuernummer: '12/345/67890', ustId: '', iban: 'DE02 1203 0000 0000 2020 51', bic: 'BYLADEM1001', bank: 'DKB' },
empfaenger: { name: 'Max Gast', firma: '', strasse: 'Weg 2', plz: '10115', ort: 'Berlin', land: 'Deutschland' },
unterkunft: { name: 'Ferienwohnung Süd' },
von: '2026-11-12', bis: '2026-11-15', naechte: 3, personen: 2,
posten: [{ text: 'Übernachtung (Frühstück), 3 Nächte', menge: 3, einzelCent: 9000, ustProzent: 7 }, { text: 'Endreinigung', menge: 1, einzelCent: 5000, ustProzent: 19 }],
summen: { bruttoCent: 32000, nettoCent: 29000, ust: [{ satz: 7, nettoCent: 25234, steuerCent: 1766 }, { satz: 19, nettoCent: 4202, steuerCent: 798 }], steuerart: 'regel' },
steuerart: 'regel', zahlart: 'vorort', bezugNummer: '', anfrageNummer: 'A-1', ...extra,
}
}
const kleinInhalt = () => inhalt({
steuerart: 'kleinunternehmer',
posten: [{ text: 'Übernachtung, 3 Nächte', menge: 3, einzelCent: 9000, ustProzent: 0 }],
summen: { bruttoCent: 27000, nettoCent: 27000, ust: [], steuerart: 'kleinunternehmer' },
})
test('PdfDoc: Struktur, xref, Kopf und Ende', () => {
const d = new PdfDoc()
d.text(50, 50, 'Hallo')
const b = d.toBuffer()
const s = latin(b)
assert.ok(s.startsWith('%PDF-1.4'))
assert.ok(s.endsWith('%%EOF\n'))
const start = Number(/startxref\n(\d+)\n/.exec(s)[1])
assert.equal(s.slice(start, start + 4), 'xref')
const eintraege = s.slice(start).split('\n').filter((z) => /^\d{10} 00000 n $/.test(z))
assert.ok(eintraege.length >= 6)
eintraege.forEach((z, k) => {
const off = Number(z.slice(0, 10))
assert.ok(s.slice(off).startsWith(`${k + 1} 0 obj`), `Objekt ${k + 1}`)
})
})
test('PdfDoc: CP1252, Euro, Escaping, Minus', () => {
const d = new PdfDoc()
d.text(10, 10, 'Übernachtung ß 5 € −3 (a) \\ ☃')
const s = latin(d.toBuffer())
assert.ok(s.includes('\xdcbernachtung \xdf 5 \x80 -3 \\(a\\) \\\\ ?'))
assert.ok(s.includes('/Encoding /WinAnsiEncoding'))
})
test('PdfDoc: Breiten und Umbruch', () => {
const d = new PdfDoc()
assert.equal(Math.round(d.breite('Hello', 10) * 100), 2278)
assert.ok(d.breite('Hello', 10, true) > d.breite('Hello', 10))
const z = d.umbrechen('eins zwei drei vier fünf sechs', 60, 10)
assert.ok(z.length > 1)
z.forEach((x) => assert.ok(d.breite(x, 10) <= 60))
assert.ok(d.umbrechen('Donaudampfschifffahrtsgesellschaft', 50, 10).length > 1)
assert.ok(d.breite('x', 10) > 0 && d.breite('€', 10) > 0)
})
test('Rechnung: Regelbesteuerung', () => {
const s = latin(rechnungPdf(inhalt(), { art: 'rechnung', nummer: 'RE-2026-0001', datum: '2026-11-01' }))
assert.ok(s.includes('RE-2026-0001'))
assert.ok(s.includes('Enthaltene USt 7 %'))
assert.ok(s.includes('Nettobetrag 19 %'))
assert.ok(s.includes('12.11.2026'))
assert.ok(s.includes('Beherbergung Ferienwohnung S\xfcd'))
assert.ok(s.includes('Zahlung vor Ort'))
assert.ok(!s.includes('Gem\xe4\xdf \xa7 19 UStG'))
assert.ok(s.includes('Steuernummer 12/345/67890'))
assert.ok(s.includes('IBAN'))
})
test('Rechnung: Kleinunternehmer und Online-Zahlung', () => {
const s = latin(rechnungPdf({ ...kleinInhalt(), zahlart: 'online' }, { art: 'rechnung', nummer: 'RE-2026-0002', datum: '2026-11-01' }))
assert.ok(s.includes('Gem\xe4\xdf \xa7 19 UStG wird keine Umsatzsteuer berechnet.'))
assert.ok(s.includes('Online-Zahlung'))
assert.ok(!s.includes('Enthaltene USt'))
})
test('Storno: Titel mit Bezug, negative Betraege', () => {
const neg = kleinInhalt()
neg.posten = neg.posten.map((p) => ({ ...p, einzelCent: -p.einzelCent }))
neg.summen = { ...neg.summen, bruttoCent: -27000, nettoCent: -27000 }
neg.bezugNummer = 'RE-2026-0002'
const s = latin(rechnungPdf(neg, { art: 'storno', nummer: 'RE-2026-0003', datum: '2026-11-02' }))
assert.ok(s.includes('Stornorechnung zu RE-2026-0002'))
assert.ok(s.includes('-270,00 \x80'))
})
test('Viele Posten: Seitenumbruch', () => {
const posten = Array.from({ length: 40 }, (_, k) => ({ text: `Posten ${k + 1}`, menge: 1, einzelCent: 1000, ustProzent: 0 }))
const s = latin(rechnungPdf({ ...kleinInhalt(), posten }, { art: 'rechnung', nummer: 'RE-2026-0004', datum: '2026-11-01' }))
assert.ok(s.includes('/Count 2'))
assert.ok(s.includes('Seite 2 von 2'))
})
test('Deterministisch', () => {
const o = { art: 'rechnung', nummer: 'RE-2026-0001', datum: '2026-11-01' }
assert.ok(rechnungPdf(inhalt(), o).equals(rechnungPdf(inhalt(), o)))
})
test('Beispiel-PDF schreiben (nur wenn Zielordner existiert)', async () => {
const { mkdirSync, writeFileSync } = await import('node:fs')
const dir = '/root/reports/buchungskalender-2026-10-07'
if (process.env.UK_BEISPIEL_PDF !== '1') return
mkdirSync(dir, { recursive: true })
writeFileSync(`${dir}/beispiel-rechnung.pdf`, rechnungPdf(inhalt(), { art: 'rechnung', nummer: 'RE-2026-0001', datum: '2026-11-01' }))
})