diff --git a/public/invoice-view.js b/public/invoice-view.js index 8d19d4c..1b504e4 100644 --- a/public/invoice-view.js +++ b/public/invoice-view.js @@ -147,7 +147,12 @@ function renderInvoiceRow(invoice) { : `Draft`; let statusBadge = ''; + const amountPaid = parseFloat(invoice.amount_paid) || 0; + const balance = parseFloat(invoice.balance) ?? (parseFloat(invoice.total) - amountPaid); + const isPartiallyPaid = !paid && amountPaid > 0 && balance > 0; + if (paid) statusBadge = `Paid`; + else if (isPartiallyPaid) statusBadge = `Partial $${amountPaid.toFixed(2)}`; else if (overdue) statusBadge = `Overdue`; // Send Date @@ -203,7 +208,12 @@ function renderInvoiceRow(invoice) { ${formatDate(invoice.invoice_date)} ${sendDateDisplay} ${invoice.terms} - $${parseFloat(invoice.total).toFixed(2)} + + ${isPartiallyPaid + ? `$${balance.toFixed(2)} $${parseFloat(invoice.total).toFixed(2)}` + : `$${parseFloat(invoice.total).toFixed(2)}` + } + ${editBtn} ${qboBtn} ${pdfBtn} ${htmlBtn} ${paidBtn} ${delBtn} diff --git a/public/payment-modal.js b/public/payment-modal.js index f2e36c0..58648e9 100644 --- a/public/payment-modal.js +++ b/public/payment-modal.js @@ -37,9 +37,12 @@ export async function openPaymentModal(invoiceIds = []) { 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: parseFloat(data.invoice.total) + payAmount: balance > 0 ? balance : total }); } } catch (e) { console.error('Error loading invoice:', id, e); } @@ -82,9 +85,13 @@ async function addInvoiceById() { 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: detailData.invoice, - payAmount: parseFloat(detailData.invoice.total) + invoice: detailInv, + payAmount: detailBalance > 0 ? detailBalance : detailTotal }); renderInvoiceList(); @@ -223,8 +230,14 @@ function renderInvoiceList() { container.innerHTML = selectedInvoices.map(si => { const inv = si.invoice; const total = parseFloat(inv.total); - const isPartial = si.payAmount < total; - const isOver = si.payAmount > total; + const amountPaid = parseFloat(inv.amount_paid) || 0; + const balance = total - amountPaid; + const isPartial = si.payAmount < balance; + const isOver = si.payAmount > balance; + + const paidInfo = amountPaid > 0 + ? `Paid: $${amountPaid.toFixed(2)}` + : ''; return `
@@ -232,6 +245,7 @@ function renderInvoiceList() { #${inv.invoice_number || 'Draft'} ${inv.customer_name || ''} (Total: $${total.toFixed(2)}) + ${paidInfo} ${isPartial ? 'Partial' : ''} ${isOver ? 'Overpay' : ''}
diff --git a/server.js b/server.js index 18befa5..15de2ed 100644 --- a/server.js +++ b/server.js @@ -403,12 +403,19 @@ app.delete('/api/quotes/:id', async (req, res) => { app.get('/api/invoices', async (req, res) => { try { const result = await pool.query(` - SELECT i.*, c.name as customer_name, c.qbo_id as customer_qbo_id + SELECT i.*, c.name as customer_name, c.qbo_id as customer_qbo_id, + COALESCE((SELECT SUM(pi.amount) FROM payment_invoices pi WHERE pi.invoice_id = i.id), 0) as amount_paid FROM invoices i LEFT JOIN customers c ON i.customer_id = c.id ORDER BY i.created_at DESC `); - res.json(result.rows); + // balance berechnen + const rows = result.rows.map(r => ({ + ...r, + amount_paid: parseFloat(r.amount_paid) || 0, + balance: (parseFloat(r.total) || 0) - (parseFloat(r.amount_paid) || 0) + })); + res.json(rows); } catch (error) { console.error('Error fetching invoices:', error); res.status(500).json({ error: 'Error fetching invoices' }); @@ -429,25 +436,30 @@ app.get('/api/invoices/next-number', async (req, res) => { app.get('/api/invoices/:id', async (req, res) => { const { id } = req.params; try { - // KORRIGIERT: c.line1, c.line2, c.line3, c.line4 statt c.street const invoiceResult = await pool.query(` - SELECT i.*, c.name as customer_name, c.line1, c.line2, c.line3, c.line4, c.city, c.state, c.zip_code, c.account_number + SELECT i.*, c.name as customer_name, c.qbo_id as customer_qbo_id, + c.line1, c.line2, c.line3, c.line4, c.city, c.state, c.zip_code, c.account_number, + COALESCE((SELECT SUM(pi.amount) FROM payment_invoices pi WHERE pi.invoice_id = i.id), 0) as amount_paid FROM invoices i LEFT JOIN customers c ON i.customer_id = c.id WHERE i.id = $1 `, [id]); - + if (invoiceResult.rows.length === 0) { return res.status(404).json({ error: 'Invoice not found' }); } - + + const invoice = invoiceResult.rows[0]; + invoice.amount_paid = parseFloat(invoice.amount_paid) || 0; + invoice.balance = (parseFloat(invoice.total) || 0) - invoice.amount_paid; + const itemsResult = await pool.query( 'SELECT * FROM invoice_items WHERE invoice_id = $1 ORDER BY item_order', [id] ); - + res.json({ - invoice: invoiceResult.rows[0], + invoice: invoice, items: itemsResult.rows }); } catch (error) { @@ -456,6 +468,9 @@ app.get('/api/invoices/:id', async (req, res) => { } }); + + + app.post('/api/invoices', async (req, res) => { const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, created_from_quote_id, scheduled_send_date } = req.body;