1f85277a3b
- 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>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
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<KantineSettingsResponse> {
|
|
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<KantineSettingsResponse> {
|
|
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<KantineDatevSettings | null> {
|
|
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<KantinePreview> {
|
|
return processKantineCsv(buffer, await this.loadConfigured(tenantId));
|
|
}
|
|
|
|
async export(tenantId: string, buffer: Buffer): Promise<FileResponse> {
|
|
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;
|
|
}
|
|
}
|
|
}
|