/** * invoice-modal.js — Invoice create/edit modal * Uses shared item-editor for accordion items */ import { addItem, getItems, resetItemCounter } from '../utils/item-editor.js'; import { setDefaultDate, showSpinner, hideSpinner } from '../utils/helpers.js'; let currentInvoiceId = null; let qboLaborRate = null; /** * Load labor rate from QBO (called once at startup) */ export async function loadLaborRate() { try { const response = await fetch('/api/qbo/labor-rate'); const data = await response.json(); if (data.rate) { qboLaborRate = data.rate; console.log(`💰 Labor Rate geladen: $${qboLaborRate}`); } } catch (e) { console.log('Labor Rate konnte nicht geladen werden, verwende keinen Default.'); } } export function getLaborRate() { return qboLaborRate; } export async function openInvoiceModal(invoiceId = null) { currentInvoiceId = invoiceId; if (invoiceId) { await loadInvoiceForEdit(invoiceId); } else { prepareNewInvoice(); } document.getElementById('invoice-modal').classList.add('active'); } export function closeInvoiceModal() { document.getElementById('invoice-modal').classList.remove('active'); currentInvoiceId = null; } async function loadInvoiceForEdit(invoiceId) { document.getElementById('invoice-modal-title').textContent = 'Edit Invoice'; const response = await fetch(`/api/invoices/${invoiceId}`); const data = await response.json(); // Set customer in Alpine component const allCust = window.getCustomers ? window.getCustomers() : (window.customers || []); const customer = allCust.find(c => c.id === data.invoice.customer_id); if (customer) { const customerInput = document.querySelector('#invoice-modal input[placeholder="Search customer..."]'); if (customerInput) { customerInput.value = customer.name; customerInput.dispatchEvent(new Event('input')); const alpineData = Alpine.$data(customerInput.closest('[x-data]')); if (alpineData) { alpineData.search = customer.name; alpineData.selectedId = customer.id; alpineData.selectedName = customer.name; } } } document.getElementById('invoice-number').value = data.invoice.invoice_number || ''; document.getElementById('invoice-customer').value = data.invoice.customer_id; document.getElementById('invoice-date').value = data.invoice.invoice_date.split('T')[0]; document.getElementById('invoice-terms').value = data.invoice.terms; document.getElementById('invoice-authorization').value = data.invoice.auth_code || ''; document.getElementById('invoice-tax-exempt').checked = data.invoice.tax_exempt; document.getElementById('invoice-bill-to-name').value = data.invoice.bill_to_name || ''; const sendDateEl = document.getElementById('invoice-send-date'); if (sendDateEl) { sendDateEl.value = data.invoice.scheduled_send_date ? data.invoice.scheduled_send_date.split('T')[0] : ''; } // Load items using shared editor document.getElementById('invoice-items').innerHTML = ''; resetItemCounter(); data.items.forEach(item => { addItem('invoice-items', { item, type: 'invoice', laborRate: qboLaborRate, onUpdate: updateInvoiceTotals }); }); updateInvoiceTotals(); } function prepareNewInvoice() { document.getElementById('invoice-modal-title').textContent = 'New Invoice'; document.getElementById('invoice-form').reset(); document.getElementById('invoice-items').innerHTML = ''; document.getElementById('invoice-terms').value = 'Net 30'; document.getElementById('invoice-number').value = ''; document.getElementById('invoice-send-date').value = ''; resetItemCounter(); setDefaultDate(); // Add one default item addItem('invoice-items', { type: 'invoice', laborRate: qboLaborRate, onUpdate: updateInvoiceTotals }); } export function addInvoiceItem(item = null) { addItem('invoice-items', { item, type: 'invoice', laborRate: qboLaborRate, onUpdate: updateInvoiceTotals }); } export function updateInvoiceTotals() { const items = getItems('invoice-items'); const taxExempt = document.getElementById('invoice-tax-exempt').checked; let subtotal = 0; items.forEach(item => { const amount = parseFloat(item.amount.replace(/[$,]/g, '')) || 0; subtotal += amount; }); const taxAmount = taxExempt ? 0 : (subtotal * 8.25 / 100); const total = subtotal + taxAmount; document.getElementById('invoice-subtotal').textContent = `$${subtotal.toFixed(2)}`; document.getElementById('invoice-tax').textContent = taxExempt ? '$0.00' : `$${taxAmount.toFixed(2)}`; document.getElementById('invoice-total').textContent = `$${total.toFixed(2)}`; document.getElementById('invoice-tax-row').style.display = taxExempt ? 'none' : 'block'; } export async function handleInvoiceSubmit(e) { e.preventDefault(); const data = { invoice_number: document.getElementById('invoice-number').value || null, customer_id: document.getElementById('invoice-customer').value, invoice_date: document.getElementById('invoice-date').value, terms: document.getElementById('invoice-terms').value, auth_code: document.getElementById('invoice-authorization').value, tax_exempt: document.getElementById('invoice-tax-exempt').checked, scheduled_send_date: document.getElementById('invoice-send-date')?.value || null, bill_to_name: document.getElementById('invoice-bill-to-name')?.value || null, items: getItems('invoice-items') }; if (!data.customer_id) { alert('Please select a customer.'); return; } if (!data.items || data.items.length === 0) { alert('Please add at least one item.'); return; } const invoiceId = currentInvoiceId; const url = invoiceId ? `/api/invoices/${invoiceId}` : '/api/invoices'; const method = invoiceId ? 'PUT' : 'POST'; showSpinner(invoiceId ? 'Saving invoice & syncing QBO...' : 'Creating invoice & exporting to QBO...'); try { const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { closeInvoiceModal(); if (result.qbo_doc_number) { console.log(`✅ Invoice saved & exported to QBO: #${result.qbo_doc_number}`); } else if (result.qbo_synced) { console.log('✅ Invoice saved & synced to QBO'); } else { console.log('✅ Invoice saved locally (QBO sync pending)'); } if (window.invoiceView) window.invoiceView.loadInvoices(); } else { alert(`Error: ${result.error}`); } } catch (error) { console.error('Error:', error); alert('Error saving invoice'); } finally { hideSpinner(); } } // Wire up form submit and tax-exempt checkbox export function initInvoiceModal() { const form = document.getElementById('invoice-form'); if (form) form.addEventListener('submit', handleInvoiceSubmit); const taxExempt = document.getElementById('invoice-tax-exempt'); if (taxExempt) taxExempt.addEventListener('change', updateInvoiceTotals); } // Expose for onclick handlers window.openInvoiceModal = openInvoiceModal; window.closeInvoiceModal = closeInvoiceModal; window.addInvoiceItem = addInvoiceItem;