mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
A station target's plan is per station AND per cargo type; the other two dimensions already carry the category inside dimension_key. update() kept whatever was stored whenever the payload omitted the key, and the admin form builds its payload from visible fields only — so switching "Plan by" away from Station left the old category behind on a row that no longer has any use for one. That row is not merely untidy. It survives the COALESCE(cargo_category,'') unique index alongside the legitimate null-category row for the same key, plannedRowsSql groups by the column, and the two plan rows then join the same operated row through the FULL OUTER JOIN: the category lists twice, each line carrying the full operated tonnage, while the summary tiles are computed separately and stay correct — so the table disagrees with its own totals and nothing says why. create() and update() now resolve the slot through one place, which is the point: the two paths cannot drift again. cargoCategory is derived from the effective dimension rather than carried over, and a station target without one is rejected instead of stored as a plan the report can never match. dimensionKey is checked against the vocabulary the reports actually emit — CARGO_CATEGORIES / CONTAINER_CLASSES, or live yards.code for stations. An unknown key used to store fine, list fine and fall back to showing the raw key, while being a plan no report would ever find. Also drops @Global from the module. Nothing outside it injects either service; the reports read both tables in raw SQL, so the docstring's stated reason for being global was not true.
80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
import { Transform } from 'class-transformer';
|
|
import {
|
|
IsDateString,
|
|
IsIn,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
MaxLength,
|
|
Min,
|
|
} from 'class-validator';
|
|
|
|
import {
|
|
TARGET_DIMENSIONS,
|
|
TARGET_METRICS,
|
|
TARGET_PERIOD_TYPES,
|
|
TargetDimension,
|
|
TargetMetric,
|
|
TargetPeriodType,
|
|
} from '../entities/operations-target.entity';
|
|
|
|
const toNumber = ({ value }: { value: unknown }) =>
|
|
value === '' || value == null ? value : Number(value);
|
|
|
|
export class CreateOperationsTargetDto {
|
|
@ApiProperty({ enum: TARGET_PERIOD_TYPES })
|
|
@IsIn(TARGET_PERIOD_TYPES as unknown as string[])
|
|
periodType!: TargetPeriodType;
|
|
|
|
@ApiProperty({
|
|
example: '2026-08-01',
|
|
description: 'Any date inside the bucket — normalised to the bucket start on write.',
|
|
})
|
|
@IsDateString()
|
|
periodStart!: string;
|
|
|
|
@ApiProperty({ enum: TARGET_METRICS })
|
|
@IsIn(TARGET_METRICS as unknown as string[])
|
|
metric!: TargetMetric;
|
|
|
|
@ApiProperty({ enum: TARGET_DIMENSIONS })
|
|
@IsIn(TARGET_DIMENSIONS as unknown as string[])
|
|
dimension!: TargetDimension;
|
|
|
|
@ApiProperty({
|
|
example: 'CONTAINER_IMPORT_MULTIMODAL',
|
|
description: 'Category key, container-class key or yard code — not a display label.',
|
|
})
|
|
@IsString()
|
|
@MaxLength(60)
|
|
dimensionKey!: string;
|
|
|
|
@ApiProperty({ example: 1200 })
|
|
@Transform(toNumber)
|
|
@IsNumber()
|
|
@Min(0)
|
|
plannedValue!: number;
|
|
|
|
@ApiPropertyOptional({
|
|
description:
|
|
'Station targets only: which cargo category this station plan covers. Ignored for the ' +
|
|
'other dimensions, whose key already carries the category.',
|
|
example: 'CONTAINER_IMPORT_MULTIMODAL',
|
|
})
|
|
@IsOptional()
|
|
// `'' ?? null` is `''`, and an empty string matches neither the unique
|
|
// index's `COALESCE(cargo_category, '')` nor the report's join — it reads as
|
|
// a category that does not exist. Blank means absent.
|
|
@Transform(({ value }) => (value === '' ? null : value))
|
|
@IsString()
|
|
@MaxLength(60)
|
|
cargoCategory?: string | null;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(500)
|
|
note?: string;
|
|
}
|