diff --git a/src/components/SubcontractorInvoiceForm.jsx b/src/components/SubcontractorInvoiceForm.jsx index 8752350..e910980 100644 --- a/src/components/SubcontractorInvoiceForm.jsx +++ b/src/components/SubcontractorInvoiceForm.jsx @@ -347,6 +347,8 @@ export default function SubcontractorInvoiceForm({ embedded = false, onCancel, o {loadingTasks ? (
Loading…
+ ) : error ? ( +
{error}
) : completedTasks.length === 0 ? (
No completed tasks available to invoice.
) : ( @@ -442,6 +444,8 @@ export default function SubcontractorInvoiceForm({ embedded = false, onCancel, o {loadingTasks ? (

Loading...

+ ) : error ? ( +

{error}

) : completedTasks.length === 0 ? (

No completed tasks available to invoice.

) : ( diff --git a/src/pages/team/TeamReports.jsx b/src/pages/team/TeamReports.jsx index 31cf2d7..45e2c00 100644 --- a/src/pages/team/TeamReports.jsx +++ b/src/pages/team/TeamReports.jsx @@ -34,9 +34,16 @@ function subBillingStatusFromInvoices(items = []) { function billingLabel(status) { if (status === 'paid') return 'Paid'; if (status === 'invoiced') return 'Invoiced'; + if (status === 'not_applicable') return 'N/A'; return 'Not Invoiced'; } +function asArray(value) { + if (Array.isArray(value)) return value; + if (value == null) return []; + return typeof value === 'object' ? [value] : []; +} + function versionTypeLabel(versionNumber) { return Number(versionNumber || 0) === 0 ? 'New' : 'Revision'; } @@ -85,6 +92,7 @@ export default function TeamReports() { { data: submissionRows, error: submissionsError }, { data: invoiceItemRows, error: invoiceItemsError }, { data: subInvoiceRows, error: subInvoicesError }, + { data: profileRows, error: profilesError }, ] = await Promise.all([ supabase .from('tasks') @@ -92,7 +100,7 @@ export default function TeamReports() { .order('title'), supabase .from('submissions') - .select('id, task_id, type, version_number, submitted_at, invoiced, description') + .select('id, task_id, type, version_number, submitted_at, invoiced, description, delivery:deliveries(sent_by)') .in('type', ['initial', 'revision']) .order('submitted_at', { ascending: true }), supabase @@ -101,15 +109,28 @@ export default function TeamReports() { supabase .from('subcontractor_invoices') .select('status, items:subcontractor_invoice_items(task_id, version_number, description)') - .in('status', ['submitted', 'approved', 'paid']) + .in('status', ['submitted', 'approved', 'paid']), + supabase + .from('profiles') + .select('name, role'), ]); if (tasksError) throw tasksError; if (submissionsError) throw submissionsError; if (invoiceItemsError) throw invoiceItemsError; if (subInvoicesError) throw subInvoicesError; + if (profilesError) throw profilesError; if (cancelled) return; + // Sub billing only applies to work an external subcontractor delivered. + // A team member's own delivery is billed to the client directly and is + // never sub-invoiced, no matter who started the task's earlier versions. + const roleByName = new Map( + (profileRows || []) + .filter((profile) => profile.name) + .map((profile) => [profile.name.trim().toLowerCase(), profile.role]) + ); + const taskMap = new Map((taskRows || []).map((task) => [task.id, task])); const companiesMap = new Map(); const projectsMap = new Map(); @@ -137,6 +158,12 @@ export default function TeamReports() { if (!entry.first_submitted_at || (submission.submitted_at && submission.submitted_at < entry.first_submitted_at)) { entry.first_submitted_at = submission.submitted_at; } + // Duplicate submissions for the same task+version can exist (double-submits); + // take the deliverer from whichever one actually has a delivery logged. + if (!entry.delivered_by) { + const deliverer = asArray(submission.delivery)[0]?.sent_by; + if (deliverer) entry.delivered_by = deliverer; + } } const invoiceItemGroups = new Map(); @@ -190,7 +217,12 @@ export default function TeamReports() { const key = `${task.id}:${Number(versionEntry.version_number || 0)}`; const invoiceItems = invoiceItemGroups.get(key) || []; const billingStatus = billingStatusFromInvoices(invoiceItems); - const subBillingStatus = subBillingStatusFromInvoices(subBillingGroups.get(key) || []); + const delivererRole = versionEntry.delivered_by + ? roleByName.get(versionEntry.delivered_by.trim().toLowerCase()) || null + : null; + const subBillingStatus = delivererRole === 'external' + ? subBillingStatusFromInvoices(subBillingGroups.get(key) || []) + : 'not_applicable'; rows.push({ key, company_id: company?.id || '',