This commit is contained in:
Marshal
2026-07-23 14:18:24 +00:00
parent 15a6bab5d0
commit 127676055b
15 changed files with 362 additions and 160 deletions

View File

@@ -194,14 +194,8 @@ export class RatesService {
}): void {
const { appliesTo, trigger, tradeDirection, intercityKind, cargoKind } = input;
const { containerTypeId, cargoTypeId } = input;
if (trigger === 'CUSTOMS_CLEARANCE' || trigger === 'LASHING') {
const label = trigger === 'CUSTOMS_CLEARANCE' ? 'customs clearance' : 'lashing';
// Customs is sold per direction; lashing applies both ways.
if (
trigger === 'CUSTOMS_CLEARANCE' &&
tradeDirection !== 'IMPORT' &&
tradeDirection !== 'EXPORT'
) {
if (trigger === 'CUSTOMS_CLEARANCE') {
if (tradeDirection !== 'IMPORT' && tradeDirection !== 'EXPORT') {
throw new BadRequestException(
'A customs clearance rate must say whether it covers IMPORT or EXPORT.',
);
@@ -211,17 +205,44 @@ export class RatesService {
// that absence is what marks it as the bulk fee.
if (cargoKind !== 'CONTAINER' && cargoKind !== 'BULK') {
throw new BadRequestException(
`A ${label} rate must say whether it covers containers or bulk.`,
'A customs clearance rate must say whether it covers containers or bulk.',
);
}
if (cargoKind === 'CONTAINER' && !containerTypeId) {
throw new BadRequestException(
`A container ${label} rate must name the container type it covers.`,
'A container customs clearance rate must name the container type it covers.',
);
}
if (cargoKind === 'BULK' && containerTypeId) {
throw new BadRequestException(
`A bulk ${label} rate cannot be scoped to a container type.`,
'A bulk customs clearance rate cannot be scoped to a container type.',
);
}
// The bulk customs fee names the commodity it covers (sugar and
// fertilizer clear differently).
if (cargoKind === 'BULK' && !cargoTypeId) {
throw new BadRequestException(
'A bulk customs clearance rate must name the bulk cargo type it covers.',
);
}
if (cargoKind === 'CONTAINER' && cargoTypeId) {
throw new BadRequestException(
'A container customs clearance rate cannot be scoped to a bulk cargo type.',
);
}
return;
}
if (trigger === 'LASHING') {
// Bulk-only cargo securing, sold per direction. May narrow to one leaf
// commodity (specific wins over the commodity-wide catch-all).
if (tradeDirection !== 'IMPORT' && tradeDirection !== 'EXPORT') {
throw new BadRequestException(
'A lashing rate must say whether it covers IMPORT or EXPORT.',
);
}
if (containerTypeId) {
throw new BadRequestException(
'Lashing is bulk-only — it cannot be scoped to a container type.',
);
}
return;
@@ -309,22 +330,27 @@ export class RatesService {
// container type — both are sold per lane (and per container type).
const isSurcharge = trigger !== 'ALWAYS';
const cargoKind =
trigger === 'CUSTOMS_CLEARANCE' || trigger === 'LASHING'
trigger === 'CUSTOMS_CLEARANCE'
? ((dto.cargoKind as 'CONTAINER' | 'BULK' | undefined) ?? null)
: null;
const containerTypeId =
trigger === 'WITH_RETURN' ||
((trigger === 'CUSTOMS_CLEARANCE' || trigger === 'LASHING') &&
cargoKind === 'CONTAINER')
(trigger === 'CUSTOMS_CLEARANCE' && cargoKind === 'CONTAINER')
? (dto.containerTypeId ?? null)
: isSurcharge
? null
: (dto.containerTypeId ?? null);
const cargoTypeId = isSurcharge ? null : (dto.cargoTypeId ?? null);
const cargoTypeId =
(trigger === 'CUSTOMS_CLEARANCE' && cargoKind === 'BULK') ||
trigger === 'LASHING'
? (dto.cargoTypeId ?? null)
: isSurcharge
? null
: (dto.cargoTypeId ?? null);
// Intercity never leaves Ethiopia, so it has no trade direction to store —
// its yard pair already says where it runs.
const tradeDirection =
trigger === 'CUSTOMS_CLEARANCE' || trigger === 'WITH_RETURN'
trigger === 'CUSTOMS_CLEARANCE' || trigger === 'WITH_RETURN' || trigger === 'LASHING'
? (dto.tradeDirection ?? null)
: isSurcharge || appliesTo === 'INTERCITY'
? null
@@ -456,7 +482,7 @@ export class RatesService {
// A patch that leaves the cargo kind unsaid keeps the one the rate already
// has — read back off its container scope (container fees carry the type).
const cargoKind =
trigger !== 'CUSTOMS_CLEARANCE' && trigger !== 'LASHING'
trigger !== 'CUSTOMS_CLEARANCE'
? null
: ((dto.cargoKind as 'CONTAINER' | 'BULK' | undefined) ??
(existing.containerTypeId ? 'CONTAINER' : 'BULK'));
@@ -464,20 +490,23 @@ export class RatesService {
const keepsContainerType =
!isSurcharge ||
trigger === 'WITH_RETURN' ||
((trigger === 'CUSTOMS_CLEARANCE' || trigger === 'LASHING') &&
cargoKind === 'CONTAINER');
(trigger === 'CUSTOMS_CLEARANCE' && cargoKind === 'CONTAINER');
const containerTypeId = !keepsContainerType
? null
: dto.containerTypeId !== undefined
? dto.containerTypeId
: existing.containerTypeId;
const cargoTypeId = isSurcharge
const keepsCargoType =
!isSurcharge ||
(trigger === 'CUSTOMS_CLEARANCE' && cargoKind === 'BULK') ||
trigger === 'LASHING';
const cargoTypeId = !keepsCargoType
? null
: dto.cargoTypeId !== undefined
? dto.cargoTypeId
: existing.cargoTypeId;
const tradeDirection =
trigger === 'CUSTOMS_CLEARANCE' || trigger === 'WITH_RETURN'
trigger === 'CUSTOMS_CLEARANCE' || trigger === 'WITH_RETURN' || trigger === 'LASHING'
? dto.tradeDirection !== undefined
? dto.tradeDirection
: existing.tradeDirection