diff --git a/server/services/finance/settings.js b/server/services/finance/settings.js index 0426cea..f312384 100644 --- a/server/services/finance/settings.js +++ b/server/services/finance/settings.js @@ -21,6 +21,7 @@ const SETTINGS_ID = 'main' // Dokument-IDs wie in Appwrite (hoechstens 36 Zeichen, auch eine UUID); alles andere kann es nicht geben const DOCUMENT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,35}$/ const MAX_BANK_DETAILS = 500 +const MAX_STEUERNUMMER = 40 const MAX_NAME = 120 const MAX_MATCH = 120 const MAX_BUFFER_CENTS = 100_000_000 @@ -35,6 +36,7 @@ export const DEFAULT_SETTINGS = Object.freeze({ previousYearRevenueCents: null, estPrepaymentCents: 0, bankDetailsText: '', + steuernummer: '', }) const invalid = (field, message) => new SettingsError(400, `${field}: ${message}`) @@ -74,6 +76,15 @@ const SETTINGS_RULES = { wholeNumber('estPrepaymentCents', v, 0, Number.MAX_SAFE_INTEGER, 'Die ESt-Vorauszahlung muss eine ganze Zahl ab 0 Cent sein.'), bankDetailsText: (v) => text('bankDetailsText', v, { min: 0, max: MAX_BANK_DETAILS }, `Der Bankverbindungstext muss ein Text mit höchstens ${MAX_BANK_DETAILS} Zeichen sein.`), + // Steuernummer oder USt-IdNr. für die Auftragsbestätigung: Buchstaben A-Z, Ziffern, Leerzeichen, / - und . (kein + // Tabulator, kein Zeilenumbruch, keine Umlaute); '' löscht den Wert. Die Meldung ohne Feldnamen (sie erscheint im Formular). + steuernummer: (v) => { + const trimmed = typeof v === 'string' ? v.trim() : null + if (trimmed === null || trimmed.length > MAX_STEUERNUMMER || !/^[A-Za-z0-9/ .-]*$/.test(trimmed)) { + throw new SettingsError(400, 'Steuernummer ungültig.') + } + return trimmed + }, } const FIXED_COST_RULES = { @@ -154,7 +165,8 @@ async function saveSettings(data) { /** * Einstellungen aendern; patch nennt nur die zu aendernden Felder: minBufferCents 0 bis 100.000.000, * taxReservePercent 0 bis 60, previousYearRevenueCents >= 0 oder null (null loescht den Wert), estPrepaymentCents >= 0, - * bankDetailsText hoechstens 500 Zeichen (ohne Rand-Leerraum gespeichert). Ein unbekanntes oder ungueltiges Feld: + * bankDetailsText hoechstens 500 Zeichen (ohne Rand-Leerraum gespeichert), steuernummer hoechstens 40 Zeichen aus + * Buchstaben, Ziffern, Leerzeichen, / - und . (getrimmt gespeichert, '' loescht). Ein unbekanntes oder ungueltiges Feld: * SettingsError 400 mit dem Feldnamen, es wird nichts gespeichert. Legt das Dokument an, falls es fehlt; ein leerer * patch aendert und legt nichts an. Liefert die Einstellungen danach. */ diff --git a/test/financeSettings.test.js b/test/financeSettings.test.js index 11eef04..6bd8900 100644 --- a/test/financeSettings.test.js +++ b/test/financeSettings.test.js @@ -43,7 +43,7 @@ const storedSettings = () => aw.docs(SETTINGS()).get('main') const storedCost = (id) => aw.docs(COSTS()).get(id) const callsTo = (method, path) => st.calls.filter((c) => c.method === method && (path instanceof RegExp ? path.test(c.path) : c.path === path)) -const DEFAULTS = { minBufferCents: 0, taxReservePercent: 30, previousYearRevenueCents: null, estPrepaymentCents: 0, bankDetailsText: '' } +const DEFAULTS = { minBufferCents: 0, taxReservePercent: 30, previousYearRevenueCents: null, estPrepaymentCents: 0, bankDetailsText: '', steuernummer: '' } const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ async function reset() { @@ -124,7 +124,7 @@ test('GET /einstellungen: gespeicherte Werte ueberschreiben die Standardwerte, f assert.deepEqual((await getSettings()).einstellungen, { ...DEFAULTS, previousYearRevenueCents: 0, bankDetailsText: 'IBAN DE00' }) }) -test('GET /einstellungen: nur die fuenf Felder, nie Appwrite-Metafelder', async () => { +test('GET /einstellungen: nur die sechs Felder, nie Appwrite-Metafelder', async () => { aw.seed(SETTINGS(), { $id: 'main', taxReservePercent: 25, secret: 'intern' }) const { einstellungen } = await getSettings() assert.deepEqual(Object.keys(einstellungen).sort(), Object.keys(DEFAULTS).sort()) @@ -184,6 +184,28 @@ test('PATCH: der Bankverbindungstext wird ohne Rand-Leerraum gespeichert', async assert.equal(storedSettings().bankDetailsText, 'IBAN DE00 0000') }) +test('PATCH steuernummer: Vorgabe leer, getrimmt gespeichert; Buchstaben, Ziffern, Leerzeichen, / - und . bis 40 Zeichen, sonst 400', async () => { + assert.equal((await getSettings()).einstellungen.steuernummer, '') + const getrimmt = await patchSettings({ steuernummer: ' 12/345/67890 ' }) + assert.equal(getrimmt.status, 200, JSON.stringify(getrimmt.body)) + assert.equal(getrimmt.body.einstellungen.steuernummer, '12/345/67890') + assert.equal(storedSettings().steuernummer, '12/345/67890') + assert.equal((await getSettings()).einstellungen.steuernummer, '12/345/67890') + for (const wert of ['DE123456789', 'de 123.456.789', '12 345 67890', '12-345-678', 'A'.repeat(40), ` ${'1'.repeat(40)} `, ' ', '']) { + const res = await patchSettings({ steuernummer: wert }) + assert.equal(res.status, 200, JSON.stringify(wert)) + assert.equal(res.body.einstellungen.steuernummer, wert.trim()) + assert.equal(storedSettings().steuernummer, wert.trim()) + } + assert.equal((await patchSettings({ steuernummer: 'DE123456789' })).status, 200) + for (const wert of ['