fix(analytics): recognize revenue/commission on percentage-of-completion basis (fix spurious negative gross profit)
This commit is contained in:
+15
-6
@@ -31,13 +31,22 @@ export function winRate(kanbanLeads = []) {
|
|||||||
return { won, lost, decided, rate: decided ? Math.round((won / decided) * 100) : 0 };
|
return { won, lost, decided, rate: decided ? Math.round((won / decided) * 100) : 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revenue recognized = Σ client payments received (paymentSchedule status 'paid'); cost = Σ actualCost.
|
// Earned (percentage-of-completion) P&L. Revenue and commission are recognized in proportion to
|
||||||
|
// work done (completionPercentage); cost is what's actually been incurred. This is the correct
|
||||||
|
// basis for in-progress construction: the previous approach compared cash-collected client
|
||||||
|
// payments against fully-incurred cost, which made gross profit spuriously negative mid-project.
|
||||||
|
// `received` is still exposed for any cash-basis view that wants money actually collected.
|
||||||
export function revenueSummary(projects = []) {
|
export function revenueSummary(projects = []) {
|
||||||
const revenue = sum(projects, (p) =>
|
let revenue = 0, cost = 0, commissions = 0, received = 0;
|
||||||
sum((p.paymentSchedule || []).filter((ps) => ps.status === 'paid'), (ps) => ps.amount));
|
for (const p of projects) {
|
||||||
const cost = sum(projects, (p) => Number(p.actualCost ?? p.spent ?? 0));
|
const pct = Math.min(Math.max(Number(p.completionPercentage || 0) / 100, 0), 1);
|
||||||
const commissions = sum(projects, (p) => Number(p.commission || 0));
|
const budget = Number(p.approvedBudget ?? p.budget ?? 0);
|
||||||
return { revenue, cost, grossProfit: revenue - cost, commissions, netProfit: revenue - cost - commissions };
|
revenue += Math.round(pct * budget);
|
||||||
|
cost += Number(p.actualCost ?? p.spent ?? 0);
|
||||||
|
commissions += Math.round(pct * Number(p.commission || 0));
|
||||||
|
received += sum((p.paymentSchedule || []).filter((ps) => ps.status === 'paid'), (ps) => ps.amount);
|
||||||
|
}
|
||||||
|
return { revenue, received, cost, grossProfit: revenue - cost, commissions, netProfit: revenue - cost - commissions };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commission grouped by the project's sales rep. resolvePerson(id)->{name} optional, for display.
|
// Commission grouped by the project's sales rep. resolvePerson(id)->{name} optional, for display.
|
||||||
|
|||||||
Reference in New Issue
Block a user