E-Mail als PDF speichern: Tab "Vertragsdokument" ergänzt
Bisher hatte das "E-Mail als PDF speichern"-Modal nur die Tabs
"Als Dokument" + "Als Rechnung" (nur Energieverträge). Wenn die
E-Mail einem Vertrag zugeordnet ist, fehlte die Möglichkeit, sie
direkt als Vertragsdokument (Auftragsformular, Lieferbestätigung
etc.) zu hinterlegen – analog zum Anhang-Modal.
Backend: neuer Endpoint POST /api/emails/:id/save-as-contract-document
{ documentType, notes?, deliveryDate? } – generiert das Mail-PDF,
speichert es unter /uploads/contract-documents und legt einen
ContractDocument-Eintrag an. Bei documentType "Lieferbestätigung"
wird der bestehende maybeActivateOnDeliveryConfirmation-Workflow
getriggert (DRAFT → ACTIVE, startDate-Übernahme).
Frontend: SaveEmailAsPdfModal bekommt den dritten Tab parallel zu
SaveAttachmentModal. Tab erscheint, sobald die E-Mail einem Vertrag
zugeordnet ist (auch bei Nicht-Energieverträgen); Tab "Als Rechnung"
bleibt auf Energieverträge beschränkt. Dokumenttyp-Dropdown und
Notizen-Feld werden aus dem Anhang-Modal übernommen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1814,6 +1814,93 @@ export async function saveEmailAsInvoice(req: AuthRequest, res: Response): Promi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== SAVE EMAIL AS CONTRACT DOCUMENT ====================
|
||||||
|
|
||||||
|
// E-Mail als PDF exportieren und als Vertragsdokument hinterlegen.
|
||||||
|
// Parallel zu saveAttachmentAsContractDocument (Anhang-Variante) – damit
|
||||||
|
// auch reine Mail-Bestätigungen ohne Anhang als Auftragsformular/
|
||||||
|
// Lieferbestätigung etc. an einem Vertrag landen können.
|
||||||
|
export async function saveEmailAsContractDocument(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const emailId = parseInt(req.params.id);
|
||||||
|
if (!(await canAccessCachedEmail(req, res, emailId))) return;
|
||||||
|
const { documentType, notes } = req.body;
|
||||||
|
|
||||||
|
if (!documentType || typeof documentType !== 'string') {
|
||||||
|
res.status(400).json({ success: false, error: 'documentType ist erforderlich' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = await cachedEmailService.getCachedEmailById(emailId);
|
||||||
|
if (!email) {
|
||||||
|
res.status(404).json({ success: false, error: 'E-Mail nicht gefunden' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!email.contractId) {
|
||||||
|
res.status(400).json({ success: false, error: 'E-Mail ist keinem Vertrag zugeordnet' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contract = await prisma.contract.findUnique({
|
||||||
|
where: { id: email.contractId },
|
||||||
|
select: { id: true, contractNumber: true, customerId: true },
|
||||||
|
});
|
||||||
|
if (!contract) {
|
||||||
|
res.status(404).json({ success: false, error: 'Vertrag nicht gefunden' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await canAccessContract(req as AuthRequest, res, contract.id))) return;
|
||||||
|
|
||||||
|
// Empfänger-Adressen parsen
|
||||||
|
let toAddresses: string[] = [];
|
||||||
|
let ccAddresses: string[] = [];
|
||||||
|
try { toAddresses = JSON.parse(email.toAddresses); } catch { toAddresses = [email.toAddresses]; }
|
||||||
|
try { if (email.ccAddresses) ccAddresses = JSON.parse(email.ccAddresses); } catch { /* ignore */ }
|
||||||
|
|
||||||
|
const pdfBuffer = await generateEmailPdf({
|
||||||
|
from: email.fromAddress,
|
||||||
|
to: toAddresses.join(', '),
|
||||||
|
cc: ccAddresses.length > 0 ? ccAddresses.join(', ') : undefined,
|
||||||
|
subject: email.subject || '(Kein Betreff)',
|
||||||
|
date: email.receivedAt,
|
||||||
|
bodyText: email.textBody || undefined,
|
||||||
|
bodyHtml: email.htmlBody || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadsDir = path.join(process.cwd(), 'uploads', 'contract-documents');
|
||||||
|
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true });
|
||||||
|
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
const safeType = documentType.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
||||||
|
const newFilename = `${safeType}-email-${uniqueSuffix}.pdf`;
|
||||||
|
const filePath = path.join(uploadsDir, newFilename);
|
||||||
|
const relativePath = `/uploads/contract-documents/${newFilename}`;
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, pdfBuffer);
|
||||||
|
|
||||||
|
const doc = await prisma.contractDocument.create({
|
||||||
|
data: {
|
||||||
|
contractId: contract.id,
|
||||||
|
documentType,
|
||||||
|
documentPath: relativePath,
|
||||||
|
originalName: `${email.subject || 'email'}.pdf`,
|
||||||
|
notes: notes || null,
|
||||||
|
uploadedBy: (req as any).user?.email || 'email-import',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer
|
||||||
|
const deliveryDate = typeof req.body?.deliveryDate === 'string' ? req.body.deliveryDate : null;
|
||||||
|
await maybeActivateOnDeliveryConfirmation(contract.id, documentType, req, deliveryDate);
|
||||||
|
|
||||||
|
res.json({ success: true, data: doc } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('saveEmailAsContractDocument error:', error);
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||||
|
res.status(500).json({ success: false, error: `Fehler beim Speichern: ${errorMessage}` } as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== SAVE ATTACHMENT AS INVOICE ====================
|
// ==================== SAVE ATTACHMENT AS INVOICE ====================
|
||||||
|
|
||||||
// E-Mail-Anhang als Rechnung speichern
|
// E-Mail-Anhang als Rechnung speichern
|
||||||
|
|||||||
@@ -194,6 +194,15 @@ router.post(
|
|||||||
cachedEmailController.saveEmailAsInvoice
|
cachedEmailController.saveEmailAsInvoice
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// E-Mail als PDF exportieren und als Vertragsdokument hinterlegen
|
||||||
|
// POST /api/emails/:id/save-as-contract-document { documentType, notes?, deliveryDate? }
|
||||||
|
router.post(
|
||||||
|
'/emails/:id/save-as-contract-document',
|
||||||
|
authenticate,
|
||||||
|
requirePermission('contracts:update'),
|
||||||
|
cachedEmailController.saveEmailAsContractDocument
|
||||||
|
);
|
||||||
|
|
||||||
// Anhang als Rechnung speichern
|
// Anhang als Rechnung speichern
|
||||||
// POST /api/emails/:id/attachments/:filename/save-as-invoice { invoiceDate, invoiceType, notes? }
|
// POST /api/emails/:id/attachments/:filename/save-as-invoice { invoiceDate, invoiceType, notes? }
|
||||||
router.post(
|
router.post(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FileText, User, CreditCard, IdCard, AlertTriangle, Check, ChevronDown, ChevronRight, Receipt } from 'lucide-react';
|
import { FileText, User, CreditCard, IdCard, AlertTriangle, Check, ChevronDown, ChevronRight, Receipt, FolderOpen } from 'lucide-react';
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import Input from '../ui/Input';
|
import Input from '../ui/Input';
|
||||||
@@ -9,6 +9,17 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import type { InvoiceType } from '../../types';
|
import type { InvoiceType } from '../../types';
|
||||||
|
|
||||||
|
const CONTRACT_DOCUMENT_TYPES = [
|
||||||
|
'Auftragsformular',
|
||||||
|
'Auftragsbestätigung',
|
||||||
|
'Lieferbestätigung',
|
||||||
|
'Vertragsunterlagen',
|
||||||
|
'Vollmacht',
|
||||||
|
'Widerrufsbelehrung',
|
||||||
|
'Preisblatt',
|
||||||
|
'Sonstiges',
|
||||||
|
];
|
||||||
|
|
||||||
interface SaveEmailAsPdfModalProps {
|
interface SaveEmailAsPdfModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -24,7 +35,7 @@ type SelectedTarget = {
|
|||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SaveMode = 'document' | 'invoice';
|
type SaveMode = 'document' | 'invoice' | 'contractDocument';
|
||||||
|
|
||||||
export default function SaveEmailAsPdfModal({
|
export default function SaveEmailAsPdfModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -40,6 +51,11 @@ export default function SaveEmailAsPdfModal({
|
|||||||
invoiceType: 'INTERIM' as InvoiceType,
|
invoiceType: 'INTERIM' as InvoiceType,
|
||||||
notes: '',
|
notes: '',
|
||||||
});
|
});
|
||||||
|
const [contractDocumentData, setContractDocumentData] = useState({
|
||||||
|
documentType: CONTRACT_DOCUMENT_TYPES[0],
|
||||||
|
notes: '',
|
||||||
|
deliveryDate: new Date().toISOString().split('T')[0],
|
||||||
|
});
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// Ziele laden (gleiche wie bei Anhängen)
|
// Ziele laden (gleiche wie bei Anhängen)
|
||||||
@@ -51,8 +67,10 @@ export default function SaveEmailAsPdfModal({
|
|||||||
|
|
||||||
const targets = targetsData?.data;
|
const targets = targetsData?.data;
|
||||||
|
|
||||||
// Prüfen ob es ein Energievertrag ist
|
// Prüfen ob es ein Energievertrag ist (für Rechnungs-Modus)
|
||||||
const isEnergyContract = targets?.contract?.type === 'ELECTRICITY' || targets?.contract?.type === 'GAS';
|
const isEnergyContract = targets?.contract?.type === 'ELECTRICITY' || targets?.contract?.type === 'GAS';
|
||||||
|
// Vertrag zugeordnet? → Vertragsdokument-Modus erlaubt
|
||||||
|
const hasContract = !!targets?.contract;
|
||||||
|
|
||||||
const saveMutation = useMutation({
|
const saveMutation = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
@@ -111,6 +129,33 @@ export default function SaveEmailAsPdfModal({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const saveContractDocumentMutation = useMutation({
|
||||||
|
mutationFn: () => {
|
||||||
|
const isDelivery = contractDocumentData.documentType.trim().toLowerCase() === 'lieferbestätigung';
|
||||||
|
return cachedEmailApi.saveEmailAsContractDocument(emailId, {
|
||||||
|
documentType: contractDocumentData.documentType,
|
||||||
|
notes: contractDocumentData.notes || undefined,
|
||||||
|
deliveryDate: isDelivery ? contractDocumentData.deliveryDate : undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('E-Mail als Vertragsdokument gespeichert');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['attachment-targets', emailId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contracts'] });
|
||||||
|
|
||||||
|
if (targets?.contract?.id) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contract', targets.contract.id.toString()] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contract-documents', targets.contract.id] });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess?.();
|
||||||
|
handleClose();
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(error.message || 'Fehler beim Speichern des Vertragsdokuments');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setSelectedTarget(null);
|
setSelectedTarget(null);
|
||||||
setSaveMode('document');
|
setSaveMode('document');
|
||||||
@@ -119,6 +164,11 @@ export default function SaveEmailAsPdfModal({
|
|||||||
invoiceType: 'INTERIM',
|
invoiceType: 'INTERIM',
|
||||||
notes: '',
|
notes: '',
|
||||||
});
|
});
|
||||||
|
setContractDocumentData({
|
||||||
|
documentType: CONTRACT_DOCUMENT_TYPES[0],
|
||||||
|
notes: '',
|
||||||
|
deliveryDate: new Date().toISOString().split('T')[0],
|
||||||
|
});
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,7 +285,7 @@ export default function SaveEmailAsPdfModal({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPending = saveMutation.isPending || saveInvoiceMutation.isPending;
|
const isPending = saveMutation.isPending || saveInvoiceMutation.isPending || saveContractDocumentMutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={handleClose} title="E-Mail als PDF speichern" size="lg">
|
<Modal isOpen={isOpen} onClose={handleClose} title="E-Mail als PDF speichern" size="lg">
|
||||||
@@ -263,12 +313,13 @@ export default function SaveEmailAsPdfModal({
|
|||||||
|
|
||||||
{targets && (
|
{targets && (
|
||||||
<>
|
<>
|
||||||
{/* Mode Toggle für Energieverträge */}
|
{/* Mode Toggle: Vertragsdokument-Tab wenn ein Vertrag verknüpft ist,
|
||||||
{isEnergyContract && (
|
Rechnungs-Tab nur bei Energieverträgen. */}
|
||||||
|
{hasContract && (
|
||||||
<div className="flex gap-2 p-1 bg-gray-100 rounded-lg">
|
<div className="flex gap-2 p-1 bg-gray-100 rounded-lg">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSaveMode('document')}
|
onClick={() => setSaveMode('document')}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
saveMode === 'document'
|
saveMode === 'document'
|
||||||
? 'bg-white text-blue-600 shadow-sm'
|
? 'bg-white text-blue-600 shadow-sm'
|
||||||
: 'text-gray-600 hover:text-gray-900'
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
@@ -277,9 +328,21 @@ export default function SaveEmailAsPdfModal({
|
|||||||
<FileText className="w-4 h-4" />
|
<FileText className="w-4 h-4" />
|
||||||
Als Dokument
|
Als Dokument
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setSaveMode('contractDocument')}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
saveMode === 'contractDocument'
|
||||||
|
? 'bg-white text-orange-600 shadow-sm'
|
||||||
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FolderOpen className="w-4 h-4" />
|
||||||
|
Vertragsdokument
|
||||||
|
</button>
|
||||||
|
{isEnergyContract && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setSaveMode('invoice')}
|
onClick={() => setSaveMode('invoice')}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
saveMode === 'invoice'
|
saveMode === 'invoice'
|
||||||
? 'bg-white text-green-600 shadow-sm'
|
? 'bg-white text-green-600 shadow-sm'
|
||||||
: 'text-gray-600 hover:text-gray-900'
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
@@ -288,6 +351,7 @@ export default function SaveEmailAsPdfModal({
|
|||||||
<Receipt className="w-4 h-4" />
|
<Receipt className="w-4 h-4" />
|
||||||
Als Rechnung
|
Als Rechnung
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -377,6 +441,46 @@ export default function SaveEmailAsPdfModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Contract Document Mode */}
|
||||||
|
{saveMode === 'contractDocument' && hasContract && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-3 bg-orange-50 rounded-lg">
|
||||||
|
<p className="text-sm text-orange-700">
|
||||||
|
Die E-Mail wird als Vertragsdokument für den Vertrag <strong>{targets.contract?.contractNumber}</strong> gespeichert.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Dokumenttyp"
|
||||||
|
value={contractDocumentData.documentType}
|
||||||
|
onChange={(e) => setContractDocumentData({ ...contractDocumentData, documentType: e.target.value })}
|
||||||
|
options={CONTRACT_DOCUMENT_TYPES.map((t) => ({ value: t, label: t }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Notizen (optional)"
|
||||||
|
value={contractDocumentData.notes}
|
||||||
|
onChange={(e) => setContractDocumentData({ ...contractDocumentData, notes: e.target.value })}
|
||||||
|
placeholder="Optionale Anmerkungen..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
{contractDocumentData.documentType.trim().toLowerCase() === 'lieferbestätigung' && (
|
||||||
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Lieferdatum</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={contractDocumentData.deliveryDate}
|
||||||
|
onChange={(e) => setContractDocumentData({ ...contractDocumentData, deliveryDate: e.target.value })}
|
||||||
|
className="block w-full max-w-[220px] px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-gray-600 mt-1">
|
||||||
|
Falls der Vertrag noch auf Entwurf steht, wird er auf Aktiv gesetzt und dieses Datum als Vertragsbeginn übernommen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -396,14 +500,15 @@ export default function SaveEmailAsPdfModal({
|
|||||||
<Button variant="secondary" onClick={handleClose}>
|
<Button variant="secondary" onClick={handleClose}>
|
||||||
Abbrechen
|
Abbrechen
|
||||||
</Button>
|
</Button>
|
||||||
{saveMode === 'document' ? (
|
{saveMode === 'document' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => saveMutation.mutate()}
|
onClick={() => saveMutation.mutate()}
|
||||||
disabled={!selectedTarget || isPending}
|
disabled={!selectedTarget || isPending}
|
||||||
>
|
>
|
||||||
{isPending ? 'Wird erstellt...' : 'Als PDF speichern'}
|
{isPending ? 'Wird erstellt...' : 'Als PDF speichern'}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
)}
|
||||||
|
{saveMode === 'invoice' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => saveInvoiceMutation.mutate()}
|
onClick={() => saveInvoiceMutation.mutate()}
|
||||||
disabled={!invoiceData.invoiceDate || isPending}
|
disabled={!invoiceData.invoiceDate || isPending}
|
||||||
@@ -411,6 +516,14 @@ export default function SaveEmailAsPdfModal({
|
|||||||
{isPending ? 'Wird erstellt...' : 'Als Rechnung speichern'}
|
{isPending ? 'Wird erstellt...' : 'Als Rechnung speichern'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{saveMode === 'contractDocument' && (
|
||||||
|
<Button
|
||||||
|
onClick={() => saveContractDocumentMutation.mutate()}
|
||||||
|
disabled={!contractDocumentData.documentType || isPending}
|
||||||
|
>
|
||||||
|
{isPending ? 'Wird erstellt...' : 'Als Vertragsdokument speichern'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -710,6 +710,18 @@ export const cachedEmailApi = {
|
|||||||
);
|
);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
// E-Mail als Vertragsdokument speichern (PDF aus der Mail wird als
|
||||||
|
// ContractDocument hinterlegt – parallel zur Anhang-Variante)
|
||||||
|
saveEmailAsContractDocument: async (
|
||||||
|
emailId: number,
|
||||||
|
params: { documentType: string; notes?: string; deliveryDate?: string },
|
||||||
|
) => {
|
||||||
|
const res = await api.post<ApiResponse<{ id: number; documentType: string; documentPath: string }>>(
|
||||||
|
`/emails/${emailId}/save-as-contract-document`,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
// Anhang als Rechnung speichern (für Energieverträge)
|
// Anhang als Rechnung speichern (für Energieverträge)
|
||||||
saveAttachmentAsInvoice: async (emailId: number, filename: string, params: { invoiceDate: string; invoiceType: string; notes?: string }) => {
|
saveAttachmentAsInvoice: async (emailId: number, filename: string, params: { invoiceDate: string; invoiceType: string; notes?: string }) => {
|
||||||
const encodedFilename = encodeURIComponent(filename);
|
const encodedFilename = encodeURIComponent(filename);
|
||||||
|
|||||||
Reference in New Issue
Block a user