From 0078654465a77e52744c5b8940d62518c7caf3e5 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 12 Aug 2026 09:26:35 +0200 Subject: [PATCH] Gutschrift: Beleg-Upload auch fuer Sachwerte + kein Unterschriftsblock bei Geld - Beleg-Upload jetzt fuer beide Arten: bei Geld die Ueberweisungs- bestaetigung, bei Sachwert das unterschriebene Dokument. ReceiptControls in der Liste fuer Geld UND Sachwert (Label je nach Typ). Endpoint war schon typ-agnostisch. - PDF-Unterschriftsblock nur noch bei Sachwert - eine Ueberweisung wird nicht unterschrieben (Beleg = hochgeladene Ueberweisungsbestaetigung). Bei Geld entfaellt der Unterschrift/Ort-Block; im Formular sind Ort + 'Unterschrift am' bei Geld ausgeblendet. Verifiziert: beide PDFs erzeugen sauber (je 1 Seite). Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/services/creditNotePdf.service.ts | 25 +++++--- docs/todo.md | 9 +++ .../contracts/CreditNotesSection.tsx | 64 +++++++++++-------- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/backend/src/services/creditNotePdf.service.ts b/backend/src/services/creditNotePdf.service.ts index 82041efa..234340d8 100644 --- a/backend/src/services/creditNotePdf.service.ts +++ b/backend/src/services/creditNotePdf.service.ts @@ -158,16 +158,21 @@ export async function generateCreditNotePdf(creditNoteId: number): Promise<{ buf } // ---- Unterschriftsblock ---- - doc.moveDown(3); - const y = doc.y; - const place = cn.place || company.city || ''; - doc.fontSize(10).fillColor('#000'); - doc.text('_______________________________', 50, y); - doc.text('_______________________________', 320, y); - doc.fontSize(8).fillColor('#666'); - doc.text(`Ort/Datum${place ? ` (${place})` : ''}`, 50, y + 14); - doc.text('Unterschrift', 320, y + 14); - doc.fillColor('#000'); + // Nur bei Sachwert: das Dokument wird vom Kunden unterschrieben. Bei einer + // Geld-Überweisung ist keine Unterschrift nötig – als Beleg dient die + // hochgeladene Überweisungsbestätigung. + if (cn.type === 'SACHWERT') { + doc.moveDown(3); + const y = doc.y; + const place = cn.place || company.city || ''; + doc.fontSize(10).fillColor('#000'); + doc.text('_______________________________', 50, y); + doc.text('_______________________________', 320, y); + doc.fontSize(8).fillColor('#666'); + doc.text(`Ort/Datum${place ? ` (${place})` : ''}`, 50, y + 14); + doc.text('Unterschrift', 320, y + 14); + doc.fillColor('#000'); + } // ---- Fußzeile (Firmendaten) ---- // Position aus der Seitengeometrie berechnen, damit die Fußzeile am diff --git a/docs/todo.md b/docs/todo.md index ce04fd98..710ce537 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,15 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🧾 Gutschrift: Beleg-Upload auch für Sachwerte + kein Unterschriftsblock bei Geld** (2026-08-12) + - **Beleg-Upload jetzt für beide Arten:** bei Geld die Überweisungsbestätigung, + bei Sachwert das **unterschriebene Dokument**. `ReceiptControls` in der Liste + für Geld UND Sachwert (Label „Überweisungsbeleg" bzw. „Unterschr. Dokument"). + Endpoint `/upload/credit-notes/:id/receipt` war schon typ-agnostisch. + - **PDF-Unterschriftsblock nur noch bei Sachwert:** eine Geld-Überweisung wird + nicht unterschrieben (Beleg = hochgeladene Überweisungsbestätigung). Bei Geld + entfällt der Unterschrift/Ort-Block im PDF; im Formular sind „Ort" + + „Unterschrift am" bei Geld ausgeblendet. Verifiziert (beide PDFs 1 Seite). - [x] **📦 Lieferschein-Nummernkreis für betragslose Sachwerte (GoBD)** (2026-08-12) - Pentester-Hinweis R142: Beim Übergang Geld→betragsloser Sachwert wurde die schon vergebene Gutschriftsnummer auf null gesetzt → **Lücke** in der GS-Serie. diff --git a/frontend/src/components/contracts/CreditNotesSection.tsx b/frontend/src/components/contracts/CreditNotesSection.tsx index 2a6a1277..105a7d56 100644 --- a/frontend/src/components/contracts/CreditNotesSection.tsx +++ b/frontend/src/components/contracts/CreditNotesSection.tsx @@ -280,22 +280,25 @@ function CreditNoteFormModal({ value={form.creditDate} onChange={(e) => set('creditDate', e.target.value)} /> - set('place', e.target.value)} - placeholder="z.B. Oldenburg" - /> + {/* Ort/Unterschrift nur bei Sachwert – Überweisung wird nicht unterschrieben. */} + {isSachwert && ( + set('place', e.target.value)} + placeholder="z.B. Oldenburg" + /> + )} -
- set('signedAt', e.target.value)} - /> - {form.type === 'SACHWERT' && ( + {isSachwert && ( +
+ set('signedAt', e.target.value)} + /> - )} -
+
+ )}
@@ -318,9 +321,11 @@ function CreditNoteFormModal({ />
- {form.type === 'GELD' && !editing && ( + {!editing && (

- Der Überweisungsbeleg kann nach dem Speichern in der Liste hochgeladen werden. + {isSachwert + ? 'Das unterschriebene Dokument kann nach dem Speichern in der Liste hochgeladen werden.' + : 'Der Überweisungsbeleg kann nach dem Speichern in der Liste hochgeladen werden.'}

)} @@ -336,7 +341,7 @@ function CreditNoteFormModal({ } // ---------- Beleg-Upload (nur GELD) ---------- -function ReceiptControls({ cn, canEdit, onChanged }: { cn: CreditNote; canEdit: boolean; onChanged: () => void }) { +function ReceiptControls({ cn, canEdit, onChanged, label = 'Beleg' }: { cn: CreditNote; canEdit: boolean; onChanged: () => void; label?: string }) { const fileRef = useRef(null); const uploadMutation = useMutation({ mutationFn: (file: File) => creditNoteApi.uploadReceipt(cn.id, file), @@ -353,17 +358,17 @@ function ReceiptControls({ cn, canEdit, onChanged }: { cn: CreditNote; canEdit: return ( - Beleg + {label} {canEdit && ( - )} ); } - if (!canEdit) return kein Beleg; + if (!canEdit) return kein {label}; return ( <> - {uploadMutation.isPending ? 'lädt …' : 'Beleg'} + {uploadMutation.isPending ? 'lädt …' : label} ); @@ -497,11 +502,14 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
an Bankkonto: {card.iban} ({card.accountHolder})
) : null; })()} - {cn.type === 'GELD' && ( -
- -
- )} +
+ +
{canEdit && (