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:
@@ -35,6 +35,13 @@ function calcAmounts(amount: number, vatRelevant: boolean, basis: CreditNoteAmou
|
||||
|
||||
const todayIso = () => new Date().toISOString().split('T')[0];
|
||||
|
||||
// Betragsloser Sachwert = Lieferschein → zeigt die Lieferscheinnummer,
|
||||
// sonst die Gutschriftsnummer.
|
||||
function displayNumber(cn: CreditNote): string {
|
||||
const nonMonetary = cn.type === 'SACHWERT' && cn.amountGross === 0;
|
||||
return (nonMonetary ? cn.deliveryNoteNumber : cn.number) ?? `#${cn.id}`;
|
||||
}
|
||||
|
||||
interface FormDefaults {
|
||||
customerType: CreditNoteCustomerType;
|
||||
vatRelevant: boolean;
|
||||
@@ -158,7 +165,7 @@ function CreditNoteFormModal({
|
||||
: amountNum > 0; // Geld: Betrag > 0 Pflicht
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose} title={editing ? `Gutschrift ${editing.number ?? 'Sachwert-Übergabe'} bearbeiten` : 'Gutschrift anlegen'} size="lg">
|
||||
<Modal isOpen onClose={onClose} title={editing ? `Beleg ${displayNumber(editing)} bearbeiten` : 'Gutschrift anlegen'} size="lg">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Select
|
||||
@@ -448,7 +455,7 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-mono text-sm font-medium">{cn.number ?? 'Sachwert-Übergabe'}</span>
|
||||
<span className="font-mono text-sm font-medium">{displayNumber(cn)}</span>
|
||||
<Badge variant={cn.type === 'GELD' ? 'info' : 'default'}>
|
||||
{cn.type === 'GELD' ? 'Geld' : 'Sachwert'}
|
||||
</Badge>
|
||||
@@ -511,7 +518,7 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { if (confirm(`Gutschrift ${cn.number ?? 'Sachwert-Übergabe'} wirklich löschen?`)) deleteMutation.mutate(cn.id); }}
|
||||
onClick={() => { if (confirm(`Beleg ${displayNumber(cn)} wirklich löschen?`)) deleteMutation.mutate(cn.id); }}
|
||||
className="text-gray-400 hover:text-red-600 p-1"
|
||||
title="Löschen"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user