mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix(reports): apply the trade scope to six unscoped reports
These reports resolved ctx.directions and never read it, so a user restricted to one trade direction saw every row, and a user with no trade access — where directions is [] and the rule is show nothing — saw all of them. first-last-mile-bookings scopes on the booking's own direction; the other five scope on ts.direction. global-logistics-wagons uses the fragment form so a log row whose schedule is gone stays visible, which is the rule the other ledgers apply to rows carrying no direction. Also adds the missing soft-delete guards: b.deleted_at on the first/last mile booking join, ts.deleted_at on the wagon-teu-utilization and global-logistics-wagons schedule joins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<ObjectLiteral> {
|
||||
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<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ObjectLiteral> {
|
||||
const { params } = ctx;
|
||||
const { params, directions } = ctx;
|
||||
const qb = ctx.ds
|
||||
.createQueryBuilder()
|
||||
.from(TrainSetWagon, 'tsw')
|
||||
@@ -24,6 +25,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ObjectLiteral> {
|
||||
const { params } = ctx;
|
||||
const { params, directions } = ctx;
|
||||
const qb = ctx.ds
|
||||
.createQueryBuilder()
|
||||
.from(TrainSchedule, 'ts')
|
||||
@@ -28,6 +29,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ObjectLiteral> {
|
||||
const { params } = ctx;
|
||||
const { params, directions } = ctx;
|
||||
const qb = ctx.ds
|
||||
.createQueryBuilder()
|
||||
.from(TrainSchedule, 'ts')
|
||||
@@ -22,6 +23,8 @@ function baseQuery(ctx: ReportContext): SelectQueryBuilder<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ObjectLiteral> {
|
||||
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<ObjectLiteral> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user