mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix: revenue by customer
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ObjectLiteral, SelectQueryBuilder } from "typeorm";
|
||||
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||
|
||||
import { ReportContext, ReportColumn, ReportDefinition } from "../report.types";
|
||||
import { ReportContext, ReportColumn, ReportDefinition } from '../report.types';
|
||||
import {
|
||||
PAID_SHARE,
|
||||
PAYER_EXPR,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
REVENUE_SUM,
|
||||
currencyOf,
|
||||
revenueLedgerQb,
|
||||
} from "../revenue-classification";
|
||||
} from '../revenue-classification';
|
||||
|
||||
/**
|
||||
* One column per payment class, pivoted with FILTER. The class values are the
|
||||
@@ -19,16 +19,14 @@ import {
|
||||
*/
|
||||
const CLASS_COLUMNS = PAYMENT_CLASSES.map((c) => ({
|
||||
value: c.value,
|
||||
key: c.value
|
||||
.toLowerCase()
|
||||
.replace(/_(.)/g, (_, ch: string) => ch.toUpperCase()),
|
||||
key: c.value.toLowerCase().replace(/_(.)/g, (_, ch: string) => ch.toUpperCase()),
|
||||
label: c.label,
|
||||
}));
|
||||
|
||||
const classMoneyColumns: ReportColumn[] = CLASS_COLUMNS.map((c) => ({
|
||||
key: c.key,
|
||||
label: c.label,
|
||||
type: "money",
|
||||
type: 'money',
|
||||
sortable: true,
|
||||
}));
|
||||
|
||||
@@ -37,46 +35,43 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder<ObjectLiteral> {
|
||||
}
|
||||
|
||||
export const revenueByCustomerReport: ReportDefinition = {
|
||||
key: "revenue-by-customer",
|
||||
title: "Revenue by Customer",
|
||||
key: 'revenue-by-customer',
|
||||
title: 'Revenue by Customer',
|
||||
description:
|
||||
"Every paying customer on one row: total billed revenue, what they have settled, " +
|
||||
"what is still open, and a column per charge type — rail transport, customs " +
|
||||
"clearance, first/last mile, overweight, cancellation, demurrage, storage, loading " +
|
||||
"and unloading, and additional charges. Built on invoice lines, so the charge-type " +
|
||||
"split is the billed one; a booking total is a lump sum and cannot be split. The " +
|
||||
"payer is the company or, for shipping-line credit invoices, the shipping line. " +
|
||||
"There is no dedicated loading/unloading charge type in the system — handling, " +
|
||||
"double-handling and lashing stand in for it.",
|
||||
group: "Finance",
|
||||
'Every paying customer on one row: total billed revenue, what they have settled, ' +
|
||||
'what is still open, and a column per charge type — rail transport, customs ' +
|
||||
'clearance, first/last mile, overweight, cancellation, demurrage, storage, loading ' +
|
||||
'and unloading, and additional charges. Built on invoice lines, so the charge-type ' +
|
||||
'split is the billed one; a booking total is a lump sum and cannot be split. The ' +
|
||||
'payer is the company or, for shipping-line credit invoices, the shipping line. ' +
|
||||
'There is no dedicated loading/unloading charge type in the system — handling, ' +
|
||||
'double-handling and lashing stand in for it.',
|
||||
group: 'Finance',
|
||||
filters: REVENUE_FILTERS,
|
||||
columns: [
|
||||
{
|
||||
key: "customer",
|
||||
label: "Customer",
|
||||
type: "string",
|
||||
key: 'customer',
|
||||
label: 'Customer',
|
||||
type: 'string',
|
||||
sortable: true,
|
||||
sortExpr: PAYER_EXPR,
|
||||
},
|
||||
{ key: "revenue", label: "Total revenue", type: "money", sortable: true },
|
||||
{ key: "paid", label: "Paid", type: "money", sortable: true },
|
||||
{ key: "outstanding", label: "Outstanding", type: "money", sortable: true },
|
||||
{ key: 'revenue', label: 'Total revenue', type: 'money', sortable: true },
|
||||
{ key: 'paid', label: 'Paid', type: 'money', sortable: true },
|
||||
{ key: 'outstanding', label: 'Outstanding', type: 'money', sortable: true },
|
||||
...classMoneyColumns,
|
||||
{ key: "invoices", label: "Invoices", type: "number", sortable: true },
|
||||
{ key: 'invoices', label: 'Invoices', type: 'number', sortable: true },
|
||||
],
|
||||
defaultSort: { key: "revenue", dir: "DESC" },
|
||||
chart: { type: "bar", x: "customer", y: ["revenue"] },
|
||||
drill: { to: "revenue-transactions", carry: { customer: "customer" } },
|
||||
defaultSort: { key: 'revenue', dir: 'DESC' },
|
||||
chart: { type: 'bar', x: 'customer', y: ['revenue'] },
|
||||
drill: { to: 'revenue-transactions', carry: { customer: 'customer' } },
|
||||
query(ctx) {
|
||||
const qb = baseQuery(ctx)
|
||||
.select(PAYER_EXPR, "customer")
|
||||
.addSelect(REVENUE_SUM, "revenue")
|
||||
.addSelect(`ROUND(COALESCE(SUM(${PAID_SHARE}), 0))::float8`, "paid")
|
||||
.addSelect(
|
||||
`ROUND(COALESCE(SUM(il.amount - (${PAID_SHARE})), 0))::float8`,
|
||||
"outstanding",
|
||||
)
|
||||
.addSelect("COUNT(DISTINCT i.id)::int", "invoices")
|
||||
.select(PAYER_EXPR, 'customer')
|
||||
.addSelect(REVENUE_SUM, 'revenue')
|
||||
.addSelect(`ROUND(COALESCE(SUM(${PAID_SHARE}), 0))::float8`, 'paid')
|
||||
.addSelect(`ROUND(COALESCE(SUM(il.amount - (${PAID_SHARE})), 0))::float8`, 'outstanding')
|
||||
.addSelect('COUNT(DISTINCT i.id)::int', 'invoices')
|
||||
.groupBy(PAYER_EXPR);
|
||||
|
||||
for (const c of CLASS_COLUMNS) {
|
||||
@@ -89,18 +84,18 @@ export const revenueByCustomerReport: ReportDefinition = {
|
||||
},
|
||||
async summary(ctx) {
|
||||
const row = await baseQuery(ctx)
|
||||
.select(`COUNT(DISTINCT ${PAYER_EXPR})::int`, "customers")
|
||||
.addSelect(REVENUE_SUM, "revenue")
|
||||
.addSelect(`ROUND(COALESCE(SUM(${PAID_SHARE}), 0))::float8`, "paid")
|
||||
.select(`COUNT(DISTINCT ${PAYER_EXPR})::int`, 'customers')
|
||||
.addSelect(REVENUE_SUM, 'revenue')
|
||||
.addSelect(`ROUND(COALESCE(SUM(${PAID_SHARE}), 0))::float8`, 'paid')
|
||||
.getRawOne<{ customers: number; revenue: number; paid: number }>();
|
||||
const revenue = Number(row?.revenue ?? 0);
|
||||
const paid = Number(row?.paid ?? 0);
|
||||
const unit = currencyOf(ctx.params);
|
||||
return [
|
||||
{ label: "Customers", value: Number(row?.customers ?? 0) },
|
||||
{ label: "Total revenue", value: revenue, unit },
|
||||
{ label: "Paid", value: paid, unit },
|
||||
{ label: "Outstanding", value: Math.round(revenue - paid), unit },
|
||||
{ label: 'Customers', value: Number(row?.customers ?? 0) },
|
||||
{ label: 'Total revenue', value: revenue, unit },
|
||||
{ label: 'Paid', value: paid, unit },
|
||||
{ label: 'Outstanding', value: Math.round(revenue - paid), unit },
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user