Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 03:03:58 +00:00
co-authored by renee-png
parent ae1d84162e
commit 167b38401b
+31 -10
View File
@@ -70,7 +70,12 @@ function InvoiceDetail() {
if (!map.has(key)) map.set(key, { caseRow: it.case, items: [], subtotal: 0 });
const g = map.get(key)!;
g.items.push(it);
g.subtotal += Number(it.amount);
// Only billable rows contribute to the case subtotal. A line is
// non-billable when it references a source time/expense entry but its
// amount is zero (the generator stores it that way for transparency).
const isNonBillable =
Number(it.amount) === 0 && (it.time_entry_id || it.expense_id);
if (!isNonBillable) g.subtotal += Number(it.amount);
}
return Array.from(map.values());
}, [items]);
@@ -205,15 +210,31 @@ function InvoiceDetail() {
caseTitle: g.caseRow?.title ?? "(unassigned)",
practiceArea: g.caseRow?.practice_area,
subtotal: g.subtotal,
items: g.items.map((it) => ({
kind: it.kind,
description: it.description,
work_date: it.work_date,
quantity: Number(it.quantity),
rate: Number(it.rate),
amount: Number(it.amount),
user_name: it.user?.full_name || it.user?.email,
})),
items: g.items.map((it) => {
const name: string =
it.user?.full_name || it.user?.email || "";
const initials = name
.trim()
.split(/\s+/)
.map((p: string) => p[0])
.filter(Boolean)
.slice(0, 3)
.join("")
.toUpperCase();
const isNonBillable =
Number(it.amount) === 0 && (it.time_entry_id || it.expense_id);
return {
kind: it.kind,
description: it.description,
work_date: it.work_date,
quantity: Number(it.quantity),
rate: Number(it.rate),
amount: Number(it.amount),
user_name: name || null,
user_initials: initials || null,
billable: !isNonBillable,
};
}),
})),
totals: {
subtotal: Number(invoice.subtotal),