Gutschrift: Sachwert ohne Betrag = keine Rechnung (kein ZUGFeRD)

Eine Sachwert-Gutschrift darf betragslos sein (Betrag leer/0): dann
findet keine Rechnungsstellung statt - der Kunde hat den Gegenstand
einfach als Subvention erhalten.

- Backend: leeres amount -> 0; Sachwert erlaubt 0, Geld verlangt > 0
  (400 sonst). vatRelevant bei 0 erzwungen-false.
- PDF: betragsloser Sachwert -> Titel 'Sachwert-Uebergabe', kein
  Betrags-/USt-Block (Hinweis keine Rechnungsstellung), KEIN ZUGFeRD-
  Embedding. Mit Betrag -> unveraendert ZUGFeRD.
- Frontend: Wert-Feld bei Sachwert optional; USt-Block ausgeblendet ohne
  Betrag; Liste zeigt 'Sachwert ohne Betrag (keine Rechnung)'.

Verifiziert: Sachwert 0 -> kein factur-x.xml; Geld 0 -> 400;
Sachwert 200 -> ZUGFeRD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-09 13:22:13 +02:00
co-authored by Claude Opus 4.8
parent 4d14390b2a
commit 6f95005530
4 changed files with 131 additions and 73 deletions
@@ -142,14 +142,20 @@ function CreditNoteFormModal({
onError: (err: Error) => toast.error(err.message || 'Speichern fehlgeschlagen'),
});
const amountLabel = form.vatRelevant
? `Betrag (${form.amountBasis === 'NETTO' ? 'netto' : 'brutto'})`
: 'Betrag';
const amountNum = Number(form.amount) || 0;
// Sachwert darf betragslos sein (reine Übergabe → keine Rechnung).
const isSachwert = form.type === 'SACHWERT';
const nonMonetary = isSachwert && amountNum <= 0;
const canSave =
!!form.amount &&
Number(form.amount) >= 0 &&
(form.type !== 'SACHWERT' || form.sachwertDescription.trim().length > 0);
const amountLabel = isSachwert
? 'Wert (optional)'
: form.vatRelevant
? `Betrag (${form.amountBasis === 'NETTO' ? 'netto' : 'brutto'})`
: 'Betrag';
const canSave = isSachwert
? form.sachwertDescription.trim().length > 0 // Betrag optional
: amountNum > 0; // Geld: Betrag > 0 Pflicht
return (
<Modal isOpen onClose={onClose} title={editing ? `Gutschrift ${editing.number} bearbeiten` : 'Gutschrift anlegen'} size="lg">
@@ -184,18 +190,8 @@ function CreditNoteFormModal({
/>
)}
{/* USt-Block */}
{/* Betrag + USt-Block */}
<div className="rounded-lg border border-gray-200 p-3 space-y-3 bg-gray-50">
<label className="flex items-center gap-2 text-sm font-medium">
<input
type="checkbox"
checked={form.vatRelevant}
onChange={(e) => set('vatRelevant', e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
USt-relevant (mit Umsatzsteuer ausweisen)
</label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Input
label={amountLabel}
@@ -205,7 +201,7 @@ function CreditNoteFormModal({
onChange={(e) => set('amount', e.target.value)}
placeholder="0,00"
/>
{form.vatRelevant && (
{amountNum > 0 && form.vatRelevant && (
<>
<Select
label="Basis"
@@ -227,11 +223,28 @@ function CreditNoteFormModal({
)}
</div>
<div className="flex flex-wrap gap-x-6 gap-y-1 text-sm">
<span>Netto: <strong>{formatEuro(preview.net)}</strong></span>
<span>USt: <strong>{formatEuro(preview.vat)}</strong></span>
<span>Brutto: <strong>{formatEuro(preview.gross)}</strong></span>
</div>
{nonMonetary ? (
<p className="text-sm text-gray-600">
Ohne Betrag: reine Sachwert-Übergabe <strong>keine Rechnung, keine USt</strong>. Es wird nur der Empfang des Gegenstands als Subvention dokumentiert.
</p>
) : (
<>
<label className="flex items-center gap-2 text-sm font-medium">
<input
type="checkbox"
checked={form.vatRelevant}
onChange={(e) => set('vatRelevant', e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
USt-relevant (mit Umsatzsteuer ausweisen)
</label>
<div className="flex flex-wrap gap-x-6 gap-y-1 text-sm">
<span>Netto: <strong>{formatEuro(preview.net)}</strong></span>
<span>USt: <strong>{formatEuro(preview.vat)}</strong></span>
<span>Brutto: <strong>{formatEuro(preview.gross)}</strong></span>
</div>
</>
)}
</div>
{form.type === 'GELD' && (
@@ -439,10 +452,12 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
<Badge variant={cn.type === 'GELD' ? 'info' : 'default'}>
{cn.type === 'GELD' ? 'Geld' : 'Sachwert'}
</Badge>
{cn.vatRelevant ? (
<Badge variant="default">USt {cn.vatRate}%</Badge>
) : (
<Badge variant="default">ohne USt</Badge>
{!(cn.type === 'SACHWERT' && cn.amountGross === 0) && (
cn.vatRelevant ? (
<Badge variant="default">USt {cn.vatRate}%</Badge>
) : (
<Badge variant="default">ohne USt</Badge>
)
)}
<Badge variant="default">{cn.customerType === 'FIRMA' ? 'Firma' : 'Privat'}</Badge>
{cn.type === 'SACHWERT' && cn.goodsReceived && (
@@ -452,9 +467,15 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
)}
</div>
<div className="text-sm text-gray-700 mt-1">
<strong>{formatEuro(cn.amountGross, cn.currency)}</strong>
{cn.vatRelevant && (
<span className="text-gray-500"> (netto {formatEuro(cn.amountNet, cn.currency)} + USt {formatEuro(cn.amountVat, cn.currency)})</span>
{cn.type === 'SACHWERT' && cn.amountGross === 0 ? (
<span className="italic text-gray-600">Sachwert ohne Betrag (keine Rechnung)</span>
) : (
<>
<strong>{formatEuro(cn.amountGross, cn.currency)}</strong>
{cn.vatRelevant && (
<span className="text-gray-500"> (netto {formatEuro(cn.amountNet, cn.currency)} + USt {formatEuro(cn.amountVat, cn.currency)})</span>
)}
</>
)}
<span className="text-gray-500"> · {formatDate(cn.creditDate)}</span>
{cn.place && <span className="text-gray-500"> · {cn.place}</span>}