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) <noreply@anthropic.com>
This commit is contained in:
@@ -158,16 +158,21 @@ export async function generateCreditNotePdf(creditNoteId: number): Promise<{ buf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Unterschriftsblock ----
|
// ---- Unterschriftsblock ----
|
||||||
doc.moveDown(3);
|
// Nur bei Sachwert: das Dokument wird vom Kunden unterschrieben. Bei einer
|
||||||
const y = doc.y;
|
// Geld-Überweisung ist keine Unterschrift nötig – als Beleg dient die
|
||||||
const place = cn.place || company.city || '';
|
// hochgeladene Überweisungsbestätigung.
|
||||||
doc.fontSize(10).fillColor('#000');
|
if (cn.type === 'SACHWERT') {
|
||||||
doc.text('_______________________________', 50, y);
|
doc.moveDown(3);
|
||||||
doc.text('_______________________________', 320, y);
|
const y = doc.y;
|
||||||
doc.fontSize(8).fillColor('#666');
|
const place = cn.place || company.city || '';
|
||||||
doc.text(`Ort/Datum${place ? ` (${place})` : ''}`, 50, y + 14);
|
doc.fontSize(10).fillColor('#000');
|
||||||
doc.text('Unterschrift', 320, y + 14);
|
doc.text('_______________________________', 50, y);
|
||||||
doc.fillColor('#000');
|
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) ----
|
// ---- Fußzeile (Firmendaten) ----
|
||||||
// Position aus der Seitengeometrie berechnen, damit die Fußzeile am
|
// Position aus der Seitengeometrie berechnen, damit die Fußzeile am
|
||||||
|
|||||||
@@ -97,6 +97,15 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ 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)
|
- [x] **📦 Lieferschein-Nummernkreis für betragslose Sachwerte (GoBD)** (2026-08-12)
|
||||||
- Pentester-Hinweis R142: Beim Übergang Geld→betragsloser Sachwert wurde die
|
- Pentester-Hinweis R142: Beim Übergang Geld→betragsloser Sachwert wurde die
|
||||||
schon vergebene Gutschriftsnummer auf null gesetzt → **Lücke** in der GS-Serie.
|
schon vergebene Gutschriftsnummer auf null gesetzt → **Lücke** in der GS-Serie.
|
||||||
|
|||||||
@@ -280,22 +280,25 @@ function CreditNoteFormModal({
|
|||||||
value={form.creditDate}
|
value={form.creditDate}
|
||||||
onChange={(e) => set('creditDate', e.target.value)}
|
onChange={(e) => set('creditDate', e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Input
|
{/* Ort/Unterschrift nur bei Sachwert – Überweisung wird nicht unterschrieben. */}
|
||||||
label="Ort (Unterschrift)"
|
{isSachwert && (
|
||||||
value={form.place}
|
<Input
|
||||||
onChange={(e) => set('place', e.target.value)}
|
label="Ort (Unterschrift)"
|
||||||
placeholder="z.B. Oldenburg"
|
value={form.place}
|
||||||
/>
|
onChange={(e) => set('place', e.target.value)}
|
||||||
|
placeholder="z.B. Oldenburg"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
{isSachwert && (
|
||||||
<Input
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
label="Unterschrift am (optional)"
|
<Input
|
||||||
type="date"
|
label="Unterschrift am (optional)"
|
||||||
value={form.signedAt}
|
type="date"
|
||||||
onChange={(e) => set('signedAt', e.target.value)}
|
value={form.signedAt}
|
||||||
/>
|
onChange={(e) => set('signedAt', e.target.value)}
|
||||||
{form.type === 'SACHWERT' && (
|
/>
|
||||||
<label className="flex items-center gap-2 text-sm mt-7">
|
<label className="flex items-center gap-2 text-sm mt-7">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -305,8 +308,8 @@ function CreditNoteFormModal({
|
|||||||
/>
|
/>
|
||||||
Ware erhalten (Empfang bestätigt)
|
Ware erhalten (Empfang bestätigt)
|
||||||
</label>
|
</label>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Notiz (optional)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Notiz (optional)</label>
|
||||||
@@ -318,9 +321,11 @@ function CreditNoteFormModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{form.type === 'GELD' && !editing && (
|
{!editing && (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
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.'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -336,7 +341,7 @@ function CreditNoteFormModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Beleg-Upload (nur GELD) ----------
|
// ---------- 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<HTMLInputElement>(null);
|
const fileRef = useRef<HTMLInputElement>(null);
|
||||||
const uploadMutation = useMutation({
|
const uploadMutation = useMutation({
|
||||||
mutationFn: (file: File) => creditNoteApi.uploadReceipt(cn.id, file),
|
mutationFn: (file: File) => creditNoteApi.uploadReceipt(cn.id, file),
|
||||||
@@ -353,17 +358,17 @@ function ReceiptControls({ cn, canEdit, onChanged }: { cn: CreditNote; canEdit:
|
|||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-2 text-xs">
|
<span className="inline-flex items-center gap-2 text-xs">
|
||||||
<a href={fileUrl(cn.receiptPath, { inline: true })} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline inline-flex items-center gap-1">
|
<a href={fileUrl(cn.receiptPath, { inline: true })} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline inline-flex items-center gap-1">
|
||||||
<Eye className="w-3.5 h-3.5" /> Beleg
|
<Eye className="w-3.5 h-3.5" /> {label}
|
||||||
</a>
|
</a>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<button onClick={() => removeMutation.mutate()} className="text-gray-400 hover:text-red-600" title="Beleg entfernen">
|
<button onClick={() => removeMutation.mutate()} className="text-gray-400 hover:text-red-600" title={`${label} entfernen`}>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!canEdit) return <span className="text-xs text-gray-400">kein Beleg</span>;
|
if (!canEdit) return <span className="text-xs text-gray-400">kein {label}</span>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
@@ -378,7 +383,7 @@ function ReceiptControls({ cn, canEdit, onChanged }: { cn: CreditNote; canEdit:
|
|||||||
disabled={uploadMutation.isPending}
|
disabled={uploadMutation.isPending}
|
||||||
className="inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800"
|
className="inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800"
|
||||||
>
|
>
|
||||||
<Upload className="w-3.5 h-3.5" /> {uploadMutation.isPending ? 'lädt …' : 'Beleg'}
|
<Upload className="w-3.5 h-3.5" /> {uploadMutation.isPending ? 'lädt …' : label}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -497,11 +502,14 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
|
|||||||
<div className="text-xs text-gray-500 mt-0.5">an Bankkonto: <span className="font-mono">{card.iban}</span> ({card.accountHolder})</div>
|
<div className="text-xs text-gray-500 mt-0.5">an Bankkonto: <span className="font-mono">{card.iban}</span> ({card.accountHolder})</div>
|
||||||
) : null;
|
) : null;
|
||||||
})()}
|
})()}
|
||||||
{cn.type === 'GELD' && (
|
<div className="mt-1">
|
||||||
<div className="mt-1">
|
<ReceiptControls
|
||||||
<ReceiptControls cn={cn} canEdit={canEdit} onChanged={refresh} />
|
cn={cn}
|
||||||
</div>
|
canEdit={canEdit}
|
||||||
)}
|
onChanged={refresh}
|
||||||
|
label={cn.type === 'GELD' ? 'Überweisungsbeleg' : 'Unterschr. Dokument'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user