fix
This commit is contained in:
@@ -42,6 +42,67 @@ function applyCustomerTaxStatus(customerId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateRecurringChildUi(invoice = null) {
|
||||||
|
const recurringCb = document.getElementById('invoice-recurring');
|
||||||
|
if (!recurringCb) return;
|
||||||
|
|
||||||
|
const recurringWrapper = recurringCb.closest('label') || recurringCb.parentElement;
|
||||||
|
if (!recurringWrapper) return;
|
||||||
|
|
||||||
|
let labelText = document.getElementById('invoice-recurring-label-text');
|
||||||
|
let childBadge = document.getElementById('invoice-recurring-child-badge');
|
||||||
|
let childNote = document.getElementById('invoice-recurring-child-note');
|
||||||
|
|
||||||
|
// First-time setup: wrap/change the visible label text
|
||||||
|
if (!labelText) {
|
||||||
|
const textNode = Array.from(recurringWrapper.childNodes).find(node =>
|
||||||
|
node.nodeType === Node.TEXT_NODE && node.textContent.trim().includes('Recurring')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (textNode) {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.id = 'invoice-recurring-label-text';
|
||||||
|
span.textContent = 'Recurring';
|
||||||
|
recurringWrapper.replaceChild(span, textNode);
|
||||||
|
labelText = span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childBadge) {
|
||||||
|
childBadge = document.createElement('span');
|
||||||
|
childBadge.id = 'invoice-recurring-child-badge';
|
||||||
|
childBadge.className = 'ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800 hidden';
|
||||||
|
childBadge.textContent = 'Child invoice';
|
||||||
|
recurringWrapper.appendChild(childBadge);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childNote) {
|
||||||
|
childNote = document.createElement('div');
|
||||||
|
childNote.id = 'invoice-recurring-child-note';
|
||||||
|
childNote.className = 'mt-1 ml-8 text-xs text-gray-500 hidden';
|
||||||
|
recurringWrapper.parentElement.appendChild(childNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isChild = !!invoice?.recurring_source_id;
|
||||||
|
|
||||||
|
if (isChild) {
|
||||||
|
labelText.textContent = 'Recurring child invoice';
|
||||||
|
childBadge.classList.remove('hidden');
|
||||||
|
|
||||||
|
const sourceNumber = invoice.recurring_source_invoice_number
|
||||||
|
|| invoice.source_invoice_number
|
||||||
|
|| invoice.recurring_source_id;
|
||||||
|
|
||||||
|
childNote.textContent = `This invoice was generated from recurring invoice #${sourceNumber} and will not create further recurring invoices.`;
|
||||||
|
childNote.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
labelText.textContent = 'Recurring';
|
||||||
|
childBadge.classList.add('hidden');
|
||||||
|
childNote.classList.add('hidden');
|
||||||
|
childNote.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function openInvoiceModal(invoiceId = null) {
|
export async function openInvoiceModal(invoiceId = null) {
|
||||||
currentInvoiceId = invoiceId;
|
currentInvoiceId = invoiceId;
|
||||||
if (invoiceId) {
|
if (invoiceId) {
|
||||||
@@ -112,6 +173,9 @@ async function loadInvoiceForEdit(invoiceId) {
|
|||||||
if (recurringGroup) {
|
if (recurringGroup) {
|
||||||
recurringGroup.style.display = recurringCb.checked ? 'block' : 'none';
|
recurringGroup.style.display = recurringCb.checked ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make recurring child invoices obvious in the UI
|
||||||
|
updateRecurringChildUi(data.invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load items
|
// Load items
|
||||||
@@ -136,6 +200,11 @@ function prepareNewInvoice() {
|
|||||||
const recurringGroup = document.getElementById('invoice-recurring-group');
|
const recurringGroup = document.getElementById('invoice-recurring-group');
|
||||||
if (recurringCb) recurringCb.checked = false;
|
if (recurringCb) recurringCb.checked = false;
|
||||||
if (recurringGroup) recurringGroup.style.display = 'none';
|
if (recurringGroup) recurringGroup.style.display = 'none';
|
||||||
|
|
||||||
|
const recurringInterval = document.getElementById('invoice-recurring-interval');
|
||||||
|
if (recurringCb) recurringCb.disabled = false;
|
||||||
|
if (recurringInterval) recurringInterval.disabled = false;
|
||||||
|
updateRecurringChildUi(null);
|
||||||
|
|
||||||
resetItemCounter();
|
resetItemCounter();
|
||||||
setDefaultDate();
|
setDefaultDate();
|
||||||
|
|||||||
@@ -84,11 +84,30 @@ router.get('/:id', async (req, res) => {
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
try {
|
try {
|
||||||
const invoiceResult = await pool.query(`
|
const invoiceResult = await pool.query(`
|
||||||
SELECT i.*, c.name as customer_name, c.qbo_id as customer_qbo_id,
|
SELECT
|
||||||
c.email, c.line1, c.line2, c.line3, c.line4, c.city, c.state, c.zip_code, c.account_number,
|
i.*,
|
||||||
COALESCE((SELECT SUM(pi.amount) FROM payment_invoices pi WHERE pi.invoice_id = i.id), 0) as amount_paid
|
c.name as customer_name,
|
||||||
|
c.qbo_id as customer_qbo_id,
|
||||||
|
c.email,
|
||||||
|
c.line1,
|
||||||
|
c.line2,
|
||||||
|
c.line3,
|
||||||
|
c.line4,
|
||||||
|
c.city,
|
||||||
|
c.state,
|
||||||
|
c.zip_code,
|
||||||
|
c.account_number,
|
||||||
|
src.invoice_number AS recurring_source_invoice_number,
|
||||||
|
COALESCE((
|
||||||
|
SELECT SUM(pi.amount)
|
||||||
|
FROM payment_invoices pi
|
||||||
|
WHERE pi.invoice_id = i.id
|
||||||
|
), 0) as amount_paid
|
||||||
FROM invoices i
|
FROM invoices i
|
||||||
LEFT JOIN customers c ON i.customer_id = c.id
|
LEFT JOIN customers c
|
||||||
|
ON i.customer_id = c.id
|
||||||
|
LEFT JOIN invoices src
|
||||||
|
ON src.id = i.recurring_source_id
|
||||||
WHERE i.id = $1
|
WHERE i.id = $1
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
@@ -269,7 +288,7 @@ router.put('/:id', async (req, res) => {
|
|||||||
[customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, id]
|
[customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve existing next_recurring_date when editing an already-recurring invoice.
|
// Preserve existing next_recurring_date when editing an already-recurring invoice.
|
||||||
// Otherwise editing an old invoice can move next_recurring_date backwards and create duplicates.
|
// Otherwise editing an old invoice can move next_recurring_date backwards and create duplicates.
|
||||||
const existingRecurringResult = await client.query(
|
const existingRecurringResult = await client.query(
|
||||||
|
|||||||
Reference in New Issue
Block a user