Gutschrift: betragsloser Sachwert bekommt keine Gutschriftsnummer
Ein betragsloser Sachwert ist eher ein Lieferschein als eine Gutschrift -> er soll KEINE Gutschriftsnummer aus dem Nummernkreis verbrauchen. - Schema: CreditNote.number nullbar (Migration MODIFY ... NULL, UNIQUE bleibt - MySQL erlaubt mehrere NULLs). - createCreditNote: betragsloser Sachwert -> number=null, assignNextNumber wird NICHT aufgerufen (Zaehler unangetastet). - updateCreditNote: Uebergaenge - wird betragslos -> Nummer entfernen; bekommt nachtraeglich Betrag & hatte keine -> jetzt Nummer vergeben. - PDF/Liste/Modal/Audit: Fallback 'Sachwert-Uebergabe'/'Beleg #id' wenn keine Nummer; PDF-Titel 'Sachwert-Uebergabe', kein ZUGFeRD (schon vorher). Verifiziert: Sachwert 0 -> number null + Zaehler bleibt; Geld -> Nummer + Zaehler +1; Sachwert nachtraeglich mit Betrag -> Nummer vergeben. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,12 @@ export function computeAmounts(params: {
|
||||
return { amountNet: net, amountVat: round2(gross - net), amountGross: gross };
|
||||
}
|
||||
|
||||
// Betragsloser Sachwert = reine Übergabe/Lieferschein (keine Rechnung,
|
||||
// keine USt, keine Gutschriftsnummer, kein ZUGFeRD).
|
||||
function isNonMonetary(n: { type: CreditNoteType; amountGross: number }): boolean {
|
||||
return n.type === 'SACHWERT' && n.amountGross === 0;
|
||||
}
|
||||
|
||||
const ALLOWED_TYPES = new Set(['GELD', 'SACHWERT']);
|
||||
const ALLOWED_CUSTOMER_TYPES = new Set(['PRIVAT', 'FIRMA']);
|
||||
const ALLOWED_BASIS = new Set(['NETTO', 'BRUTTO']);
|
||||
@@ -217,7 +223,9 @@ export async function createCreditNote(
|
||||
if (normalized.payoutBankCardId) {
|
||||
await assertBankCardBelongsToContract(contractId, normalized.payoutBankCardId);
|
||||
}
|
||||
const number = await assignNextNumber();
|
||||
// Betragsloser Sachwert = Lieferschein → KEINE Gutschriftsnummer vergeben
|
||||
// (Nummernkreis-Zähler bleibt unangetastet). Sonst nächste Nummer ziehen.
|
||||
const number = isNonMonetary(normalized) ? null : await assignNextNumber();
|
||||
|
||||
return prisma.creditNote.create({
|
||||
data: {
|
||||
@@ -238,11 +246,22 @@ export async function updateCreditNote(id: number, input: CreateCreditNoteInput)
|
||||
if (normalized.payoutBankCardId) {
|
||||
await assertBankCardBelongsToContract(existing.contractId, normalized.payoutBankCardId);
|
||||
}
|
||||
// Nummer bleibt unverändert (einmal vergeben = fix). Ein evtl. schon
|
||||
// erzeugtes PDF ist nach inhaltlicher Änderung veraltet → Pfad leeren
|
||||
// (der User erzeugt es bei Bedarf neu). Reihenfolge (R140): erst DB-Update,
|
||||
// DANN die alte Datei löschen – schlägt das Update fehl, bleibt die Datei.
|
||||
const updated = await prisma.creditNote.update({ where: { id }, data: { ...normalized, pdfPath: null } });
|
||||
|
||||
// Nummer an den (neuen) monetären Status anpassen:
|
||||
// - wird betragsloser Sachwert (Lieferschein) → Nummer entfernen,
|
||||
// - wird eine echte Gutschrift und hatte noch keine Nummer → jetzt vergeben,
|
||||
// - sonst bestehende Nummer beibehalten (einmal vergeben = fix).
|
||||
let number = existing.number;
|
||||
if (isNonMonetary(normalized)) {
|
||||
number = null;
|
||||
} else if (number === null) {
|
||||
number = await assignNextNumber();
|
||||
}
|
||||
|
||||
// Ein evtl. schon erzeugtes PDF ist nach inhaltlicher Änderung veraltet →
|
||||
// Pfad leeren. Reihenfolge (R140): erst DB-Update, DANN die alte Datei
|
||||
// löschen – schlägt das Update fehl, bleibt die Datei.
|
||||
const updated = await prisma.creditNote.update({ where: { id }, data: { ...normalized, number, pdfPath: null } });
|
||||
deleteUploadByRelativePath(existing.pdfPath);
|
||||
return updated;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user