Gutschriften Phase 3b/1: Gutschrift-PDF (pdfkit)
PDF-Erzeugung fuer Gutschriften mit Firmenstammdaten als Absender, Empfaenger (Kunde+Adresse), Netto/USt/Brutto (bzw. ohne USt-Ausweis), Sachwert/'Ware erhalten' bzw. Ueberweisungs-Bankverbindung, Unterschriftsblock, Firmen-Fusszeile. Endpoint POST /credit-notes/:id/pdf; 'PDF'-Button in CreditNotesSection (erzeugen + im Tab oeffnen bzw. vorhandenes ansehen). PDF-Pfad wird bei inhaltlicher Aenderung geleert -> Neu-Erzeugung. Download ueber fileDownload (credit-notes subDir, Vertrags-Ownership). Lokal verifiziert (valides %PDF, korrekte Betraege). ZUGFeRD-XML-Embedding (PDF/A-3, EN 16931) folgt als Teil 2 mit Validator-Pruefung. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Plus, Edit, Trash2, Gift, Banknote, Package, Upload, Eye, X, Check } from 'lucide-react';
|
||||
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';
|
||||
@@ -358,6 +358,16 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
|
||||
onError: (err: Error) => toast.error(err.message || 'Löschen fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const pdfMutation = useMutation({
|
||||
mutationFn: (id: number) => creditNoteApi.generatePdf(id),
|
||||
onSuccess: (res) => {
|
||||
toast.success('PDF erzeugt');
|
||||
refresh();
|
||||
if (res.data?.pdfPath) window.open(fileUrl(res.data.pdfPath, { inline: true }), '_blank', 'noopener');
|
||||
},
|
||||
onError: (err: Error) => toast.error(err.message || 'PDF fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const openCreate = () => { setEditing(null); setShowForm(true); };
|
||||
const openEdit = (cn: CreditNote) => { setEditing(cn); setShowForm(true); };
|
||||
|
||||
@@ -429,6 +439,15 @@ export default function CreditNotesSection({ contractId, canEdit }: { contractId
|
||||
</div>
|
||||
{canEdit && (
|
||||
<div className="flex items-center gap-1">
|
||||
{cn.pdfPath ? (
|
||||
<a href={fileUrl(cn.pdfPath, { inline: true })} target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-blue-600 p-1" title="PDF ansehen">
|
||||
<FileText className="w-4 h-4" />
|
||||
</a>
|
||||
) : (
|
||||
<button onClick={() => pdfMutation.mutate(cn.id)} disabled={pdfMutation.isPending} className="text-gray-400 hover:text-blue-600 p-1" title="PDF erzeugen">
|
||||
<FileText className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => openEdit(cn)} className="text-gray-400 hover:text-blue-600 p-1" title="Bearbeiten">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
@@ -301,6 +301,10 @@ export const creditNoteApi = {
|
||||
const res = await api.delete<ApiResponse<void>>(`/credit-notes/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
generatePdf: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{ pdfPath: string }>>(`/credit-notes/${id}/pdf`, {});
|
||||
return res.data;
|
||||
},
|
||||
getNumberRange: async () => {
|
||||
const res = await api.get<ApiResponse<CreditNoteNumberRange>>(`/credit-notes/number-range`);
|
||||
return res.data;
|
||||
|
||||
Reference in New Issue
Block a user