mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 13:28:11 +00:00
feat(bookings): filter by container count, export per-type quantities
Container filters on the booking-requests list:
- "Container type" — bookings carrying that type.
- "Containers" — a count of BOXES (booking_container is one row per line with
a quantity, so this sums quantity rather than counting rows), as an exact
value or a range. It reads the container-type filter when one is set, so the
one control answers both "10 containers in total" and "10 forty-footers".
Export gains a column per container type ("20FT containers", "40FT
containers"), plus the total "Containers" column and the two filters. Container
types are reference rows, not a constant, so `ExportDataset` gains an optional
`dynamicFields` resolver — DB-driven columns appended to the static list and
cached for the process, mirroring the existing `ExportFilterDef.optionsQuery`.
Adding a 45ft container type adds its column with no code change. The type id
is interpolated into raw SQL (ExportField.select has no parameter bag), so the
resolver drops any id that is not a uuid.
Also repoints the export's "Container VGM" column at the per-line sum. It was
projecting bookings.cargo_total_weight_vgm, which the portal wizard leaves at 0
for container freight — the same trap the tonnage fix addressed — so the column
read 0 for every portal-created container booking. Non-zero on dev data goes
from 54 to 170 of 208 container bookings.
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import {
|
||||
CARGO_TYPE_SUBTREE_SQL,
|
||||
bookingContainerCountSql,
|
||||
bookingContainerVgmSql,
|
||||
bookingContentMatchSql,
|
||||
bookingContentSql,
|
||||
bookingHasContainerTypeSql,
|
||||
} from './booking-content.sql';
|
||||
|
||||
describe('bookingContentSql', () => {
|
||||
@@ -62,3 +65,44 @@ describe('bookingContentMatchSql', () => {
|
||||
expect(sql.trimEnd().endsWith(')')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bookingContainerCountSql', () => {
|
||||
// booking_container is one row per LINE carrying a quantity, so counting rows
|
||||
// would report a 54-container booking as 1.
|
||||
it('sums the line quantities rather than counting lines', () => {
|
||||
expect(bookingContainerCountSql('b')).toContain('SUM(bc.quantity)');
|
||||
expect(bookingContainerCountSql('b')).not.toContain('COUNT(');
|
||||
});
|
||||
|
||||
it('counts every type by default and one type when scoped', () => {
|
||||
expect(bookingContainerCountSql('b')).not.toContain('container_type_id');
|
||||
expect(bookingContainerCountSql('b', true)).toContain(
|
||||
'bc.container_type_id = :containerTypeId',
|
||||
);
|
||||
});
|
||||
|
||||
it('is 0, never NULL, so a bound comparison still decides', () => {
|
||||
expect(bookingContainerCountSql('b')).toContain('COALESCE(SUM(bc.quantity), 0)');
|
||||
});
|
||||
|
||||
it('ignores soft-deleted lines', () => {
|
||||
expect(bookingContainerCountSql('b')).toContain('bc.deleted_at IS NULL');
|
||||
expect(bookingHasContainerTypeSql('b')).toContain('bc.deleted_at IS NULL');
|
||||
});
|
||||
|
||||
it('rewrites the booking reference under another alias', () => {
|
||||
expect(bookingContainerCountSql('bk')).toContain('bc.booking_id = bk.id');
|
||||
expect(bookingHasContainerTypeSql('bk')).toContain('bc.booking_id = bk.id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bookingContainerVgmSql', () => {
|
||||
// The whole point: b.cargo_total_weight_vgm is 0 for portal container
|
||||
// bookings, so the weight has to come off the lines.
|
||||
it('reads the lines, never the booking-level column', () => {
|
||||
const sql = bookingContainerVgmSql('b');
|
||||
expect(sql).toContain('SUM(bc.total_vgm_tons)');
|
||||
expect(sql).not.toContain('cargo_total_weight_vgm');
|
||||
expect(sql).toContain('bc.deleted_at IS NULL');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user