mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
customers, contracts, invoices, payments, train-schedules, locomotives, trains and wagons. 319 fields across the nine datasets, all reusing the existing engine — no change to export.types.ts was needed, which is the result the bookings-first phase was meant to test. Per-dataset notes worth keeping: - trains resolves route, stations and current yard, which the list endpoint never loads — the UI shows raw FK uuids there today. - wagons reads tare/payload/length off wagon_types (they are not on the wagon), and reproduces the service's attachStatusDates() as correlated subqueries. wagon_status_logs stores from_status/to_status, not status. - payments applies no soft-delete guard: freight.payments has neither deleted_at nor updated_at, so the usual predicate is a 42703. Failure columns are failer_code/failer_message. payment_refunds stores MINOR units, so refundedTotal divides by 100. - train-schedules derives freightType from the bookings aboard rather than a column, matching the list service. - customers stays one row per company; profiles, bookings and invoice totals aggregate in subqueries. Verified no row multiplication: trains, customers and contracts each return exactly their counted row count while selecting one-to-many aggregate fields. EXPLAIN-validated against the database: every dataset's widest query, its count query, and all 319 fields individually. That run caught five columns typed varchar rather than timestamp (companies.date_registered, renewal_date, renewed_from, renewed_to and invoices.eims_ack_date), which were being pushed through to_char and would have 500'd the moment anyone ticked them; they now export verbatim. All nine count endpoints verified equal to SELECT count(*) on their table.
111 lines
6.8 KiB
TypeScript
111 lines
6.8 KiB
TypeScript
import { FREIGHT_PERMS } from '../../../seed/freight-permissions.registry';
|
|
import { Yard } from '../../rule-engine/entities/yard.entity';
|
|
import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity';
|
|
import { Train } from '../../trains/entities/train.entity';
|
|
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
|
|
import { Wagon } from '../../wagons/entities/wagon.entity';
|
|
import { ExportDataset } from '../export.types';
|
|
|
|
/**
|
|
* Two traps this dataset works around:
|
|
*
|
|
* 1. Tare weight, payload and length live on WAGON_TYPE, not wagon — which is
|
|
* why they carry `requires: ['wt']` and their sortExpr points at `wt`.
|
|
* 2. `lastMaintenanceAt` / `lastAvailableAt` come from a grouped query over
|
|
* wagon_status_logs in the service (`attachStatusDates`). Ported here as
|
|
* correlated subqueries so they compose with everything else and cost
|
|
* nothing when unticked. Note the log columns are `from_status`/`to_status`,
|
|
* not `status`.
|
|
*/
|
|
export const wagonsDataset: ExportDataset = {
|
|
key: 'wagons',
|
|
title: 'Wagons',
|
|
description: 'Wagon fleet with type specs, location, train assignment and status history',
|
|
group: 'Fleet',
|
|
permission: FREIGHT_PERMS.wagons.view,
|
|
base: { entity: Wagon, alias: 'w' },
|
|
|
|
joins: [
|
|
{ alias: 'wt', entity: WagonType, on: 'wt.id = w.wagon_type_id' },
|
|
{ alias: 'y', entity: Yard, on: 'y.id = w.current_yard_id' },
|
|
{ alias: 't', entity: Train, on: 't.id = w.train_id' },
|
|
{ alias: 'sch', entity: TrainSchedule, on: 'sch.id = w.current_train_schedule_id' },
|
|
],
|
|
|
|
groups: [
|
|
{ id: 'wagon', label: 'Wagon' },
|
|
{ id: 'type', label: 'Type & specs' },
|
|
{ id: 'location', label: 'Location' },
|
|
{ id: 'assignment', label: 'Assignment' },
|
|
{ id: 'history', label: 'Status history' },
|
|
],
|
|
|
|
fields: [
|
|
{ key: 'wagonNumber', label: 'Wagon number', type: 'string', group: 'wagon', default: true, select: 'w.wagon_number', sortExpr: 'w.wagon_number' },
|
|
{ key: 'status', label: 'Status', type: 'string', group: 'wagon', default: true, select: 'w.status', sortExpr: 'w.status' },
|
|
{ key: 'sequenceNumber', label: 'Sequence no.', type: 'number', group: 'wagon', select: 'w.sequence_number' },
|
|
{ key: 'notes', label: 'Notes', type: 'string', group: 'wagon', select: 'w.notes' },
|
|
{ key: 'createdAt', label: 'Added', type: 'date', group: 'wagon', select: `to_char(w.created_at, 'YYYY-MM-DD')`, sortExpr: 'w.created_at' },
|
|
|
|
{ key: 'wagonType', label: 'Type', type: 'string', group: 'type', default: true, requires: ['wt'], select: 'wt.name', sortExpr: 'wt.name' },
|
|
{ key: 'wagonTypeCode', label: 'Type code', type: 'string', group: 'type', requires: ['wt'], select: 'wt.code' },
|
|
{ key: 'capacityTons', label: 'Capacity (t)', type: 'tons', group: 'type', default: true, requires: ['wt'], select: 'wt.capacity_tons::float8', sortExpr: 'wt.capacity_tons' },
|
|
{ key: 'tareWeightTons', label: 'Tare weight (t)', type: 'tons', group: 'type', requires: ['wt'], select: 'wt.tare_weight_tons::float8' },
|
|
{ key: 'lengthMeters', label: 'Length (m)', type: 'number', group: 'type', requires: ['wt'], select: 'wt.length_meters::float8' },
|
|
{ key: 'equatedLengthM', label: 'Equated length (m)', type: 'number', group: 'type', requires: ['wt'], select: 'wt.equated_length_m::float8' },
|
|
{ key: 'maxContainerGrossT', label: 'Max container gross (t)', type: 'tons', group: 'type', requires: ['wt'], select: 'wt.max_container_gross_t::float8' },
|
|
{ key: 'supportsContainer', label: 'Supports container', type: 'boolean', group: 'type', requires: ['wt'], select: 'wt.supports_container' },
|
|
{ key: 'supportedLoadTypes', label: 'Supported load types', type: 'string', group: 'type', requires: ['wt'], select: 'wt.supported_load_types::text' },
|
|
|
|
{ key: 'currentYard', label: 'Current yard', type: 'string', group: 'location', default: true, requires: ['y'], select: 'y.label' },
|
|
{ key: 'currentYardCode', label: 'Current yard code', type: 'string', group: 'location', requires: ['y'], select: 'y.code' },
|
|
{ key: 'currentYardCountry', label: 'Current yard country', type: 'string', group: 'location', requires: ['y'], select: 'y.country' },
|
|
|
|
{ key: 'trainCode', label: 'Train', type: 'string', group: 'assignment', default: true, requires: ['t'], select: 't.code' },
|
|
{ key: 'trainNumber', label: 'Train number', type: 'string', group: 'assignment', requires: ['t'], select: 't.train_number' },
|
|
{ key: 'exportTrainNumber', label: 'Export run', type: 'string', group: 'assignment', select: 'w.export_train_number' },
|
|
{ key: 'importTrainNumber', label: 'Import run', type: 'string', group: 'assignment', select: 'w.import_train_number' },
|
|
{ key: 'scheduleReference', label: 'Current schedule', type: 'string', group: 'assignment', requires: ['sch'], select: 'sch.reference' },
|
|
{ key: 'scheduleDeparture', label: 'Schedule departure', type: 'date', group: 'assignment', requires: ['sch'], select: `to_char(sch.scheduled_departure_date, 'YYYY-MM-DD')` },
|
|
|
|
{
|
|
key: 'lastMaintenanceAt', label: 'Last maintenance', type: 'datetime', group: 'history',
|
|
select: `(SELECT to_char(MAX(l.created_at), 'YYYY-MM-DD HH24:MI')
|
|
FROM freight.wagon_status_logs l
|
|
WHERE l.wagon_id = w.id AND l.to_status = 'MAINTENANCE' AND l.deleted_at IS NULL)`,
|
|
},
|
|
{
|
|
key: 'lastAvailableAt', label: 'Last available', type: 'datetime', group: 'history',
|
|
select: `(SELECT to_char(MAX(l.created_at), 'YYYY-MM-DD HH24:MI')
|
|
FROM freight.wagon_status_logs l
|
|
WHERE l.wagon_id = w.id AND l.to_status = 'AVAILABLE' AND l.deleted_at IS NULL)`,
|
|
},
|
|
{
|
|
key: 'statusChangeCount', label: 'Status changes', type: 'number', group: 'history',
|
|
select: `(SELECT COUNT(*)::int FROM freight.wagon_status_logs l
|
|
WHERE l.wagon_id = w.id AND l.deleted_at IS NULL)`,
|
|
},
|
|
],
|
|
|
|
filters: [
|
|
{ key: 'status', label: 'Status', type: 'text' },
|
|
{ key: 'wagonTypeId', label: 'Wagon type', type: 'text' },
|
|
{ key: 'currentYardId', label: 'Current yard', type: 'text' },
|
|
{ key: 'trainId', label: 'Train', type: 'text' },
|
|
{ key: 'search', label: 'Search wagon number', type: 'text' },
|
|
],
|
|
|
|
defaultSort: { key: 'wagonNumber', dir: 'ASC' },
|
|
|
|
scope(ctx, qb) {
|
|
const { params } = ctx;
|
|
qb.andWhere('w.deleted_at IS NULL');
|
|
if (params.status) qb.andWhere('w.status = :status', { status: params.status });
|
|
if (params.wagonTypeId) qb.andWhere('w.wagon_type_id = :wagonTypeId', { wagonTypeId: params.wagonTypeId });
|
|
if (params.currentYardId) qb.andWhere('w.current_yard_id = :currentYardId', { currentYardId: params.currentYardId });
|
|
if (params.trainId) qb.andWhere('w.train_id = :trainId', { trainId: params.trainId });
|
|
if (params.search) qb.andWhere('w.wagon_number ILIKE :search', { search: `%${params.search as string}%` });
|
|
// Wagons carry no trade direction — nothing to scope. Intentional.
|
|
},
|
|
};
|