From da0e5d5c39aa1628500c47f9d85f5c3df1757771 Mon Sep 17 00:00:00 2001 From: Krao Hasanee Date: Tue, 9 Jun 2026 22:22:18 -0500 Subject: [PATCH] fix: guard task status updates on invoice lifecycle transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent overwriting task status when task has moved to a new revision cycle after being invoiced. Now uses .eq('status', guardStatus) to only update tasks still at the expected prior state: - invoice creation: only update tasks at client_approved → invoiced - mark sent: only client_approved → invoiced - mark paid: only invoiced → paid - reopen: only paid → client_approved Co-Authored-By: Claude Sonnet 4.6 --- .claude/settings.local.json | 3 ++- src/pages/team/TeamCreateInvoice.jsx | 2 +- src/pages/team/TeamInvoiceDetail.jsx | 5 ++++- src/pages/team/TeamInvoices.jsx | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2499a1e..07025d9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -24,7 +24,8 @@ "mcp__plugin_supabase_supabase__execute_sql", "Bash(sed -n '88,100p' src/pages/Tasks.jsx)", "Bash(sed -n '570,600p' src/pages/Tasks.jsx)", - "Bash(git commit -q -m 'fix: hide counter on All tab in projects table *)" + "Bash(git commit -q -m 'fix: hide counter on All tab in projects table *)", + "Bash(git commit -q -m 'fix: guard task status updates on invoice lifecycle transitions *)" ] } } diff --git a/src/pages/team/TeamCreateInvoice.jsx b/src/pages/team/TeamCreateInvoice.jsx index 5222046..5c86a59 100644 --- a/src/pages/team/TeamCreateInvoice.jsx +++ b/src/pages/team/TeamCreateInvoice.jsx @@ -235,7 +235,7 @@ export default function CreateInvoice() { const taskIds = [...new Set(validItems.filter(i => i.task_id && !i.submission_id).map(i => i.task_id))]; if (taskIds.length > 0) { - const { error: taskError } = await supabase.from('tasks').update({ invoiced: true, status: 'invoiced' }).in('id', taskIds); + const { error: taskError } = await supabase.from('tasks').update({ invoiced: true, status: 'invoiced' }).in('id', taskIds).eq('status', 'client_approved'); if (taskError) throw taskError; } diff --git a/src/pages/team/TeamInvoiceDetail.jsx b/src/pages/team/TeamInvoiceDetail.jsx index ee348c2..b8eb15b 100644 --- a/src/pages/team/TeamInvoiceDetail.jsx +++ b/src/pages/team/TeamInvoiceDetail.jsx @@ -83,7 +83,10 @@ export function TeamInvoiceDetailPanel({ const taskIds = (freshItems || []).filter(i => i.task_id && !i.submission_id).map(i => i.task_id); if (taskIds.length > 0) { const newTaskStatus = status === 'paid' ? 'paid' : status === 'sent' ? 'invoiced' : 'client_approved'; - await supabase.from('tasks').update({ status: newTaskStatus }).in('id', taskIds); + // Guard: only update tasks still at the expected prior status so tasks + // that have moved on to a new revision cycle aren't overwritten. + const guardStatus = status === 'paid' ? 'invoiced' : status === 'sent' ? 'client_approved' : 'paid'; + await supabase.from('tasks').update({ status: newTaskStatus }).in('id', taskIds).eq('status', guardStatus); } if (status === 'paid') { try { diff --git a/src/pages/team/TeamInvoices.jsx b/src/pages/team/TeamInvoices.jsx index aeb79a3..c75c688 100644 --- a/src/pages/team/TeamInvoices.jsx +++ b/src/pages/team/TeamInvoices.jsx @@ -410,7 +410,7 @@ export default function Invoices() { const validItems = invItems.filter(i => i.description); if (validItems.length > 0) await supabase.from('invoice_items').insert(validItems.map(it => ({ invoice_id: invoice.id, task_id: it.task_id || null, submission_id: it.submission_id || null, description: it.description, quantity: Number(it.quantity) || 1, unit_price: Number(it.unit_price) || 0 }))); const taskIds = [...new Set(validItems.filter(i => i.task_id && !i.submission_id).map(i => i.task_id))]; - if (taskIds.length > 0) await supabase.from('tasks').update({ invoiced: true, status: 'invoiced' }).in('id', taskIds); + if (taskIds.length > 0) await supabase.from('tasks').update({ invoiced: true, status: 'invoiced' }).in('id', taskIds).eq('status', 'client_approved'); const subIds = [...new Set(validItems.filter(i => i.submission_id).map(i => i.submission_id))]; if (subIds.length > 0) await supabase.from('submissions').update({ invoiced: true }).in('id', subIds); if (status === 'sent') {