diff --git a/apps/edr-freight-api/src/modules/reports/definitions/first-last-mile-bookings.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/first-last-mile-bookings.report.ts index 06e26d246..5832e75f3 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/first-last-mile-bookings.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/first-last-mile-bookings.report.ts @@ -3,6 +3,7 @@ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { Booking } from '../../bookings/entities/booking.entity'; import { Company } from '../../companies/entities/company.entity'; import { Vehicle } from '../../vehicles/entities/vehicle.entity'; +import { applyDirectionScope } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // FirstMile and LastMile are separate tables with an identical shape (status, @@ -27,11 +28,11 @@ const STATUS_OPTIONS = [ ]; function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(LEG_UNION, 'fl') - .innerJoin(Booking, 'b', 'b.id = fl.booking_id') + .innerJoin(Booking, 'b', 'b.id = fl.booking_id AND b.deleted_at IS NULL') .leftJoin(Company, 'c', 'c.id = b.company_id') .leftJoin(Vehicle, 'v', 'v.id = fl.vehicle_id') .where('1 = 1'); @@ -41,6 +42,10 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder { if (params.dateTo) qb.andWhere('fl.created_at < :dateTo', { dateTo: params.dateTo }); const statuses = params.statuses as string[] | null; if (statuses) qb.andWhere('fl.status IN (:...statuses)', { statuses }); + + // Every leg hangs off a booking, so the trade scope is the booking's own + // direction — the same rule the booking-grain reports apply. + applyDirectionScope(qb, 'b.trade_direction', directions); return qb; } diff --git a/apps/edr-freight-api/src/modules/reports/definitions/global-logistics-wagons.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/global-logistics-wagons.report.ts index d3fd759f7..c6a8edc7a 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/global-logistics-wagons.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/global-logistics-wagons.report.ts @@ -2,22 +2,28 @@ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { ScheduleWagonAdjustmentLog } from '../../train-schedules/entities/schedule-wagon-adjustment-log.entity'; import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity'; +import { directionScopeSql } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // ADD = allocated, REMOVE = cancelled. SWITCH (a physical wagon swap, net // count unchanged) is excluded — it's neither an allocation nor a cancellation. function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(ScheduleWagonAdjustmentLog, 'l') - .leftJoin(TrainSchedule, 'ts', 'ts.id = l.train_schedule_id') + .leftJoin(TrainSchedule, 'ts', 'ts.id = l.train_schedule_id AND ts.deleted_at IS NULL') .where('l.deleted_at IS NULL') .andWhere("l.action IN ('ADD', 'REMOVE')"); if (params.dateFrom) qb.andWhere('l.occurred_at >= :dateFrom', { dateFrom: params.dateFrom }); if (params.dateTo) qb.andWhere('l.occurred_at < :dateTo', { dateTo: params.dateTo }); if (params.direction) qb.andWhere('ts.direction = :direction', { direction: params.direction }); + + // A log row whose schedule is gone carries no direction to scope by and + // stays visible — the rule the other ledgers apply to booking-less rows. + const scope = directionScopeSql('ts.direction', directions); + qb.andWhere(`(ts.id IS NULL OR ${scope.sql})`, scope.params); return qb; } diff --git a/apps/edr-freight-api/src/modules/reports/definitions/loaded-capacity.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/loaded-capacity.report.ts index 65dfada57..0c8f08c7f 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/loaded-capacity.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/loaded-capacity.report.ts @@ -3,13 +3,14 @@ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { TrainSetWagon } from '../../train-sets/entities/train-set-wagon.entity'; import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity'; import { WagonType } from '../../wagon-types/entities/wagon-type.entity'; +import { applyDirectionScope } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // train_set_wagons.assigned_weight_tons is the planned load per slot, already // maintained by the wagon-allocation flow — no need to re-derive it from // bulk/container line items. function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(TrainSetWagon, 'tsw') @@ -24,6 +25,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder { qb.andWhere('ts.scheduled_departure_date >= :dateFrom', { dateFrom: params.dateFrom }); } if (params.dateTo) qb.andWhere('ts.scheduled_departure_date < :dateTo', { dateTo: params.dateTo }); + + applyDirectionScope(qb, 'ts.direction', directions); return qb; } diff --git a/apps/edr-freight-api/src/modules/reports/definitions/train-schedule-status.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/train-schedule-status.report.ts index b81ea7896..1104d5895 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/train-schedule-status.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/train-schedule-status.report.ts @@ -2,6 +2,7 @@ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { TrainSchedule, TRAIN_SCHEDULE_STATUSES } from '../../train-schedules/entities/train-schedule.entity'; import { Yard } from '../../rule-engine/entities/yard.entity'; +import { applyDirectionScope } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // ITLMS's spec lists Scheduled/Dispatched/In Transit/Arrived/Cancelled as the @@ -11,7 +12,7 @@ import { ReportContext, ReportDefinition } from '../report.types'; const STATUS_OPTIONS = TRAIN_SCHEDULE_STATUSES.map((v) => ({ value: v, label: v })); function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(TrainSchedule, 'ts') @@ -28,6 +29,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder { if (params.direction) qb.andWhere('ts.direction = :direction', { direction: params.direction }); const statuses = params.statuses as string[] | null; if (statuses) qb.andWhere('ts.status IN (:...statuses)', { statuses }); + + applyDirectionScope(qb, 'ts.direction', directions); return qb; } diff --git a/apps/edr-freight-api/src/modules/reports/definitions/train-turnaround.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/train-turnaround.report.ts index 7dc7b8c3c..48304f054 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/train-turnaround.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/train-turnaround.report.ts @@ -2,6 +2,7 @@ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity'; import { Yard } from '../../rule-engine/entities/yard.entity'; +import { applyDirectionScope } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // "Turnaround" here is departure-to-arrival transit time on the actual (not @@ -9,7 +10,7 @@ import { ReportContext, ReportDefinition } from '../report.types'; // departure) would need pairing consecutive schedules by physical train, // which isn't tracked directly — deferred, not modeled as a shortcut. function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(TrainSchedule, 'ts') @@ -22,6 +23,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder { if (params.dateFrom) qb.andWhere('ts.actual_departure_at >= :dateFrom', { dateFrom: params.dateFrom }); if (params.dateTo) qb.andWhere('ts.actual_departure_at < :dateTo', { dateTo: params.dateTo }); if (params.direction) qb.andWhere('ts.direction = :direction', { direction: params.direction }); + + applyDirectionScope(qb, 'ts.direction', directions); return qb; } diff --git a/apps/edr-freight-api/src/modules/reports/definitions/wagon-teu-utilization.report.ts b/apps/edr-freight-api/src/modules/reports/definitions/wagon-teu-utilization.report.ts index a10df9976..3d81c06ed 100644 --- a/apps/edr-freight-api/src/modules/reports/definitions/wagon-teu-utilization.report.ts +++ b/apps/edr-freight-api/src/modules/reports/definitions/wagon-teu-utilization.report.ts @@ -5,16 +5,21 @@ import { WagonType } from '../../wagon-types/entities/wagon-type.entity'; import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity'; import { Container } from '../../container-management/entities/container.entity'; import { ContainerType } from '../../rule-engine/entities/container-type.entity'; +import { applyDirectionScope } from '../../user-trade-access/trade-scope.util'; import { ReportContext, ReportDefinition } from '../report.types'; // TEU = container size in feet / 20 (20ft -> 1 TEU, 40ft -> 2 TEU). Scoped to // each wagon's CURRENT schedule pin — a live-state view, not a historical one. function baseQuery(ctx: ReportContext): SelectQueryBuilder { - const { params } = ctx; + const { params, directions } = ctx; const qb = ctx.ds .createQueryBuilder() .from(Wagon, 'w') - .innerJoin(TrainSchedule, 'ts', 'ts.id = w.current_train_schedule_id') + .innerJoin( + TrainSchedule, + 'ts', + 'ts.id = w.current_train_schedule_id AND ts.deleted_at IS NULL', + ) .leftJoin(WagonType, 'wt', 'wt.id = w.wagon_type_id') .leftJoin(Container, 'c', 'c.wagon_id = w.id AND c.deleted_at IS NULL') .leftJoin(ContainerType, 'ct', 'ct.id = c.container_type_id') @@ -27,6 +32,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder { qb.andWhere('ts.scheduled_departure_date >= :dateFrom', { dateFrom: params.dateFrom }); } if (params.dateTo) qb.andWhere('ts.scheduled_departure_date < :dateTo', { dateTo: params.dateTo }); + + applyDirectionScope(qb, 'ts.direction', directions); return qb; }