import { BadRequestException, Injectable, UnprocessableEntityException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { forTenant } from '../prisma/prisma-tenant.extension'; import type { KantineDatevSettingsDto } from './dto/kantine-datev-settings.dto'; import { buildKantineExport, KantineExportError, processKantineCsv, } from './kantine-datev.pipeline'; import type { FileResponse, KantineDatevSettings, KantinePreview } from './kantine-datev.types'; export interface KantineSettingsResponse { beraterNr: string | null; mandantNr: string | null; lohnart: string | null; configured: boolean; } /** * Kantinenabrechnung (quick-261002-fm5). Die Einstellungen liegen je Mandant * in `KantineDatevConfig` (mandantengebunden); die hochgeladene CSV wird nur * im Arbeitsspeicher verarbeitet und weder gespeichert noch protokolliert. */ @Injectable() export class KantineDatevService { constructor(private readonly prisma: PrismaService) {} async getSettings(tenantId: string): Promise { const tenantPrisma = forTenant(this.prisma, tenantId); const row = await tenantPrisma.kantineDatevConfig.findUnique({ where: { tenantId } }); const beraterNr = row?.beraterNr ?? null; const mandantNr = row?.mandantNr ?? null; const lohnart = row?.lohnart ?? null; return { beraterNr, mandantNr, lohnart, configured: Boolean(beraterNr && mandantNr && lohnart), }; } async saveSettings( tenantId: string, dto: KantineDatevSettingsDto, ): Promise { const tenantPrisma = forTenant(this.prisma, tenantId); const data = { beraterNr: dto.beraterNr, mandantNr: dto.mandantNr, lohnart: dto.lohnart }; await tenantPrisma.kantineDatevConfig.upsert({ where: { tenantId }, create: { tenantId, ...data }, update: data, }); return { ...data, configured: true }; } private async loadConfigured(tenantId: string): Promise { const s = await this.getSettings(tenantId); if (!s.configured || !s.beraterNr || !s.mandantNr || !s.lohnart) return null; return { beraterNr: s.beraterNr, mandantNr: s.mandantNr, lohnart: s.lohnart }; } async preview(tenantId: string, buffer: Buffer): Promise { return processKantineCsv(buffer, await this.loadConfigured(tenantId)); } async export(tenantId: string, buffer: Buffer): Promise { const settings = await this.loadConfigured(tenantId); try { return buildKantineExport(buffer, settings); } catch (error) { if (error instanceof KantineExportError) { if (error.code === 'qualityCheckFailed') { throw new UnprocessableEntityException({ code: error.code, message: error.message }); } throw new BadRequestException({ code: error.code, message: error.message, errors: error.errors, }); } throw error; } } }