Files
thetool/public/js/pages/ManualInvoice/ManualInvoice.js

539 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Vue.component('manual-invoice', {
template: `
<tt-card>
<div class="d-flex justify-content-between align-items-center mb-3">
<tt-button text="Neue Rechnung" icon="fas fa-plus" @click="openModal()" additional-class="btn-primary"/>
</div>
<tt-table-crud ref="table" emit-edit @edit="openModal($event)" @createGutschrift="openGutschriftModal($event)" @pdfPreview="handlePdfPreview($event)" @sendInvoice="handleSendInvoice($event)">
<template v-slot:total="{ row }">{{ formatPrice(row.total) }}</template>
<template v-slot:total_gross="{ row }">{{ formatPrice(row.total_gross) }}</template>
<template v-slot:invoice_date="{ row }">{{ formatDate(row.invoice_date) }}</template>
<template v-slot:customerName="{ row }">{{ row.customerName }}</template>
<template v-slot:status="{ row }">
<span :class="getStatusClass(row.status)">{{ getStatusText(row.status) }}</span>
</template>
<template v-slot:actions="{ row }">
<button class="btn btn-sm btn-primary" @click="downloadPdf(row.id)" title="PDF herunterladen"><i class="fas fa-file-pdf"></i></button>
</template>
</tt-table-crud>
<manual-invoice-modal v-if="isModalOpen" :initial-data="editingInvoiceData" @close="closeModal" @save="handleSave"/>
<gutschrift-modal v-if="isGutschriftModalOpen" :invoice-id="gutschriftInvoiceId" @close="closeGutschriftModal" @created="handleGutschriftCreated"/>
<send-invoice-modal v-if="isSendModalOpen" :invoice-id="sendInvoiceId" @close="closeSendModal" @sent="handleInvoiceSent"/>
</tt-card>
`,
data: () => ({ isModalOpen: false, editingInvoiceData: null, isGutschriftModalOpen: false, gutschriftInvoiceId: null, isSendModalOpen: false, sendInvoiceId: null }),
methods: {
openModal(invoice = null) {
this.editingInvoiceData = invoice ? JSON.parse(JSON.stringify(invoice)) : null;
this.isModalOpen = true;
},
closeModal() {
this.isModalOpen = false;
this.editingInvoiceData = null;
this.$refs.table.$refs.table.refreshTable();
},
async handleSave(invoiceData) {
try {
const positions = invoiceData.positions.map(p => {
const amount = parseFloat(p.amount) || 0;
const price = parseFloat(p.price) || 0;
const discount = parseFloat(p.discount) || 0;
const vatrate = parseFloat(p.vatrate) || 0;
const priceAfterDiscount = amount * price * (1 - discount / 100);
return {
...p, amount, price, discount, vatrate,
unit: p.unit || 'Stk.',
price_total: priceAfterDiscount,
price_gross: priceAfterDiscount * (1 + vatrate / 100),
product_id: p.product_id || 0,
contract_id: p.contract_id || 0,
billing_id: p.billing_id || null,
matchcode: p.matchcode || null,
fibu_cost_account: p.fibu_cost_account || null,
fibu_cost_account_legacy: p.fibu_cost_account_legacy || null,
fibu_taxcode: p.fibu_taxcode || null,
options: p.options || null
};
});
const payload = {
...invoiceData,
positions,
owner_id: invoiceData.owner_id || 0,
billingaddress_id: invoiceData.billingaddress_id || 0,
customer_number: invoiceData.customer_number || 0,
country: invoiceData.country || 'Österreich',
billing_type: invoiceData.billing_type || 'invoice',
billing_delivery: 'email',
fibu_payment_due: 14,
fibu_account_number: invoiceData.fibu_account_number || 0,
vatgroup_id: 1,
gesamtrabatt: parseFloat(invoiceData.gesamtrabatt) || 0
};
const url = invoiceData.id ? window.TT_CONFIG.UPDATE_URL : window.TT_CONFIG.CREATE_URL;
const { data } = await axios.post(url, payload);
if (data.success) {
window.notify('success', data.message || 'Rechnung erfolgreich gespeichert!');
this.closeModal();
} else {
window.notify('error', data.message || 'Fehler beim Speichern der Rechnung');
}
} catch (e) {
console.error('Error saving invoice:', e);
window.notify('error', 'Fehler: ' + (e.response?.data?.message || e.message));
}
},
downloadPdf(id) { window.location.href = `${window.TT_CONFIG.BASE_PATH}/ManualInvoice/downloadInvoicePdf?id=${id}`; },
formatPrice(v) { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(v || 0); },
formatDate(ts) { return ts ? moment.unix(ts).format('DD.MM.YYYY') : ''; },
openGutschriftModal(invoice) {
if (invoice.total < 0) return window.notify('error', 'Kann keine Gutschrift für eine Gutschrift erstellen');
this.gutschriftInvoiceId = invoice.id;
this.isGutschriftModalOpen = true;
},
closeGutschriftModal() { this.isGutschriftModalOpen = false; this.gutschriftInvoiceId = null; },
handleGutschriftCreated() { this.closeGutschriftModal(); this.$refs.table.$refs.table.refreshTable(); },
async handlePdfPreview(invoice) {
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/pdfPreview`, { id: invoice.id });
if (data.success && data.url) {
window.open(data.url, '_blank');
} else {
window.notify('error', data.message || 'Fehler beim Öffnen der PDF Vorschau');
}
} catch (e) {
console.error('Error opening PDF preview:', e);
window.notify('error', 'Fehler: ' + (e.response?.data?.message || e.message));
}
},
handleSendInvoice(invoice) {
this.sendInvoiceId = invoice.id;
this.isSendModalOpen = true;
},
closeSendModal() {
this.isSendModalOpen = false;
this.sendInvoiceId = null;
},
handleInvoiceSent() {
this.closeSendModal();
this.$refs.table.$refs.table.refreshTable();
},
getStatusClass(s) { return { 'erstellt': 'badge badge-secondary', 'gesendet': 'badge badge-success', 'exportiert': 'badge badge-primary' }[s] || 'badge badge-secondary'; },
getStatusText(s) { return { 'erstellt': 'Erstellt', 'gesendet': 'Gesendet', 'exportiert': 'Exportiert' }[s] || s; }
}
});
Vue.component('manual-invoice-modal', {
props: ['initialData'],
template: `
<div class="manual-invoice-overlay" :class="overlayClasses" tabindex="-1" ref="overlay">
<div class="info-bar" v-if="!isLargeScreen"><i class="fas fa-info-circle mr-2"></i> Drücke <strong>STRG + Q</strong> um die Vorschau umzuschalten.</div>
<div class="invoice-editor-pane" v-show="isLargeScreen || !showPreviewOnSmallScreen">
<div class="editor-header">
<h3>{{ isCreateMode ? 'Neue Rechnung' : 'Rechnung bearbeiten' }}</h3>
<div class="editor-actions">
<tt-button text="Speichern" icon="fas fa-save" @click="saveInvoice" additional-class="btn-success"/>
<tt-button text="Schließen" icon="fas fa-times" @click="close" additional-class="btn-secondary"/>
</div>
</div>
<div class="editor-content">
<tt-card><template v-slot:header><h5><i class="fas fa-user-tie mr-2"></i>Kunde</h5></template>
<tt-autocomplete label="Kunde suchen" :api-url="customerApiUrl" v-model="invoiceData.billingaddress_id" sm row />
</tt-card>
<tt-card><template v-slot:header><h5><i class="fas fa-file-invoice mr-2"></i>Rechnungsdetails</h5></template>
<div class="form-grid">
<tt-input label="Rechnungsdatum" type="date" v-model="invoiceData.invoice_date" sm/>
<tt-select label="Zahlungsart" v-model="invoiceData.billing_type" :options="billingTypeOptions" sm/>
</div>
<tt-input label="Leistungszeitraum" v-model="invoiceData.leistungszeitraum" sm row placeholder="z.B. 01.01.2025 - 31.01.2025"/>
<tt-input label="Externe Referenz" v-model="invoiceData.externe_referenz" sm row placeholder="z.B. Auftragsnummer, Bestellnummer"/>
<tt-textarea label="Einleitender Text" v-model="invoiceData.einleitender_text" rows="3" sm row/>
</tt-card>
<tt-card><template v-slot:header><h5><i class="fas fa-list-ol mr-2"></i>Positionen</h5></template>
<tt-positions-manager group-mode ref="positionsManager" v-model="invoiceData.positions" :config="positionsConfig" />
</tt-card>
<tt-card><template v-slot:header><h5><i class="fas fa-paragraph mr-2"></i>Texte & Rabatt</h5></template>
<tt-input label="Gesamtrabatt (%)" v-model.number="invoiceData.gesamtrabatt" sm row type="number" placeholder="0"/>
<tt-textarea label="Steuerhinweis" v-model="invoiceData.tax_text" rows="2"/>
</tt-card>
</div>
</div>
<div class="invoice-preview-pane" v-show="isLargeScreen || showPreviewOnSmallScreen">
<div class="pdf-preview-container">
<div v-if="pdfLoading" class="pdf-loading"><i class="fas fa-spinner fa-spin fa-3x"></i><p>PDF wird generiert...</p></div>
<object v-else :data="pdfPreviewUrl" type="application/pdf" width="100%" height="100%">
<p>PDF Vorschau kann nicht angezeigt werden. <a :href="pdfPreviewUrl" target="_blank">Hier klicken</a></p>
</object>
</div>
</div>
</div>
`,
data() {
return {
isCreateMode: !this.initialData?.id,
customerApiUrl: window.TT_CONFIG.BASE_PATH + '/Address/Api?do=findAddress&fibu_primary_account=1',
isLargeScreen: window.innerWidth >= 1920,
showPreviewOnSmallScreen: false,
pdfLoading: false,
pdfPreviewUrl: '',
previewDebounceTimer: null,
invoiceData: {
id: null, invoice_number: null, invoice_date: moment().format('YYYY-MM-DD'),
billingaddress_id: null, owner_id: null, customer_number: 0, fibu_account_number: 0,
company: '', firstname: '', lastname: '', street: '', zip: '', city: '', country: 'Österreich',
uid: '', email: '', billing_type: 'invoice', tax_text: '',
leistungszeitraum: '', einleitender_text: '', externe_referenz: '', gesamtrabatt: 0,
positions: [], total: 0, total_gross: 0
},
billingTypeOptions: [{value: 'invoice', text: 'Rechnung'}, {value: 'sepa', text: 'SEPA'}],
positionsConfig: {
fields: {
product_name: { type: 'input', label: 'Bezeichnung' },
product_info: { type: 'input', label: 'Zusatzinfo' },
amount: { type: 'input', label: 'Menge', inputType: 'number' },
unit: {
type: 'select',
label: 'Einheit',
options: [
{ value: 'Pau.', text: 'Pau.' },
{ value: 'Stk.', text: 'Stk.' },
{ value: 'h', text: 'h' },
{ value: 'm', text: 'm' }
]
},
price: { type: 'input', label: 'Einzelpreis (€)', inputType: 'number' },
discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' },
vatrate: { type: 'input', label: 'USt. (%)', inputType: 'number' },
},
validateForm: (d) => {
if (!d.product_name) { window.notify('error', 'Bezeichnung ist erforderlich.'); return false; }
if (!d.amount) { window.notify('error', 'Menge ist erforderlich.'); return false; }
if (d.price == null) { window.notify('error', 'Preis ist erforderlich.'); return false; }
return true;
}
}
};
},
computed: {
overlayClasses() { return { 'preview-active-small': !this.isLargeScreen && this.showPreviewOnSmallScreen, 'editor-active-small': !this.isLargeScreen && !this.showPreviewOnSmallScreen }; },
totals() {
let subtotal = 0;
(this.invoiceData.positions || []).forEach(p => {
const amount = parseFloat(p.amount) || 0;
const price = parseFloat(p.price) || 0;
const discount = parseFloat(p.discount) || 0;
const lineTotal = amount * price * (1 - discount / 100);
subtotal += lineTotal;
});
// Apply gesamtrabatt
const gesamtrabatt = parseFloat(this.invoiceData.gesamtrabatt) || 0;
const net = subtotal * (1 - gesamtrabatt / 100);
// Calculate VAT
let vat = {}, gross = 0;
(this.invoiceData.positions || []).forEach(p => {
const amount = parseFloat(p.amount) || 0;
const price = parseFloat(p.price) || 0;
const discount = parseFloat(p.discount) || 0;
const r = parseInt(p.vatrate) || 0;
const lineNet = amount * price * (1 - discount / 100) * (1 - gesamtrabatt / 100);
const lineVat = lineNet * (r / 100);
vat[r] = (vat[r] || 0) + lineVat;
gross += lineNet + lineVat;
});
return { subtotal, net, vat, gross };
}
},
watch: {
'invoiceData': { handler() { this.debouncedPreviewUpdate(); }, deep: true },
'invoiceData.billingaddress_id': {
async handler(newId) {
if (!newId) return Object.assign(this.invoiceData, {
company: '', firstname: '', lastname: '', street: '', zip: '', city: '',
country: 'Österreich', uid: '', email: '', customer_number: 0, fibu_account_number: 0, owner_id: 0
});
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Address/api?do=getAddress&id=${newId}`);
if (data.status === 'OK' && data.result.address) {
const a = data.result.address;
Object.assign(this.invoiceData, {
company: a.company || '', firstname: a.firstname || '', lastname: a.lastname || '',
street: a.street || '', zip: a.zip || '', city: a.city || '', country: 'Österreich',
uid: a.uid || '', email: a.email || '', customer_number: a.customer_number || 0,
fibu_account_number: a.fibu_account_number || 0, owner_id: newId
});
}
}
}
},
created() {
if (this.initialData) {
this.invoiceData = { ...this.invoiceData, ...JSON.parse(JSON.stringify(this.initialData)) };
if (typeof this.invoiceData.positions === 'string') {
try { this.invoiceData.positions = JSON.parse(this.invoiceData.positions); } catch { this.invoiceData.positions = []; }
}
if (!Array.isArray(this.invoiceData.positions)) this.invoiceData.positions = [];
}
},
mounted() {
window.addEventListener('resize', this.handleResize);
window.addEventListener('keydown', this.handleGlobalKeydown);
this.handleResize();
this.$nextTick(() => { this.$refs.overlay?.focus(); this.updatePdfPreview(); });
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener('keydown', this.handleGlobalKeydown);
clearTimeout(this.previewDebounceTimer);
},
methods: {
close() { this.$emit('close'); },
saveInvoice() {
if (!this.invoiceData.billingaddress_id) return window.notify('error', 'Bitte wählen Sie einen Kunden aus.');
if (!this.invoiceData.positions?.length) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
this.$emit('save', this.invoiceData);
},
handleResize() { this.isLargeScreen = window.innerWidth >= 1920; },
handleGlobalKeydown(e) {
if (e.ctrlKey && e.key === 'q') { e.preventDefault(); this.togglePreviewVisibility(); }
},
togglePreviewVisibility() { if (!this.isLargeScreen) this.showPreviewOnSmallScreen = !this.showPreviewOnSmallScreen; },
debouncedPreviewUpdate() {
clearTimeout(this.previewDebounceTimer);
this.previewDebounceTimer = setTimeout(() => this.updatePdfPreview(), 2000);
},
async updatePdfPreview() {
this.pdfLoading = true;
try {
const positions = this.invoiceData.positions
.filter(p => p.product_name && (parseFloat(p.amount) || 0) > 0) // Filter out empty positions
.map(p => {
const amount = parseFloat(p.amount) || 0;
const price = parseFloat(p.price) || 0;
const discount = parseFloat(p.discount) || 0;
const vatrate = parseFloat(p.vatrate) || 0;
const priceAfterDiscount = amount * price * (1 - discount / 100);
return { ...p, amount, price, discount, vatrate, unit: p.unit || 'Stk.', price_total: priceAfterDiscount, price_gross: priceAfterDiscount * (1 + vatrate / 100) };
});
const payload = {
preview: true, ...this.invoiceData,
total: this.totals.net, total_gross: this.totals.gross, positions
};
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/createPDF`, payload, { responseType: 'blob' });
if (this.pdfPreviewUrl) URL.revokeObjectURL(this.pdfPreviewUrl);
this.pdfPreviewUrl = URL.createObjectURL(new Blob([data], { type: 'application/pdf' })) + '#view=FitH';
} catch (e) {
console.error('Error preview:', e);
window.notify('error', 'Fehler beim Generieren der PDF-Vorschau');
} finally {
this.pdfLoading = false;
}
}
}
});
Vue.component('gutschrift-modal', {
props: ['invoiceId'],
template: `
<tt-modal :show="true" @close="close" size="lg" title="Gutschrift erstellen">
<div v-if="loading" class="text-center py-5"><i class="fas fa-spinner fa-spin fa-3x"></i><p class="mt-3">Lade Rechnungsdaten...</p></div>
<div v-else-if="invoice">
<div class="alert alert-info"><strong>Originalrechnung:</strong> {{ invoice.invoice_number }} - {{ invoice.customer_name }}</div>
<div v-if="!invoice.positions.length" class="alert alert-warning"><i class="fas fa-exclamation-triangle"></i> Alle Positionen gutgeschrieben.</div>
<div v-else>
<p><strong>Positionen wählen:</strong></p>
<table class="table table-sm table-bordered">
<thead><tr>
<th style="width: 50px;"><input type="checkbox" @change="toggleAll" v-model="allSelected"></th>
<th>Bezeichnung</th><th style="width: 100px;">Orig.</th><th style="width: 100px;">Gutschr.</th><th style="width: 100px;">Verfügbar</th>
<th style="width: 120px;">Neu Gutschrift</th><th style="width: 100px;">Einzel</th><th style="width: 100px;">Gesamt</th>
</tr></thead>
<tbody>
<tr v-for="(pos, index) in invoice.positions" :key="index">
<td class="text-center"><input type="checkbox" v-model="selectedPositions[index]"></td>
<td><strong>{{ pos.product_name }}</strong><div v-if="pos.product_info" class="text-muted small">{{ pos.product_info }}</div></td>
<td class="text-right">{{ pos.original_amount }}</td><td class="text-right">{{ pos.credited_amount }}</td>
<td class="text-right">{{ pos.available_amount }}</td>
<td><input type="number" class="form-control form-control-sm" v-model.number="creditAmounts[index]" :max="pos.available_amount" :disabled="!selectedPositions[index]" step="0.001" min="0.001"></td>
<td class="text-right">{{ formatPrice(pos.price) }}</td><td class="text-right">{{ formatPrice(calcTotal(index)) }}</td>
</tr>
</tbody>
<tfoot><tr><td colspan="7" class="text-right"><strong>Gesamt:</strong></td><td class="text-right"><strong>{{ formatPrice(totalCredit) }}</strong></td></tr></tfoot>
</table>
</div>
</div>
<template v-slot:footer>
<tt-button text="Erstellen" icon="fas fa-check" @click="create" additional-class="btn-success" :disabled="!validSelection || creating"/>
<tt-button text="Abbrechen" icon="fas fa-times" @click="close" additional-class="btn-secondary"/>
</template>
</tt-modal>
`,
data: () => ({ loading: true, creating: false, invoice: null, selectedPositions: {}, creditAmounts: {}, allSelected: false }),
computed: {
validSelection() { return this.invoice && Object.keys(this.selectedPositions).some(i => this.selectedPositions[i] && this.creditAmounts[i] > 0 && this.creditAmounts[i] <= this.invoice.positions[i].available_amount); },
totalCredit() { return Object.keys(this.selectedPositions).reduce((sum, i) => this.selectedPositions[i] ? sum + ((this.creditAmounts[i] || 0) * this.invoice.positions[i].price) : sum, 0); }
},
async mounted() {
try {
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getInvoiceForGutschrift?id=${this.invoiceId}`);
if (data.success) {
this.invoice = data.invoice;
this.invoice.positions.forEach((p, i) => { this.$set(this.selectedPositions, i, false); this.$set(this.creditAmounts, i, p.available_amount); });
} else {
window.notify('error', data.message || 'Fehler'); this.close();
}
} catch (e) { window.notify('error', 'Fehler'); this.close(); } finally { this.loading = false; }
},
methods: {
toggleAll() { this.invoice.positions.forEach((p, i) => this.$set(this.selectedPositions, i, this.allSelected)); },
calcTotal(i) { return this.selectedPositions[i] ? (this.creditAmounts[i] || 0) * this.invoice.positions[i].price : 0; },
async create() {
const positions = this.invoice.positions
.map((p, i) => ({ p, i })).filter(({ i }) => this.selectedPositions[i])
.map(({ p, i }) => {
const amt = this.creditAmounts[i];
if (amt > p.available_amount) throw new Error(`Menge zu hoch: ${p.product_name}`);
return amt > 0 ? { ...p, amount: amt } : null;
}).filter(Boolean);
if (!positions.length) return window.notify('error', 'Keine Positionen gewählt');
this.creating = true;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/createGutschrift`, { original_invoice_id: this.invoiceId, positions });
if (data.success) { window.notify('success', 'Gutschrift erstellt'); this.$emit('created', data.credit_invoice_id); }
else window.notify('error', data.message || 'Fehler');
} catch (e) { window.notify('error', e.message || 'Fehler'); } finally { this.creating = false; }
},
close() { this.$emit('close'); },
formatPrice(v) { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(v || 0); }
}
});
Vue.component('send-invoice-modal', {
props: ['invoiceId'],
template: `
<tt-modal :show="true" @close="close" @submit="handleAction" :submit-text="actionButtonText" :is-loading="loading" size="md">
<template v-slot:header>
<h5><i class="fas fa-paper-plane mr-2"></i>Rechnung aussenden</h5>
</template>
<div v-if="!invoice" class="text-center py-4">
<tt-loader />
</div>
<div v-else>
<div class="mb-3">
<strong>Rechnung:</strong> {{ invoice.invoice_number }}<br/>
<strong>Kunde:</strong> {{ invoice.customerName }}
</div>
<hr/>
<div class="form-group">
<label>Aktion auswählen:</label>
<div class="form-check">
<input class="form-check-input" type="radio" id="action-email" value="email" v-model="selectedAction" :disabled="!invoice.email">
<label class="form-check-label" for="action-email">
<i class="fas fa-envelope mr-2"></i>Per E-Mail versenden
<span v-if="invoice.email" class="text-muted d-block ml-4">an {{ invoice.email }}</span>
<span v-else class="text-danger d-block ml-4">Keine E-Mail-Adresse vorhanden</span>
</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="radio" id="action-download" value="download" v-model="selectedAction">
<label class="form-check-label" for="action-download">
<i class="fas fa-download mr-2"></i>PDF herunterladen
</label>
</div>
</div>
<div v-if="selectedAction === 'email' && invoice.email" class="mt-3">
<tt-input label="E-Mail-Adresse" v-model="emailAddress" sm/>
</div>
</div>
</tt-modal>
`,
data() {
return {
invoice: null,
loading: false,
selectedAction: 'email',
emailAddress: ''
};
},
computed: {
actionButtonText() {
return this.selectedAction === 'email' ? 'E-Mail versenden' : 'Herunterladen';
}
},
async mounted() {
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getInvoiceEmail`, { id: this.invoiceId });
if (data.success) {
this.invoice = data.invoice;
this.emailAddress = data.invoice.email || '';
this.selectedAction = data.invoice.email ? 'email' : 'download';
} else {
window.notify('error', data.message || 'Fehler beim Laden der Rechnung');
this.close();
}
} catch (e) {
window.notify('error', 'Fehler beim Laden der Rechnung');
this.close();
}
},
methods: {
async handleAction() {
if (this.selectedAction === 'email') {
await this.sendEmail();
} else {
await this.downloadPdf();
}
},
async sendEmail() {
if (!this.emailAddress) {
window.notify('error', 'Bitte E-Mail-Adresse eingeben');
return;
}
this.loading = true;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/sendInvoiceEmail`, {
id: this.invoiceId,
email: this.emailAddress
});
if (data.success) {
window.notify('success', data.message);
this.$emit('sent');
} else {
window.notify('error', data.message || 'Fehler beim Versenden');
}
} catch (e) {
window.notify('error', 'Fehler: ' + (e.response?.data?.message || e.message));
} finally {
this.loading = false;
}
},
async downloadPdf() {
this.loading = true;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/downloadInvoice`, {
id: this.invoiceId
});
if (data.success && data.url) {
window.location.href = data.url;
this.$emit('sent');
} else {
window.notify('error', data.message || 'Fehler beim Download');
}
} catch (e) {
window.notify('error', 'Fehler: ' + (e.response?.data?.message || e.message));
} finally {
this.loading = false;
}
},
close() {
this.$emit('close');
}
}
});