feat(kantine-datev): Kantinenabrechnung als Modul in neuer Gruppe Finanzbuchhaltung
- neue Seitenleisten-Kategorie accounting (Finanzbuchhaltung / Financial accounting) - CSV (UTF-8, UTF-8 mit BOM, Windows-1252) pruefen, Vorschau mit Zeilenfehlern, DATEV-Lohn-ASCII-Export - Beraternummer, Mandantennummer, Lohnart je Mandant als Admin-Einstellung (KantineDatevConfig mit Zeilenschutz), anfangs leer - hochgeladene Daten werden nicht gespeichert; Download per Blob (auch im Desktop-Client) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { validateKantinenData } from './kantine-csv.validator';
|
||||
import type { KantinenRawRow } from './kantine-datev.types';
|
||||
|
||||
function row(over: Partial<KantinenRawRow> = {}): KantinenRawRow {
|
||||
return {
|
||||
personalNr: '100',
|
||||
name: 'Max Muster',
|
||||
menge: '1',
|
||||
ekPreis: '1,00',
|
||||
netto: '1,00',
|
||||
zuAbschlag: '0',
|
||||
mwst: '0',
|
||||
zuschuss: '0',
|
||||
betrag: '7,94',
|
||||
abrechnungVon: '01.03.2026',
|
||||
abrechnungBis: '31.03.2026',
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
describe('validateKantinenData', () => {
|
||||
it('akzeptiert eine gueltige Zeile und bestimmt den Monat aus "bis"', () => {
|
||||
const result = validateKantinenData([row()]);
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.abrechnungsMonat).toBe('03/2026');
|
||||
});
|
||||
|
||||
it('meldet nicht numerische und fehlende Personalnummern', () => {
|
||||
const r = validateKantinenData([row({ personalNr: 'A12' }), row({ personalNr: '' })]);
|
||||
expect(r.errors.map((e) => e.code)).toEqual(['personalNrNotNumeric', 'personalNrMissing']);
|
||||
expect(r.errors[0].message).toBe('Personalnummer muss numerisch sein');
|
||||
});
|
||||
|
||||
it('lehnt Betraege mit Tausenderpunkt oder Minus ab (wie die Vorlage)', () => {
|
||||
const r = validateKantinenData([
|
||||
row({ betrag: '1.234,56' }),
|
||||
row({ betrag: '-5,00' }),
|
||||
row({ betrag: '' }),
|
||||
]);
|
||||
expect(r.errors.map((e) => e.code)).toEqual(['betragFormat', 'betragFormat', 'betragMissing']);
|
||||
});
|
||||
|
||||
it('prueft das Datumsformat', () => {
|
||||
const r = validateKantinenData([row({ abrechnungVon: '2026-03-01', abrechnungBis: '' })]);
|
||||
expect(r.errors.map((e) => e.code)).toEqual(['vonFormat', 'bisMissing']);
|
||||
});
|
||||
|
||||
it('meldet von/bis in verschiedenen Monaten mit Zeile = Index + 2', () => {
|
||||
const r = validateKantinenData([row(), row({ abrechnungVon: '28.02.2026' })]);
|
||||
expect(r.errors).toEqual([
|
||||
expect.objectContaining({
|
||||
row: 3,
|
||||
code: 'multiMonthRange',
|
||||
field: 'abrechnungVon/abrechnungBis',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('nimmt die echte Dateizeile, wenn der Parser sie mitliefert', () => {
|
||||
const r = validateKantinenData([row({ personalNr: 'x', line: 9 })]);
|
||||
expect(r.errors[0].row).toBe(9);
|
||||
});
|
||||
|
||||
it('warnt bei zwei Abrechnungsmonaten und behaelt den ersten', () => {
|
||||
const r = validateKantinenData([
|
||||
row(),
|
||||
row({ abrechnungVon: '01.04.2026', abrechnungBis: '30.04.2026' }),
|
||||
]);
|
||||
expect(r.isValid).toBe(true);
|
||||
expect(r.abrechnungsMonat).toBe('03/2026');
|
||||
expect(r.warnings).toHaveLength(1);
|
||||
expect(r.warnings[0]).toMatchObject({
|
||||
code: 'multipleMonths',
|
||||
params: { months: ['03/2026', '04/2026'] },
|
||||
});
|
||||
expect(r.warnings[0].message).toBe(
|
||||
'Verschiedene Abrechnungsmonate erkannt: 03/2026, 04/2026. Alle Zeilen sollten im selben Abrechnungsmonat liegen.',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user