feat(handelsware-datev): Modulseite mit Import, Konten und Einstellungen
- Reiter Import: Vorschau mit neu-Markierung, Buchungsdatum (TTMM) aus dem Dateinamen, Download speichert neue Konten - Reiter Konten: anlegen, bearbeiten, loeschen mit Rueckfrage, CSV-Import (ersetzt alles, nach Rueckfrage) und -Export - Reiter Einstellungen nur fuer Administratoren; Texte de/en Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Handelsware — API-Client (quick-261002-fm5). Konsumiert
|
||||
* `/modules/handelsware-datev`. Die Excel-Datei geht als Multipart-Feld `file`;
|
||||
* beim Export zusaetzlich `buchungsdatum` und `newAccounts` (JSON-Text der von
|
||||
* der Vorschau gemeldeten neuen Konten) — die API liest alle Zeilen erneut aus
|
||||
* derselben Datei, damit die TXT nicht von der Arbeitsmappe abweichen kann.
|
||||
*/
|
||||
|
||||
import { accountingRequest, type FileResponse } from '@/lib/accounting-request';
|
||||
|
||||
export type { FileResponse } from '@/lib/accounting-request';
|
||||
export { AccountingRequestError as HandelswareRequestError } from '@/lib/accounting-request';
|
||||
|
||||
const BASE = '/modules/handelsware-datev';
|
||||
|
||||
export interface HandelswareSettings {
|
||||
erloeskonto: number | null;
|
||||
startGegenkonto: number | null;
|
||||
configured: boolean;
|
||||
}
|
||||
|
||||
export interface HandelswareSettingsInput {
|
||||
erloeskonto: number;
|
||||
startGegenkonto: number;
|
||||
}
|
||||
|
||||
export interface HandelswareAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
gegenkonto: number;
|
||||
erloeskonto: number;
|
||||
}
|
||||
|
||||
export interface HandelswareAccountInput {
|
||||
name: string;
|
||||
gegenkonto: number;
|
||||
erloeskonto: number;
|
||||
}
|
||||
|
||||
export interface NewAccount {
|
||||
name: string;
|
||||
gegenkonto: number;
|
||||
erloeskonto: number;
|
||||
}
|
||||
|
||||
export interface PreviewRow {
|
||||
line: number;
|
||||
buchungstext: string;
|
||||
umsatz: string;
|
||||
sollHaben: 'S' | 'H';
|
||||
gegenkonto: number;
|
||||
erloeskonto: number;
|
||||
isNew: boolean;
|
||||
}
|
||||
|
||||
export interface RowError {
|
||||
line: number;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface HandelswarePreview {
|
||||
headerText: string;
|
||||
suggestedBuchungsdatum: string;
|
||||
exportFilename: string;
|
||||
rows: PreviewRow[];
|
||||
newAccounts: NewAccount[];
|
||||
rowErrors: RowError[];
|
||||
}
|
||||
|
||||
export type HandelswareExportResult = FileResponse & { createdCount: number };
|
||||
|
||||
const DAYS_PER_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
/** TTMM: vier Ziffern, Monat 1-12, Tag passend zum Monat (Februar bis 29) — wie die API. */
|
||||
export function isValidBuchungsdatum(ttmm: string): boolean {
|
||||
if (!/^\d{4}$/.test(ttmm)) return false;
|
||||
const day = Number.parseInt(ttmm.slice(0, 2), 10);
|
||||
const month = Number.parseInt(ttmm.slice(2, 4), 10);
|
||||
if (month < 1 || month > 12) return false;
|
||||
return day >= 1 && day <= DAYS_PER_MONTH[month - 1];
|
||||
}
|
||||
|
||||
function fileForm(file: File): FormData {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
return form;
|
||||
}
|
||||
|
||||
export function getHandelswareSettings(): Promise<HandelswareSettings> {
|
||||
return accountingRequest(`${BASE}/settings`);
|
||||
}
|
||||
|
||||
export function saveHandelswareSettings(
|
||||
input: HandelswareSettingsInput,
|
||||
): Promise<HandelswareSettings> {
|
||||
return accountingRequest(`${BASE}/settings`, { method: 'PUT', json: input });
|
||||
}
|
||||
|
||||
export function previewHandelsware(file: File): Promise<HandelswarePreview> {
|
||||
return accountingRequest(`${BASE}/preview`, { method: 'POST', form: fileForm(file) });
|
||||
}
|
||||
|
||||
export function exportHandelsware(
|
||||
file: File,
|
||||
buchungsdatum: string,
|
||||
newAccounts: NewAccount[],
|
||||
): Promise<HandelswareExportResult> {
|
||||
const form = fileForm(file);
|
||||
form.append('buchungsdatum', buchungsdatum);
|
||||
form.append('newAccounts', JSON.stringify(newAccounts));
|
||||
return accountingRequest(`${BASE}/export`, { method: 'POST', form });
|
||||
}
|
||||
|
||||
export function listAccounts(): Promise<HandelswareAccount[]> {
|
||||
return accountingRequest(`${BASE}/accounts`);
|
||||
}
|
||||
|
||||
export function createAccount(input: HandelswareAccountInput): Promise<HandelswareAccount> {
|
||||
return accountingRequest(`${BASE}/accounts`, { method: 'POST', json: input });
|
||||
}
|
||||
|
||||
export function updateAccount(
|
||||
id: string,
|
||||
input: HandelswareAccountInput,
|
||||
): Promise<HandelswareAccount> {
|
||||
return accountingRequest(`${BASE}/accounts/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
json: input,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteAccount(id: string): Promise<{ deleted: true }> {
|
||||
return accountingRequest(`${BASE}/accounts/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export function importAccountsCsv(file: File): Promise<{ count: number }> {
|
||||
return accountingRequest(`${BASE}/accounts/import-csv`, { method: 'POST', form: fileForm(file) });
|
||||
}
|
||||
|
||||
export function exportAccountsCsv(): Promise<FileResponse> {
|
||||
return accountingRequest(`${BASE}/accounts/export-csv`);
|
||||
}
|
||||
Reference in New Issue
Block a user