Gutschrift: separater Lieferschein-Nummernkreis (GoBD, luecken-frei)
Pentester R142: Uebergang Geld->betragsloser Sachwert setzte die schon vergebene Gutschriftsnummer auf null -> Luecke in der GS-Serie. Loesung: betragsloser Sachwert = Lieferschein mit eigener Lieferscheinnummer aus separatem Nummernkreis. - Schema: CreditNote.deliveryNoteNumber (nullbar, unique) + neues Model DeliveryNoteNumberRange (Default-Praefix 'LS-') + Migration. - deliveryNoteNumberRange.service (mirror, eigener Zaehler, FOR UPDATE). - Nummern lazy pro Serie, NIE freigeben: Uebergaenge behalten die jeweils vergebene Nummer der anderen Serie reserviert -> kein Doppelverbrauch, keine Luecke. effectiveNumber() liefert je nach Typ die passende (LS/GS) fuer Anzeige/PDF/Audit. - Endpunkte GET/PUT /credit-notes/delivery-note-number-range; Settings- Seite verwaltet jetzt beide Nummernkreise. PDF-Titel 'Sachwert- Uebergabe', Dateiname lieferschein-... - Frontend: Typ + displayNumber in Liste/Modal. Verifiziert: Sachwert 0 -> LS-Nr, GS-Zaehler unberuehrt; Geld -> GS-Nr; Uebergaenge behalten beide Nummern (kein Neuverbrauch, keine Luecke). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,9 @@ import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
import * as creditNoteService from '../services/creditNote.service.js';
|
||||
import { effectiveNumber } from '../services/creditNote.service.js';
|
||||
import * as numberRangeService from '../services/creditNoteNumberRange.service.js';
|
||||
import * as deliveryRangeService from '../services/deliveryNoteNumberRange.service.js';
|
||||
import { generateCreditNotePdf } from '../services/creditNotePdf.service.js';
|
||||
|
||||
// Gutschriften sind ein reiner Mitarbeiter-/Admin-Bereich (interne
|
||||
@@ -82,7 +84,7 @@ export async function create(req: AuthRequest, res: Response): Promise<void> {
|
||||
action: 'CREATE',
|
||||
resourceType: 'CreditNote',
|
||||
resourceId: created.id.toString(),
|
||||
label: `Gutschrift ${created.number ?? `Sachwert-Uebergabe #${created.id}`} angelegt (${created.type}, ${created.amountGross} ${created.currency}) an Vertrag #${contractId}`,
|
||||
label: `Beleg ${effectiveNumber(created) ?? `#${created.id}`} angelegt (${created.type}, ${created.amountGross} ${created.currency}) an Vertrag #${contractId}`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: created } as ApiResponse);
|
||||
} catch (error) {
|
||||
@@ -117,7 +119,7 @@ export async function update(req: AuthRequest, res: Response): Promise<void> {
|
||||
action: 'UPDATE',
|
||||
resourceType: 'CreditNote',
|
||||
resourceId: updated.id.toString(),
|
||||
label: `Gutschrift ${updated.number ?? `Sachwert-Uebergabe #${updated.id}`} geändert (${updated.type}, ${updated.amountGross} ${updated.currency})`,
|
||||
label: `Beleg ${effectiveNumber(updated) ?? `#${updated.id}`} geändert (${updated.type}, ${updated.amountGross} ${updated.currency})`,
|
||||
});
|
||||
res.json({ success: true, data: updated } as ApiResponse);
|
||||
} catch (error) {
|
||||
@@ -136,7 +138,7 @@ export async function remove(req: AuthRequest, res: Response): Promise<void> {
|
||||
action: 'DELETE',
|
||||
resourceType: 'CreditNote',
|
||||
resourceId: id.toString(),
|
||||
label: `Gutschrift ${deleted.number ?? `Sachwert-Uebergabe #${deleted.id}`} gelöscht`,
|
||||
label: `Beleg ${effectiveNumber(deleted) ?? `#${deleted.id}`} gelöscht`,
|
||||
});
|
||||
res.json({ success: true } as ApiResponse);
|
||||
} catch (error) {
|
||||
@@ -193,3 +195,36 @@ export async function updateNumberRange(req: AuthRequest, res: Response): Promis
|
||||
handleError(res, error, 'Fehler beim Speichern des Nummernkreises');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Lieferschein-Nummernkreis (betragslose Sachwerte) ----
|
||||
|
||||
export async function getDeliveryNoteNumberRange(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (blockPortal(req, res)) return;
|
||||
const [range, nextNumber] = await Promise.all([
|
||||
deliveryRangeService.getOrCreateRange(),
|
||||
deliveryRangeService.previewNextNumber(),
|
||||
]);
|
||||
res.json({ success: true, data: { ...range, preview: nextNumber } } as ApiResponse);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'Fehler beim Laden des Lieferschein-Nummernkreises');
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateDeliveryNoteNumberRange(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (blockPortal(req, res)) return;
|
||||
const range = await deliveryRangeService.updateRange(req.body ?? {});
|
||||
const preview = await deliveryRangeService.previewNextNumber();
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'DeliveryNoteNumberRange',
|
||||
resourceId: range.id.toString(),
|
||||
label: `Lieferschein-Nummernkreis geändert (Präfix "${range.prefix}", nächste Nr. ${range.nextNumber})`,
|
||||
});
|
||||
res.json({ success: true, data: { ...range, preview } } as ApiResponse);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'Fehler beim Speichern des Lieferschein-Nummernkreises');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user