fix recurring

This commit is contained in:
2026-05-04 14:39:31 -05:00
parent e00b748927
commit fcfc66a3b9
2 changed files with 53 additions and 7 deletions

View File

@@ -98,9 +98,20 @@ async function loadInvoiceForEdit(invoiceId) {
const recurringInterval = document.getElementById('invoice-recurring-interval'); const recurringInterval = document.getElementById('invoice-recurring-interval');
const recurringGroup = document.getElementById('invoice-recurring-group'); const recurringGroup = document.getElementById('invoice-recurring-group');
if (recurringCb) { if (recurringCb) {
recurringCb.checked = data.invoice.is_recurring || false; const isGeneratedRecurringChild = !!data.invoice.recurring_source_id;
if (recurringInterval) recurringInterval.value = data.invoice.recurring_interval || 'monthly'; const canBeRecurringMaster = !isGeneratedRecurringChild;
if (recurringGroup) recurringGroup.style.display = data.invoice.is_recurring ? 'block' : 'none';
recurringCb.checked = canBeRecurringMaster && (data.invoice.is_recurring || false);
recurringCb.disabled = !canBeRecurringMaster;
if (recurringInterval) {
recurringInterval.value = data.invoice.recurring_interval || 'monthly';
recurringInterval.disabled = !canBeRecurringMaster;
}
if (recurringGroup) {
recurringGroup.style.display = recurringCb.checked ? 'block' : 'none';
}
} }
// Load items // Load items

View File

@@ -269,11 +269,46 @@ 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]
); );
} }
const next_recurring_date = is_recurring ? calculateNextRecurringDate(invoice_date, recurring_interval) : null;
await client.query( // Preserve existing next_recurring_date when editing an already-recurring invoice.
'UPDATE invoices SET is_recurring = $1, recurring_interval = $2, next_recurring_date = $3 WHERE id = $4', // Otherwise editing an old invoice can move next_recurring_date backwards and create duplicates.
[is_recurring || false, recurring_interval || null, next_recurring_date, id] const existingRecurringResult = await client.query(
'SELECT is_recurring, next_recurring_date, recurring_source_id FROM invoices WHERE id = $1',
[id]
); );
const existingRecurring = existingRecurringResult.rows[0];
let next_recurring_date = null;
// Automatically generated child invoices should not become recurring masters.
// recurring_source_id != null means: this invoice was created from another recurring invoice.
const isGeneratedRecurringChild = !!existingRecurring?.recurring_source_id;
if (isGeneratedRecurringChild) {
next_recurring_date = null;
} else if (is_recurring) {
next_recurring_date = existingRecurring?.next_recurring_date
? existingRecurring.next_recurring_date
: calculateNextRecurringDate(invoice_date, recurring_interval);
}
await client.query(
`
UPDATE invoices
SET is_recurring = $1,
recurring_interval = $2,
next_recurring_date = $3
WHERE id = $4
`,
[
isGeneratedRecurringChild ? false : (is_recurring || false),
isGeneratedRecurringChild ? null : (recurring_interval || null),
next_recurring_date,
id
]
);
// Delete and re-insert items // Delete and re-insert items
await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]); await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]);
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {