Vue.component('manual-invoice', { template: `
`, data: () => ({ isModalOpen: false, editingInvoiceData: null, shippingNoteImportData: null, isGutschriftModalOpen: false, gutschriftInvoiceId: null, isSendModalOpen: false, sendInvoiceId: null }), mounted() { // Check for shipping note import data const shippingNoteData = localStorage.getItem('ManualInvoice_create'); if (shippingNoteData) { try { // Parse and store the data this.shippingNoteImportData = JSON.parse(shippingNoteData); // Delete from localStorage immediately so it doesn't auto-open again on reload localStorage.removeItem('ManualInvoice_create'); // Auto-open modal for import this.openModal(); } catch (e) { console.error('Error parsing shipping note data:', e); localStorage.removeItem('ManualInvoice_create'); } } }, methods: { openModal(invoice = null) { this.editingInvoiceData = invoice ? JSON.parse(JSON.stringify(invoice)) : null; this.isModalOpen = true; }, closeModal() { this.isModalOpen = false; this.editingInvoiceData = null; this.shippingNoteImportData = null; this.$refs.table.$refs.table.refreshTable(); }, async handleSave(invoiceData) { try { const positions = invoiceData.positions.map(p => ({ ...p, amount: parseFloat(p.amount) || 0, price: parseFloat(p.price) || 0, discount: parseFloat(p.discount) || 0, vatrate: parseFloat(p.vatrate) || 0, unit: p.unit || 'Stk.', warehousearticle_id: p.warehousearticle_id || p.product_id || 0, warehousearticle_name: p.warehousearticle_name || p.product_name || '', 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', fibu_payment_due: 14, fibu_account_number: invoiceData.fibu_account_number || 0, vatgroup_id: invoiceData.vatgroup_id || 1, performance_period: invoiceData.performance_period || invoiceData.leistungszeitraum || '', introductory_text: invoiceData.introductory_text || invoiceData.einleitender_text || '', external_reference: invoiceData.external_reference || invoiceData.externe_referenz || '', total_discount: parseFloat(invoiceData.total_discount || 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', 'shippingNoteImport'], template: `
Drücke STRG + Q um die Vorschau umzuschalten.

{{ isCreateMode ? 'Neue Rechnung' : 'Rechnung bearbeiten' }}

{{ effectiveBillingType === 'sepa' ? 'SEPA' : 'Rechnung' }} Brutto überschreitet SEPA-Limit ({{ formatPrice(customerBillingInfo.manual_invoice_sepa_limit) }})

PDF wird generiert...

PDF Vorschau kann nicht angezeigt werden. Hier klicken

`, 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, customerBillingInfo: { billing_type: 'invoice', manual_invoice_sepa_limit: null, vatarea: 'domestic', tax_text: '' }, 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: '', vatgroup_id: 1, performance_period: '', introductory_text: '', external_reference: '', total_discount: 0, positions: [], total: 0, total_gross: 0 }, positionsConfig: { fields: { article_id: { type: 'input-article', label: 'Artikel', apiUrl: '/WarehouseArticle/autocomplete', customFieldReference: 'WarehouseArticle', emitDisplayValue: true }, warehousearticle_name: { type: 'input', label: 'Bezeichnung' }, product_info: { type: 'input', label: 'Zusatzinfo' }, amount: { type: 'input', label: 'Menge', inputType: 'number' }, price_type: { type: 'select', label: 'Preistyp', options: [] }, price: { type: 'input', label: 'Einzelpreis (€)', inputType: 'number' }, discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' }, }, validateForm: (d) => { if (!d.warehousearticle_name) { window.notify('error', 'Bezeichnung ist erforderlich.'); return false; } if (d.amount == null || d.amount === '') { window.notify('error', 'Menge ist erforderlich.'); return false; } if (d.price == null) { window.notify('error', 'Preis ist erforderlich.'); return false; } return true; } }, articlePrices: [] }; }, 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; }); const totalDiscount = parseFloat(this.invoiceData.total_discount) || 0; const net = subtotal * (1 - totalDiscount / 100); 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 - totalDiscount / 100); const lineVat = lineNet * (r / 100); vat[r] = (vat[r] || 0) + lineVat; gross += lineNet + lineVat; }); return { subtotal, net, vat, gross }; }, effectiveBillingType() { if (this.customerBillingInfo.billing_type !== 'sepa') return 'invoice'; if (this.customerBillingInfo.manual_invoice_sepa_limit === null) return 'sepa'; return this.totals.gross <= this.customerBillingInfo.manual_invoice_sepa_limit ? 'sepa' : 'invoice'; } }, watch: { 'invoiceData': { handler() { this.debouncedPreviewUpdate(); }, deep: true }, effectiveBillingType: { handler(newType) { this.invoiceData.billing_type = newType; }, immediate: true }, 'invoiceData.billingaddress_id': { async handler(newId) { if (!newId) { Object.assign(this.invoiceData, { company: '', firstname: '', lastname: '', street: '', zip: '', city: '', country: 'Österreich', uid: '', email: '', customer_number: 0, fibu_account_number: 0, owner_id: 0 }); this.customerBillingInfo = { billing_type: 'invoice', manual_invoice_sepa_limit: null, vatarea: 'domestic', tax_text: '' }; return; } 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 }); } await this.fetchCustomerBillingInfo(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 = []; } // Check for shipping note import data from prop if (this.shippingNoteImport && Array.isArray(this.shippingNoteImport) && this.shippingNoteImport.length > 0) { try { this.processShippingNoteImport(this.shippingNoteImport); } catch (e) { console.error('Error processing shipping note import:', e); window.notify('error', 'Fehler beim Importieren des Lieferscheins'); } } }, 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() { this.invoiceData.invoice_date = moment().format('YYYY-MM-DD'); this.invoiceData.billing_type = this.effectiveBillingType; this.invoiceData.tax_text = this.customerBillingInfo.tax_text; 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); }, formatPrice(value) { if (value === null || value === undefined) return '-'; return new Intl.NumberFormat('de-AT', { style: 'currency', currency: 'EUR' }).format(value); }, async fetchCustomerBillingInfo(addressId) { if (!addressId) return; try { const vatgroupId = this.invoiceData.vatgroup_id || 2; const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getCustomerBillingInfo?address_id=${addressId}&vatgroup_id=${vatgroupId}`); if (data.success) { this.customerBillingInfo = { billing_type: data.billing_type || 'invoice', manual_invoice_sepa_limit: data.manual_invoice_sepa_limit, vatarea: data.vatarea || 'domestic', tax_text: data.tax_text || '' }; this.invoiceData.tax_text = data.tax_text || ''; } } catch (e) { console.error('Error fetching customer billing info:', e); } }, 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; }, async onArticleSelected(articleId) { if (!articleId) { // Reset price type options when no article selected this.articlePrices = []; this.positionsConfig.fields.price_type.options = []; return; } try { const vatarea = this.customerBillingInfo.vatarea || 'domestic'; const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getArticleVatInfo?article_id=${articleId}&vatarea=${vatarea}`); if (data.success && this.$refs.positionsManager) { const pm = this.$refs.positionsManager; if (data.article) { pm.$set(pm.formData, 'warehousearticle_name', data.article.articleNumber + ' | ' + data.article.title); pm.$set(pm.formData, 'product_info', data.article.description || ''); pm.$set(pm.formData, 'unit', data.article.unit || 'Stk.'); } pm.$set(pm.formData, 'vatrate', parseFloat(data.vatrate) || 20); pm.$set(pm.formData, 'fibu_cost_account', data.fibu_cost_account); pm.$set(pm.formData, 'fibu_cost_account_legacy', data.fibu_cost_account_legacy); pm.$set(pm.formData, 'fibu_taxcode', data.fibu_taxcode); this.invoiceData.vatgroup_id = data.vatgroup_id; await this.updateTaxText(data.vatgroup_id); // Handle prices and price type selection this.articlePrices = data.prices || []; if (this.articlePrices.length > 0) { const priceOptions = this.articlePrices.map(p => ({ value: p.title, text: `${p.title} (${this.formatPrice(p.price)})` })); this.positionsConfig.fields.price_type.options = priceOptions; // Set first price as default pm.$set(pm.formData, 'price_type', this.articlePrices[0].title); pm.$set(pm.formData, 'price', this.articlePrices[0].price); } else { this.positionsConfig.fields.price_type.options = []; } } } catch (e) { console.error('Error fetching article VAT info:', e); } }, onPriceTypeChanged(priceType) { if (!priceType || !this.articlePrices.length) return; const selectedPrice = this.articlePrices.find(p => p.title === priceType); if (selectedPrice && this.$refs.positionsManager) { this.$refs.positionsManager.$set(this.$refs.positionsManager.formData, 'price', selectedPrice.price); } }, async updateTaxText(vatgroupId) { if (!vatgroupId) return; try { const vatarea = this.customerBillingInfo.vatarea || 'domestic'; const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getTaxText?vatgroup_id=${vatgroupId}&vatarea=${vatarea}`); if (data.success) { this.customerBillingInfo.tax_text = data.tax_text || ''; this.invoiceData.tax_text = data.tax_text || ''; } } catch (e) { console.error('Error fetching tax text:', e); } }, debouncedPreviewUpdate() { clearTimeout(this.previewDebounceTimer); this.previewDebounceTimer = setTimeout(() => this.updatePdfPreview(), 2000); }, async updatePdfPreview() { this.pdfLoading = true; try { const positions = this.invoiceData.positions .filter(p => p.warehousearticle_name && (parseFloat(p.amount) || 0) !== 0) .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; } }, processShippingNoteImport(shippingNoteDataArray) { // Temporarily disable the preview update during import to prevent memory leak clearTimeout(this.previewDebounceTimer); const originalWatcher = this.$options.watch['invoiceData']; delete this.$options.watch['invoiceData']; try { for (const shippingNoteData of shippingNoteDataArray) { // Pre-fill billing address fields if (shippingNoteData.billingAddress) { const addr = shippingNoteData.billingAddress; Object.assign(this.invoiceData, { billingaddress_id: addr.id, customer_number: addr.customer_number || 0, company: addr.company || '', firstname: addr.firstname || '', lastname: addr.lastname || '', street: addr.street || '', zip: addr.zip || '', city: addr.city || '', email: addr.email || '', uid: addr.uid || '', fibu_account_number: addr.fibu_account_number || 0, fibu_payment_due: addr.fibu_payment_due || 14, fibu_payment_skonto: addr.fibu_payment_skonto || 0, fibu_payment_skonto_rate: addr.fibu_payment_skonto_rate || 0, billing_type: addr.billing_type || 'invoice', owner_id: addr.id }); // Banking info (if SEPA) if (addr.billing_type === 'sepa') { Object.assign(this.invoiceData, { bank_account_bank: addr.bank_account_bank || '', bank_account_owner: addr.bank_account_owner || '', bank_account_iban: addr.bank_account_iban || '', bank_account_bic: addr.bank_account_bic || '', sepa_date: addr.sepa_date || '' }); } } // Pre-fill external reference with shipping note reference this.invoiceData.external_reference = `Lieferschein #${shippingNoteData.shippingNoteId}`; // Add introductory text if shipping note has notes if (shippingNoteData.note) { this.invoiceData.introductory_text = shippingNoteData.note; } // Add all positions (batch operation to avoid triggering watcher for each item) if (shippingNoteData.positions && Array.isArray(shippingNoteData.positions)) { const newPositions = shippingNoteData.positions.map(position => ({ warehousearticle_name: position.warehousearticle_name || position.product_name || '', product_info: position.product_info || '', amount: parseFloat(position.amount) || 0, unit: position.unit || 'Stk.', price: parseFloat(position.price) || 0, discount: parseFloat(position.discount) || 0, vatrate: parseFloat(position.vatrate) || 20 })); // Add all positions at once instead of one by one this.invoiceData.positions.push(...newPositions); } } // Notify user const positionCount = shippingNoteDataArray.reduce((sum, sn) => sum + (sn.positions?.length || 0), 0); window.notify('success', `Lieferschein erfolgreich importiert (${positionCount} Position(en))`); } finally { // Re-enable the watcher this.$options.watch['invoiceData'] = originalWatcher; // Trigger one preview update after import is complete this.$nextTick(() => { this.debouncedPreviewUpdate(); }); } } } }); Vue.component('gutschrift-modal', { props: ['invoiceId'], template: `

Lade Rechnungsdaten...

Originalrechnung: {{ invoice.invoice_number }} - {{ invoice.customer_name }}
Alle Positionen gutgeschrieben.

Positionen wählen:

BezeichnungOrig.Gutschr.Verfügbar Neu GutschriftEinzelGesamt
{{ pos.warehousearticle_name }}
{{ pos.product_info }}
{{ pos.original_amount }}{{ pos.credited_amount }} {{ pos.available_amount }} {{ formatPrice(pos.price) }}{{ formatPrice(calcTotal(index)) }}
Gesamt:{{ formatPrice(totalCredit) }}
`, 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.warehousearticle_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: `
Rechnung: {{ invoice.invoice_number }}
Kunde: {{ invoice.customerName }}

`, 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 || ''; } 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'); } } });