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,70 @@
|
||||
/**
|
||||
* Gemeinsame Anfragehilfen der Finanzbuchhaltungs-Module (Kantinenabrechnung,
|
||||
* Handelsware; quick-261002-fm5). `credentials: 'include'` fuer Cookie-Auth,
|
||||
* `NEXT_PUBLIC_API_URL` als Basis — Muster `custom-modules-api.ts`.
|
||||
*/
|
||||
|
||||
export const ACCOUNTING_API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
/** Dateiantwort der API (Base64) — wie beim Zertifikat-Manager. */
|
||||
export interface FileResponse {
|
||||
filename: string;
|
||||
content: string;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fehler mit HTTP-Status, maschinenlesbarer Kennung (`code`, falls die API
|
||||
* eine liefert) und deutscher Servermeldung.
|
||||
*/
|
||||
export class AccountingRequestError extends Error {
|
||||
constructor(
|
||||
readonly status: number,
|
||||
readonly code: string | null,
|
||||
message: string,
|
||||
readonly details: unknown = null,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'AccountingRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
async function failure(res: Response): Promise<AccountingRequestError> {
|
||||
let message = `Request failed (${res.status})`;
|
||||
let code: string | null = null;
|
||||
let details: unknown = null;
|
||||
try {
|
||||
const body = await res.json();
|
||||
const raw = body?.message;
|
||||
if (Array.isArray(raw)) message = raw.join(' ');
|
||||
else if (typeof raw === 'string') message = raw;
|
||||
if (typeof body?.code === 'string') code = body.code;
|
||||
details = body?.errors ?? null;
|
||||
} catch {
|
||||
// Antwort ohne JSON-Koerper — Standardmeldung bleibt.
|
||||
}
|
||||
return new AccountingRequestError(res.status, code, message, details);
|
||||
}
|
||||
|
||||
export async function accountingRequest<T>(
|
||||
path: string,
|
||||
init: { method?: string; json?: unknown; form?: FormData } = {},
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {};
|
||||
let body: BodyInit | undefined;
|
||||
if (init.form) {
|
||||
body = init.form;
|
||||
} else if (init.json !== undefined) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
body = JSON.stringify(init.json);
|
||||
}
|
||||
const res = await fetch(`${ACCOUNTING_API_URL}${path}`, {
|
||||
method: init.method ?? 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
if (!res.ok) throw await failure(res);
|
||||
if (res.status === 204) return undefined as T;
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
Reference in New Issue
Block a user