import { useState, useRef } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Edit, Trash2, Gift, Banknote, Package, Upload, Eye, X, Check, FileText } from 'lucide-react'; import toast from 'react-hot-toast'; import { formatDate } from '../../utils/dateFormat'; import { fileUrl } from '../../utils/fileUrl'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; import Input from '../ui/Input'; import Select from '../ui/Select'; import Badge from '../ui/Badge'; import { creditNoteApi } from '../../services/api'; import type { CreditNote, CreditNoteType, CreditNoteCustomerType, CreditNoteAmountBasis, CreditNotePayoutBankCard } from '../../types'; const round2 = (n: number) => Math.round((n + Number.EPSILON) * 100) / 100; function formatEuro(n: number, currency = 'EUR') { return new Intl.NumberFormat('de-DE', { style: 'currency', currency }).format(n || 0); } // Client-Vorschau der Beträge (der Server rechnet autoritativ nach). function calcAmounts(amount: number, vatRelevant: boolean, basis: CreditNoteAmountBasis, rate: number) { const a = round2(amount || 0); if (!vatRelevant || rate <= 0) return { net: a, vat: 0, gross: a }; const r = rate / 100; if (basis === 'NETTO') { const net = a; const vat = round2(net * r); return { net, vat, gross: round2(net + vat) }; } const gross = a; const net = round2(gross / (1 + r)); return { net, vat: round2(gross - net), gross }; } 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; bankCards: CreditNotePayoutBankCard[]; contractBankCardId: number | null; } interface FormState { type: CreditNoteType; sachwertDescription: string; customerType: CreditNoteCustomerType; vatRelevant: boolean; amountBasis: CreditNoteAmountBasis; vatRate: string; amount: string; creditDate: string; place: string; signedAt: string; goodsReceived: boolean; payoutBankCardId: string; notes: string; } function emptyForm(defaults?: FormDefaults): FormState { return { type: 'GELD', sachwertDescription: '', customerType: defaults?.customerType ?? 'PRIVAT', vatRelevant: defaults?.vatRelevant ?? false, amountBasis: 'BRUTTO', vatRate: '19', amount: '', creditDate: todayIso(), place: '', signedAt: '', goodsReceived: false, // Vorschlag: das Abbuchkonto des Vertrags (kann umgestellt werden). payoutBankCardId: defaults?.contractBankCardId ? String(defaults.contractBankCardId) : '', notes: '', }; } function formFromCreditNote(cn: CreditNote): FormState { return { type: cn.type, sachwertDescription: cn.sachwertDescription ?? '', customerType: cn.customerType, vatRelevant: cn.vatRelevant, amountBasis: cn.amountBasis, vatRate: String(cn.vatRate), // Beim Bearbeiten den Betrag auf der ursprünglichen Basis anzeigen. amount: String(cn.amountBasis === 'NETTO' ? cn.amountNet : cn.amountGross), creditDate: cn.creditDate ? cn.creditDate.split('T')[0] : todayIso(), place: cn.place ?? '', signedAt: cn.signedAt ? cn.signedAt.split('T')[0] : '', goodsReceived: cn.goodsReceived, payoutBankCardId: cn.payoutBankCardId ? String(cn.payoutBankCardId) : '', notes: cn.notes ?? '', }; } // ---------- Formular-Modal ---------- function CreditNoteFormModal({ contractId, editing, defaults, onClose, onSaved, }: { contractId: number; editing: CreditNote | null; defaults?: FormDefaults; onClose: () => void; onSaved: () => void; }) { const [form, setForm] = useState(editing ? formFromCreditNote(editing) : emptyForm(defaults)); const bankCards = defaults?.bankCards ?? []; const set = (key: K, value: FormState[K]) => setForm((f) => ({ ...f, [key]: value })); const preview = calcAmounts(parseFloat(form.amount), form.vatRelevant, form.amountBasis, parseFloat(form.vatRate) || 0); const saveMutation = useMutation({ mutationFn: () => { const payload = { type: form.type, sachwertDescription: form.type === 'SACHWERT' ? form.sachwertDescription : null, customerType: form.customerType, vatRelevant: form.vatRelevant, amountBasis: form.amountBasis, vatRate: Number(form.vatRate) || 0, amount: Number(form.amount) || 0, creditDate: form.creditDate, place: form.place || null, signedAt: form.signedAt || null, goodsReceived: form.type === 'SACHWERT' ? form.goodsReceived : false, payoutBankCardId: form.type === 'GELD' && form.payoutBankCardId ? Number(form.payoutBankCardId) : null, notes: form.notes || null, }; return editing ? creditNoteApi.update(editing.id, payload) : creditNoteApi.create(contractId, payload); }, onSuccess: () => { toast.success(editing ? 'Gutschrift gespeichert' : 'Gutschrift angelegt'); onSaved(); }, onError: (err: Error) => toast.error(err.message || 'Speichern fehlgeschlagen'), }); 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 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 (
set('customerType', e.target.value as CreditNoteCustomerType)} options={[ { value: 'PRIVAT', label: 'Privatkunde' }, { value: 'FIRMA', label: 'Firmenkunde' }, ]} />
{form.type === 'SACHWERT' && ( set('sachwertDescription', e.target.value)} placeholder="z.B. Smartphone Samsung Galaxy A55" /> )} {/* Betrag + USt-Block */}
set('amount', e.target.value)} placeholder="0,00" /> {amountNum > 0 && form.vatRelevant && ( <> set('vatRate', e.target.value)} /> )}
{nonMonetary ? (

Ohne Betrag: reine Sachwert-Übergabe – keine Rechnung, keine USt. Es wird nur der Empfang des Gegenstands als Subvention dokumentiert.

) : ( <>
Netto: {formatEuro(preview.net)} USt: {formatEuro(preview.vat)} Brutto: {formatEuro(preview.gross)}
)}
{form.type === 'GELD' && ( bankCards.length > 0 ? ( set('creditDate', e.target.value)} /> {/* Ort/Unterschrift nur bei Sachwert – Überweisung wird nicht unterschrieben. */} {isSachwert && ( set('place', e.target.value)} placeholder="z.B. Oldenburg" /> )}
{isSachwert && (
set('signedAt', e.target.value)} />
)}