AB-Kern: Nummernvergabe AB-JJJJ-NNNN nach Berliner Jahr

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:11:52 +00:00
1 parent 68ffac24c4
commit e3df5383d1
2 files changed
+76

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
/**
* Nummernvergabe der Auftragsbestätigungen: AB-JJJJ-NNNN, lückenlos je Berliner Kalenderjahr (Übersicht V4, L3).
* vergibNummer schreibt nichts und sperrt nicht selbst: der Aufrufer hält SPERRE_NUMMER (konstanten.js), und die Nummer
* ist erst mit dem Speichern der AB vergeben. Eine Nummer, die schon in Gebrauch ist, wird übersprungen.
*/
import { berlinDate } from '../finance/dates.js'
import { findAuftragByNummer, listAuftraege } from './auftragStore.js'
export function formatNummer(jahr, laufnummer) {
if (!Number.isInteger(jahr) || jahr < 2000 || jahr > 9999 || !Number.isInteger(laufnummer) || laufnummer < 1 || laufnummer > 9999) {
throw new RangeError('Ungültige Auftragsnummer.')
}
return `AB-${jahr}-${String(laufnummer).padStart(4, '0')}`
}
export async function vergibNummer(jetzt = new Date()) {
const jahr = Number(berlinDate(jetzt).slice(0, 4))
const vorhandene = await listAuftraege({ jahr })
let laufnummer = vorhandene.reduce((groesste, a) => Math.max(groesste, Number.isSafeInteger(a.laufnummer) ? a.laufnummer : 0), 0) + 1
while (await findAuftragByNummer(formatNummer(jahr, laufnummer))) laufnummer += 1
return { nummer: formatNummer(jahr, laufnummer), jahr, laufnummer }
}
+54
View File
@@ -0,0 +1,54 @@
import { test, after, beforeEach } from 'node:test'
import assert from 'node:assert/strict'
import { starteUmgebung, entwurfsDaten } from './helpers/auftraegeAb.js'
const umg = await starteUmgebung()
after(() => umg.schliesse())
beforeEach(umg.zuruecksetzen)
const { createAuftrag, updateAuftrag } = await import('../server/services/auftraege/auftragStore.js')
const { formatNummer, vergibNummer } = await import('../server/services/auftraege/nummern.js')
/** Eine versendete AB mit Nummer anlegen (über den Speicher von L0). */
async function legeNummerierteAn(jahr, laufnummer, patch = {}) {
const entwurf = await createAuftrag(entwurfsDaten())
return updateAuftrag(entwurf.id, { status: 'versendet', nummer: formatNummer(jahr, laufnummer), jahr, laufnummer, ...patch })
}
test('formatNummer: AB-JJJJ-NNNN mit vier Ziffern; ungültige Werte werfen RangeError', () => {
assert.equal(formatNummer(2026, 1), 'AB-2026-0001')
assert.equal(formatNummer(2026, 42), 'AB-2026-0042')
assert.equal(formatNummer(2027, 9999), 'AB-2027-9999')
for (const [jahr, lauf] of [[2026, 0], [2026, 10000], [2026, 1.5], [2026, '1'], [1999, 1], ['2026', 1], [2026, -3]]) {
assert.throws(() => formatNummer(jahr, lauf), RangeError, `${jahr}/${lauf}`)
}
})
test('vergibNummer: die erste Nummer des Jahres ist 0001', async () => {
assert.deepEqual(await vergibNummer(new Date('2026-10-06T10:00:00Z')), { nummer: 'AB-2026-0001', jahr: 2026, laufnummer: 1 })
})
test('vergibNummer: größte Laufnummer des Jahres plus 1 – Lücken egal, andere Jahre und Entwürfe zählen nicht', async () => {
for (const nr of [1, 2, 7]) await legeNummerierteAn(2026, nr)
await legeNummerierteAn(2025, 40)
await createAuftrag(entwurfsDaten()) // Entwurf ohne Nummer
assert.deepEqual(await vergibNummer(new Date('2026-10-06T10:00:00Z')), { nummer: 'AB-2026-0008', jahr: 2026, laufnummer: 8 })
assert.deepEqual(await vergibNummer(new Date('2025-03-01T10:00:00Z')), { nummer: 'AB-2025-0041', jahr: 2025, laufnummer: 41 })
})
test('vergibNummer: Jahreswechsel nach Berliner Zeit (31.12. 23:00 UTC ist in Berlin schon der 01.01.)', async () => {
for (const nr of [1, 2, 3]) await legeNummerierteAn(2026, nr)
assert.equal((await vergibNummer(new Date('2026-12-31T22:59:59Z'))).nummer, 'AB-2026-0004')
assert.deepEqual(await vergibNummer(new Date('2026-12-31T23:00:00Z')), { nummer: 'AB-2027-0001', jahr: 2027, laufnummer: 1 })
assert.equal((await vergibNummer(new Date('2027-01-01T08:00:00Z'))).nummer, 'AB-2027-0001')
})
test('vergibNummer: zählt auch bei mehr als 25 ABs im Jahr richtig (Seitengröße 25 wie bei Appwrite ohne limit)', async () => {
for (let i = 1; i <= 30; i += 1) await legeNummerierteAn(2026, i)
assert.equal((await vergibNummer(new Date('2026-10-06T10:00:00Z'))).nummer, 'AB-2026-0031')
})
test('vergibNummer: überspringt eine Nummer, die es schon gibt (Dokument mit nummer, aber ohne laufnummer)', async () => {
const entwurf = await createAuftrag(entwurfsDaten())
await updateAuftrag(entwurf.id, { status: 'versendet', nummer: 'AB-2026-0001' })
assert.deepEqual(await vergibNummer(new Date('2026-10-06T10:00:00Z')), { nummer: 'AB-2026-0002', jahr: 2026, laufnummer: 2 })
})