Files
tessera-ctl/apps/api/src/kantine-datev/kantine-datev.service.spec.ts
T
schalli 1f85277a3b 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>
2026-10-02 11:32:55 +02:00

84 lines
3.3 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { describe, expect, it, vi } from 'vitest';
vi.mock('../prisma/prisma-tenant.extension', () => ({
forTenant: vi.fn((p: unknown) => p),
}));
import { forTenant } from '../prisma/prisma-tenant.extension';
import { KantineDatevService } from './kantine-datev.service';
const HEADER = 'PersNr;Name;Menge;EK;Netto;ZuAb;MwSt;Zuschuss;Betrag;Von;Bis';
const GOOD = Buffer.from(
[HEADER, '100;Max;1;1,00;1,00;0;0;0;7,94;01.03.2026;31.03.2026'].join('\r\n'),
);
function setup(row: Record<string, string | null> | null = null) {
const kantineDatevConfig = {
findUnique: vi.fn(async () => row),
upsert: vi.fn(async ({ create }: any) => create),
};
return { prisma: { kantineDatevConfig }, kantineDatevConfig };
}
describe('KantineDatevService — Einstellungen', () => {
it('liefert ohne Zeile leere Werte und configured=false', async () => {
const { prisma } = setup(null);
const res = await new KantineDatevService(prisma as any).getSettings('t1');
expect(res).toEqual({ beraterNr: null, mandantNr: null, lohnart: null, configured: false });
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
});
it('configured=false, solange ein Feld fehlt', async () => {
const { prisma } = setup({ beraterNr: '1', mandantNr: null, lohnart: '3' });
expect((await new KantineDatevService(prisma as any).getSettings('t1')).configured).toBe(false);
});
it('speichert per upsert auf tenantId (aus dem Argument)', async () => {
const { prisma, kantineDatevConfig } = setup();
const res = await new KantineDatevService(prisma as any).saveSettings('t1', {
beraterNr: '1234567',
mandantNr: '12345',
lohnart: '1111',
});
expect(kantineDatevConfig.upsert).toHaveBeenCalledWith(
expect.objectContaining({ where: { tenantId: 't1' } }),
);
expect(res.configured).toBe(true);
});
});
describe('KantineDatevService — Vorschau und Export', () => {
it('Vorschau ohne Einstellungen sperrt mit settingsMissing', async () => {
const { prisma } = setup(null);
const p = await new KantineDatevService(prisma as any).preview('t1', GOOD);
expect(p.blockedReason).toBe('settingsMissing');
expect(p.rowCount).toBe(1);
});
it('Export ohne Einstellungen -> 400 mit code settingsMissing', async () => {
const { prisma } = setup(null);
const err: any = await new KantineDatevService(prisma as any)
.export('t1', GOOD)
.catch((e) => e);
expect(err).toBeInstanceOf(BadRequestException);
expect(err.getResponse().code).toBe('settingsMissing');
});
it('Export mit Fehlern -> 400 mit code hasErrors und Fehlerliste', async () => {
const { prisma } = setup({ beraterNr: '1', mandantNr: '2', lohnart: '3' });
const bad = Buffer.from(
[HEADER, 'x;Max;1;1,00;1,00;0;0;0;7,94;01.03.2026;31.03.2026'].join('\n'),
);
const err: any = await new KantineDatevService(prisma as any).export('t1', bad).catch((e) => e);
expect(err.getResponse().code).toBe('hasErrors');
expect(err.getResponse().errors).toHaveLength(1);
});
it('Export mit Einstellungen liefert Datei', async () => {
const { prisma } = setup({ beraterNr: '1234567', mandantNr: '12345', lohnart: '1111' });
const res = await new KantineDatevService(prisma as any).export('t1', GOOD);
expect(res.filename).toBe('LuG_1234567_12345_03_2026.sic');
});
});