Buchungskalender: Abschluss – S2 Rechnungsnummer unter einer Sperre, unique-Index; S3 Storno mit Ursprungs-Aussteller

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-10-07 15:21:19 +00:00
1 parent 435dd0b746
commit 727613d0e2
4 files changed
+87 -19

No files matched your search

+49
View File
@@ -65,6 +65,55 @@ test('stelleAus storno negiert Posten und Summen, traegt bezugNummer', async ()
assert.equal((await re.ladeRechnung(s.$id)).nummer, 'RE-2026-0002')
})
const ANF = {
$id: 'a9', unterkunftId: 'u1', nummer: 'A-WXYZ', von: '2026-11-10', bis: '2026-11-12', personen: 2, zahlart: 'vorort',
rechnungsadresse: { name: 'Gast Neu' },
posten: [{ text: 'Nacht', menge: 2, einzelCent: 10000, ustProzent: 7 }],
summen: { bruttoCent: 20000, nettoCent: 18692, ust: [{ satz: 7, nettoCent: 18692, steuerCent: 1308 }] },
}
test('Rechnungsnummer: scheitert die Dokument-Anlage, bleibt keine Luecke (Zaehler zurueck)', async () => {
const ctx = { featureId: 'f1', einstellungen: { firma: 'Haus GmbH' } }
const r1 = await re.stelleAus(ctx, ANF, { art: 'rechnung', now: NOW })
assert.equal(r1.nummer, 'RE-2026-0001')
aw.failNext({ collection: 'ukRechnungen', method: 'POST', status: 500 })
await assert.rejects(re.stelleAus(ctx, ANF, { art: 'rechnung', now: NOW }))
assert.deepEqual((await sp.ladeEinstellungen('f1')).rechnungZaehler, { jahr: 2026, n: 1 })
const r2 = await re.stelleAus(ctx, ANF, { art: 'rechnung', now: NOW })
assert.equal(r2.nummer, 'RE-2026-0002')
// erste Rechnung ueberhaupt scheitert: Zaehler bleibt leer
const ctx2 = { featureId: 'f2', einstellungen: {} }
aw.failNext({ collection: 'ukRechnungen', method: 'POST', status: 500 })
await assert.rejects(re.stelleAus(ctx2, ANF, { art: 'rechnung', now: NOW }))
assert.ok(!((await sp.ladeEinstellungen('f2'))?.rechnungZaehler?.n > 0))
assert.equal((await re.stelleAus(ctx2, ANF, { art: 'rechnung', now: NOW })).nummer, 'RE-2026-0001')
})
test('Rechnungsnummer: parallele Ausstellung mit einem Fehler dazwischen bleibt lueckenlos', async () => {
const ctx = { featureId: 'f1', einstellungen: {} }
aw.failNext({ collection: 'ukRechnungen', method: 'POST', status: 500 })
const res = await Promise.allSettled(Array.from({ length: 4 }, () => re.stelleAus(ctx, ANF, { art: 'rechnung', now: NOW })))
const nr = res.filter((x) => x.status === 'fulfilled').map((x) => x.value.nummer).sort()
assert.deepEqual(nr, ['RE-2026-0001', 'RE-2026-0002', 'RE-2026-0003'])
})
test('Stornorechnung traegt Aussteller und Empfaenger der Ursprungsrechnung, nicht die heutigen', async () => {
const alt = { featureId: 'f1', einstellungen: { firma: 'Alte Firma', iban: 'DE-ALT', steuernummer: '1/1' } }
const r = await re.stelleAus(alt, ANF, { art: 'rechnung', now: NOW })
const neu = { featureId: 'f1', einstellungen: { firma: 'Neue Firma', iban: 'DE-NEU' } }
const s = await re.stelleAus(neu, { ...ANF, rechnungsadresse: { name: 'Anderer Name' } }, { art: 'storno', bezug: r, now: NOW })
assert.equal(s.inhalt.aussteller.firma, 'Alte Firma')
assert.equal(s.inhalt.aussteller.iban, 'DE-ALT')
assert.deepEqual(s.inhalt.empfaenger, { name: 'Gast Neu' })
assert.equal(s.inhalt.bezugNummer, r.nummer)
})
test('Schema: Rechnungsnummer ist je Feature eindeutig (unique-Index)', async () => {
const { UNTERKUNFT_COLLECTIONS } = await import('../server/services/unterkunft/schema.js')
const i = UNTERKUNFT_COLLECTIONS.ukRechnungen.indexes.find((x) => x.type === 'unique')
assert.deepEqual(i.attributes, ['featureId', 'nummer'])
})
test('Token: Hash passt nur zum Token; Formtoken', () => {
const { token, hash } = tk.neuesToken()
assert.equal(tk.tokenPasst(hash, token), true)