feat: empty container Import

This commit is contained in:
hager
2026-09-04 22:43:20 +00:00
parent 2b6c78cf71
commit 3fb5cdf29f
36 changed files with 968 additions and 49 deletions

View File

@@ -24,7 +24,12 @@ import { IRatesRepository, RATES_REPOSITORY } from '../interfaces/rates.reposito
import { IYardsRepository, YARDS_REPOSITORY } from '../interfaces/yards.repository.interface';
/** Categories priced per rail leg — they carry an origin → destination yard pair. */
const BASE_FREIGHT_CATEGORIES: readonly Rate['appliesTo'][] = ['BULK', 'CONTAINER', 'INTERCITY'];
const BASE_FREIGHT_CATEGORIES: readonly Rate['appliesTo'][] = [
'BULK',
'CONTAINER',
'EMPTY_CONTAINER',
'INTERCITY',
];
/**
* Surcharges sold per cargo kind: the admin says container or bulk, a
* container fee then names its container type and a bulk fee its commodity.
@@ -381,6 +386,30 @@ export class RatesService {
return;
}
if (appliesTo === 'EMPTY_CONTAINER') {
// Northbound repositioning only. Southbound empties are already sold by
// the WITH_RETURN surcharge and empty_return_requests; a second path to
// the same movement would let the business double-sell it.
if (tradeDirection !== 'IMPORT') {
throw new BadRequestException(
'An empty container rate is import-only for now.',
);
}
// Size is the entire scope of an empty rate — there is no cargo to narrow
// by, so the box type must be named and a commodity must not be.
if (!containerTypeId) {
throw new BadRequestException(
'An empty container rate must name the container type it covers.',
);
}
if (cargoTypeId) {
throw new BadRequestException(
'An empty container rate cannot be scoped to a bulk cargo type.',
);
}
return;
}
if (tradeDirection !== 'IMPORT' && tradeDirection !== 'EXPORT') {
throw new BadRequestException(
`${appliesTo === 'BULK' ? 'Bulk' : 'Container'} freight must be either IMPORT or EXPORT.`,