// payment-modal.js ā ES Module v3 (clean) // Invoice payments: multi-invoice, partial, overpay // No downpayment functionality let bankAccounts = []; let paymentMethods = []; let selectedInvoices = []; // { invoice, payAmount } let dataLoaded = false; // ============================================================ // Load QBO Data // ============================================================ async function loadQboData() { if (dataLoaded) return; try { const [accRes, pmRes] = await Promise.all([ fetch('/api/qbo/accounts'), fetch('/api/qbo/payment-methods') ]); if (accRes.ok) bankAccounts = await accRes.json(); if (pmRes.ok) paymentMethods = await pmRes.json(); dataLoaded = true; } catch (e) { console.error('Error loading QBO data:', e); } } // ============================================================ // Open / Close // ============================================================ export async function openPaymentModal(invoiceIds = []) { await loadQboData(); selectedInvoices = []; for (const id of invoiceIds) { try { const res = await fetch(`/api/invoices/${id}`); const data = await res.json(); if (data.invoice) { const total = parseFloat(data.invoice.total); const amountPaid = parseFloat(data.invoice.amount_paid) || 0; const balance = total - amountPaid; selectedInvoices.push({ invoice: data.invoice, payAmount: balance > 0 ? balance : total }); } } catch (e) { console.error('Error loading invoice:', id, e); } } ensureModalElement(); renderModalContent(); document.getElementById('payment-modal').classList.add('active'); } export function closePaymentModal() { const modal = document.getElementById('payment-modal'); if (modal) modal.classList.remove('active'); selectedInvoices = []; } // ============================================================ // Add / Remove Invoices // ============================================================ async function addInvoiceById() { const input = document.getElementById('payment-add-invoice-id'); const searchVal = input.value.trim(); if (!searchVal) return; try { const res = await fetch('/api/invoices'); const allInvoices = await res.json(); const match = allInvoices.find(inv => String(inv.id) === searchVal || String(inv.invoice_number) === searchVal ); if (!match) { alert(`No invoice with #/ID "${searchVal}" found.`); return; } if (!match.qbo_id) { alert('This invoice has not been exported to QBO yet.'); return; } if (match.paid_date) { alert('This invoice is already paid.'); return; } if (selectedInvoices.find(si => si.invoice.id === match.id)) { alert('Invoice already in list.'); return; } if (selectedInvoices.length > 0 && match.customer_id !== selectedInvoices[0].invoice.customer_id) { alert('All invoices must belong to the same customer.'); return; } const detailRes = await fetch(`/api/invoices/${match.id}`); const detailData = await detailRes.json(); const detailInv = detailData.invoice; const detailTotal = parseFloat(detailInv.total); const detailPaid = parseFloat(detailInv.amount_paid) || 0; const detailBalance = detailTotal - detailPaid; selectedInvoices.push({ invoice: detailInv, payAmount: detailBalance > 0 ? detailBalance : detailTotal }); renderInvoiceList(); updateTotal(); input.value = ''; } catch (e) { console.error('Error adding invoice:', e); alert('Error searching for invoice.'); } } function removeInvoice(invoiceId) { selectedInvoices = selectedInvoices.filter(si => si.invoice.id !== invoiceId); renderInvoiceList(); updateTotal(); } function updatePayAmount(invoiceId, newAmount) { const si = selectedInvoices.find(s => s.invoice.id === invoiceId); if (si) { si.payAmount = Math.max(0, parseFloat(newAmount) || 0); } renderInvoiceList(); updateTotal(); } // ============================================================ // DOM // ============================================================ function ensureModalElement() { let modal = document.getElementById('payment-modal'); if (!modal) { modal = document.createElement('div'); modal.id = 'payment-modal'; modal.className = 'modal fixed inset-0 bg-black bg-opacity-50 z-50 justify-center items-start pt-10 overflow-y-auto'; document.body.appendChild(modal); } } function renderModalContent() { const modal = document.getElementById('payment-modal'); if (!modal) return; const accountOptions = bankAccounts.map(a => ``).join(''); const filtered = paymentMethods.filter(p => /check|ach/i.test(p.name)); const methods = filtered.length > 0 ? filtered : paymentMethods; const methodOptions = methods.map(p => ``).join(''); const today = new Date().toISOString().split('T')[0]; modal.innerHTML = `