fix(reports): count container tonnage in exports and reports

Every SQL tonnage in the export datasets and report definitions used
`COALESCE(b.bulk_total_weight_tons, b.cargo_total_weight_vgm)`. COALESCE
falls through on NULL, never on 0 — and the portal booking wizard stores
`cargo_total_weight_vgm = 0` for container freight on purpose, because VGM
is captured per container line, not as a booking-level figure. So every
portal-created container booking reported as weighing nothing. The
backoffice wizard does store a booking-level total, so the same table holds
both shapes and the numbers looked erratic rather than uniformly zero.

Extract the resolver the TypeScript side already has three copies of
(bookingCargoTons, cargoTonsAndItems, totalVgmTons) into one SQL helper:
NULLIF both booking-level columns, then fall back to
SUM(booking_container.total_vgm_tons). Applied to the bookings and
train-schedules export datasets, the cargo-summary, contract-utilization and
booking-status-breakdown reports, and the intercity booking list.

On dev data this recovers 116 of 154 zero-weight container bookings and
raises live booking tonnage from 42,973 t to 61,424 t.
This commit is contained in:
Nathnael
2026-08-28 09:20:46 +00:00
parent a6b2519136
commit 0e9cfbce66
8 changed files with 71 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
import { BookingStatus } from '@edr/types';
import { bookingTonsSql } from '../../bookings/booking-tons.sql';
import { Booking } from '../../bookings/entities/booking.entity';
import { Yard } from '../../rule-engine/entities/yard.entity';
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
@@ -9,7 +10,7 @@ import { ReportContext, ReportDefinition } from '../report.types';
// One resolver behind "Booking per status, per port/train/date/cargo/contract
// type" — the same breakdown Operation, Marketing, Global Logistics and the
// Operation Report each ask for verbatim. Embed once, reuse everywhere.
const TONS = 'COALESCE(b.bulk_total_weight_tons, b.cargo_total_weight_vgm)';
const TONS = bookingTonsSql('b');
const REVENUE = 'COALESCE(b.adjusted_total_amount, b.total_amount)';
const STATUS_OPTIONS = [...new Set(Object.values(BookingStatus))].map((v) => ({

View File

@@ -1,9 +1,10 @@
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
import { bookingTonsSql } from '../../bookings/booking-tons.sql';
import { Booking } from '../../bookings/entities/booking.entity';
import { ReportContext, ReportDefinition } from '../report.types';
const TONS = 'COALESCE(b.bulk_total_weight_tons, b.cargo_total_weight_vgm)';
const TONS = bookingTonsSql('b');
const NOT_UMBRELLA = "(b.contract_kind IS NULL OR b.contract_kind <> 'GENERAL')";
const DEAD_STATUSES = ['DRAFT', 'CANCELLED', 'REJECTED', 'EXPIRED'];

View File

@@ -1,10 +1,11 @@
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
import { bookingTonsSql } from '../../bookings/booking-tons.sql';
import { Company } from '../../companies/entities/company.entity';
import { Contract } from '../../contracts/entities/contract.entity';
import { ReportContext, ReportDefinition } from '../report.types';
const TONS = 'COALESCE(b.bulk_total_weight_tons, b.cargo_total_weight_vgm)';
const TONS = bookingTonsSql('b');
const DEAD_STATUSES = ['DRAFT', 'CANCELLED', 'REJECTED', 'EXPIRED'];
function baseQuery(ctx: ReportContext): SelectQueryBuilder<ObjectLiteral> {