update
This commit is contained in:
parent
444e8555f3
commit
7ba4eef5db
|
|
@ -147,7 +147,12 @@ function renderInvoiceRow(invoice) {
|
|||
: `<span class="text-gray-400 italic text-xs">Draft</span>`;
|
||||
|
||||
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 = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-green-100 text-green-800" title="Paid ${formatDate(invoice.paid_date)}">Paid</span>`;
|
||||
else if (isPartiallyPaid) statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800" title="Paid: $${amountPaid.toFixed(2)} / Balance: $${balance.toFixed(2)}">Partial $${amountPaid.toFixed(2)}</span>`;
|
||||
else if (overdue) statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-red-100 text-red-800" title="${daysSince(invoice.invoice_date)} days">Overdue</span>`;
|
||||
|
||||
// Send Date
|
||||
|
|
@ -203,7 +208,12 @@ function renderInvoiceRow(invoice) {
|
|||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${formatDate(invoice.invoice_date)}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${sendDateDisplay}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${invoice.terms}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-semibold">$${parseFloat(invoice.total).toFixed(2)}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-semibold">
|
||||
${isPartiallyPaid
|
||||
? `<span class="text-yellow-700">$${balance.toFixed(2)}</span> <span class="text-gray-400 text-xs line-through">$${parseFloat(invoice.total).toFixed(2)}</span>`
|
||||
: `$${parseFloat(invoice.total).toFixed(2)}`
|
||||
}
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium space-x-1">
|
||||
${editBtn} ${qboBtn} ${pdfBtn} ${htmlBtn} ${paidBtn} ${delBtn}
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
? `<span class="text-green-600 text-xs ml-1">Paid: $${amountPaid.toFixed(2)}</span>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-100 last:border-0 hover:bg-gray-50">
|
||||
|
|
@ -232,6 +245,7 @@ function renderInvoiceList() {
|
|||
<span class="font-medium text-gray-900">#${inv.invoice_number || 'Draft'}</span>
|
||||
<span class="text-gray-500 text-sm ml-2 truncate">${inv.customer_name || ''}</span>
|
||||
<span class="text-gray-400 text-xs ml-2">(Total: $${total.toFixed(2)})</span>
|
||||
${paidInfo}
|
||||
${isPartial ? '<span class="text-xs text-yellow-600 ml-1 font-semibold">Partial</span>' : ''}
|
||||
${isOver ? '<span class="text-xs text-blue-600 ml-1 font-semibold">Overpay</span>' : ''}
|
||||
</div>
|
||||
|
|
|
|||
25
server.js
25
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,9 +436,10 @@ 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
|
||||
|
|
@ -441,13 +449,17 @@ app.get('/api/invoices/:id', async (req, res) => {
|
|||
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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue