mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
integrate global logistics staff user into seeder
refactor pricing data seeder to fold surcharge types into rates update route meta subtitle to remove surcharge types enhance RuleEngineFormDialog to support conditional field visibility remove surcharge types from URL constants and related services add cargo leaf options query for bulk cargo type selection update RuleEngineResourcePage to utilize cargo leaf options modify resources configuration to remove surcharge types implement migration to fold surcharge types into rates create utility to derive legacy rate types from new rate structure
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import type { RateAppliesTo, RateTrigger, RateType } from './rate.entity';
|
||||
|
||||
/**
|
||||
* Derive the legacy `rateType` string from the friendly form fields.
|
||||
*
|
||||
* `rateType` is still the key the pricing engine uses to look up base rail
|
||||
* freight (CONTAINER_IMPORT, BULK_EXPORT, …) and what gets snapshotted on a
|
||||
* booking. The configuration UI no longer asks for it directly — the admin
|
||||
* picks `appliesTo` + `tradeDirection` (+ `trigger` for surcharges) and we map
|
||||
* that to the canonical rateType here so both layers stay in agreement.
|
||||
*/
|
||||
export function deriveRateType(input: {
|
||||
appliesTo: RateAppliesTo;
|
||||
trigger: RateTrigger;
|
||||
tradeDirection?: string | null;
|
||||
/** Whether a bulk cargo (vs a container) was selected — disambiguates intercity. */
|
||||
isBulk?: boolean;
|
||||
}): RateType {
|
||||
const { appliesTo, trigger, tradeDirection, isBulk } = input;
|
||||
|
||||
// Surcharges (trigger ≠ ALWAYS) map to their dedicated rateType.
|
||||
if (trigger !== 'ALWAYS') {
|
||||
switch (trigger) {
|
||||
case 'HAZARDOUS':
|
||||
return 'HAZARD_SURCHARGE';
|
||||
case 'REEFER':
|
||||
return 'REEFER_SURCHARGE';
|
||||
case 'OVERWEIGHT':
|
||||
return 'OVERWEIGHT_PER_TON';
|
||||
case 'SHIPPING_LINE':
|
||||
return 'DOUBLE_HANDLING';
|
||||
case 'CONSOLIDATION':
|
||||
return 'LASHING';
|
||||
case 'CANCELLATION':
|
||||
return 'CANCELLATION_FEE';
|
||||
case 'DEMURRAGE':
|
||||
return 'DEMURRAGE';
|
||||
case 'PIL_EXTRA_FEE':
|
||||
return 'PIL_EXTRA_FEE';
|
||||
}
|
||||
}
|
||||
|
||||
// Base freight (trigger = ALWAYS) maps by category + direction.
|
||||
const isExport = tradeDirection === 'EXPORT';
|
||||
switch (appliesTo) {
|
||||
case 'CONTAINER':
|
||||
return isExport ? 'CONTAINER_EXPORT' : 'CONTAINER_IMPORT';
|
||||
case 'BULK':
|
||||
return isExport ? 'BULK_EXPORT' : 'BULK_IMPORT';
|
||||
case 'INTERCITY':
|
||||
// Intercity has no trade direction; container vs bulk decided by which
|
||||
// scope field was filled (cargoTypeId → bulk, containerTypeId → container).
|
||||
return isBulk ? 'INTERCITY_BULK' : 'INTERCITY_CONTAINER';
|
||||
case 'FIRST_MILE':
|
||||
return 'FIRST_MILE';
|
||||
case 'LAST_MILE':
|
||||
return 'LAST_MILE';
|
||||
default:
|
||||
return 'CANCELLATION_FEE';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { CargoType } from './cargo-type.entity';
|
||||
import { ContainerType } from './container-type.entity';
|
||||
|
||||
export const RATE_TYPES = [
|
||||
@@ -27,18 +28,72 @@ export type RateType = typeof RATE_TYPES[number];
|
||||
export const RATE_STATUSES = ['DRAFT', 'PENDING_APPROVAL', 'LIVE', 'SUPERSEDED'] as const;
|
||||
export type RateStatus = typeof RATE_STATUSES[number];
|
||||
|
||||
export const RATE_UNITS = ['PER_WAGON', 'PER_TON', 'PER_CONTAINER', 'PER_KM', 'FLAT'] as const;
|
||||
export const RATE_UNITS = [
|
||||
'PER_WAGON',
|
||||
'PER_TON',
|
||||
'PER_CONTAINER',
|
||||
'PER_KM',
|
||||
'PER_INVOICE',
|
||||
'FLAT',
|
||||
] as const;
|
||||
export type RateUnit = typeof RATE_UNITS[number];
|
||||
|
||||
/**
|
||||
* Friendly, admin-facing category that determines how the rate is used in
|
||||
* pricing and which fields the rate form shows. Replaces the cryptic
|
||||
* `rateType` matrix for the configuration UI (rateType is still persisted and
|
||||
* derived from `appliesTo` + `tradeDirection` + `trigger` for base-freight
|
||||
* lookup and snapshots).
|
||||
*
|
||||
* - BULK / CONTAINER / INTERCITY : base rail freight (trigger = ALWAYS)
|
||||
* - FIRST_MILE / LAST_MILE : pickup / delivery legs
|
||||
* - OTHER : trigger-based surcharges (hazard, reefer …)
|
||||
*/
|
||||
export const RATE_APPLIES_TO = [
|
||||
'BULK',
|
||||
'CONTAINER',
|
||||
'INTERCITY',
|
||||
'FIRST_MILE',
|
||||
'LAST_MILE',
|
||||
'OTHER',
|
||||
] as const;
|
||||
export type RateAppliesTo = typeof RATE_APPLIES_TO[number];
|
||||
|
||||
/**
|
||||
* What makes a rate apply to a booking. `ALWAYS` is base freight (matched by
|
||||
* direction + container/bulk scope). Everything else is a surcharge that the
|
||||
* rule engine adds on top, additively, when the booking matches the trigger —
|
||||
* so hazard stacks on container/bulk with each line's own unit.
|
||||
*/
|
||||
export const RATE_TRIGGERS = [
|
||||
'ALWAYS',
|
||||
'HAZARDOUS',
|
||||
'OVERWEIGHT',
|
||||
'REEFER',
|
||||
'SHIPPING_LINE',
|
||||
'CONSOLIDATION',
|
||||
'CANCELLATION',
|
||||
'DEMURRAGE',
|
||||
'PIL_EXTRA_FEE',
|
||||
] as const;
|
||||
export type RateTrigger = typeof RATE_TRIGGERS[number];
|
||||
|
||||
@Entity({ schema: 'freight', name: 'rates' })
|
||||
@Index(['rateType'])
|
||||
@Index(['status'])
|
||||
@Index(['effectiveFrom'])
|
||||
@Index(['containerTypeId'])
|
||||
@Index(['trigger'])
|
||||
export class Rate extends BaseEntity {
|
||||
@Column({ name: 'rate_type', type: 'varchar', length: 50 })
|
||||
rateType!: RateType;
|
||||
|
||||
@Column({ name: 'applies_to', type: 'varchar', length: 20, default: 'OTHER' })
|
||||
appliesTo!: RateAppliesTo;
|
||||
|
||||
@Column({ name: 'trigger', type: 'varchar', length: 20, default: 'ALWAYS' })
|
||||
trigger!: RateTrigger;
|
||||
|
||||
@Column({ name: 'container_type_id', type: 'uuid', nullable: true })
|
||||
containerTypeId?: string | null;
|
||||
|
||||
@@ -46,6 +101,13 @@ export class Rate extends BaseEntity {
|
||||
@JoinColumn({ name: 'container_type_id' })
|
||||
containerType?: ContainerType | null;
|
||||
|
||||
@Column({ name: 'cargo_type_id', type: 'uuid', nullable: true })
|
||||
cargoTypeId?: string | null;
|
||||
|
||||
@ManyToOne(() => CargoType, { nullable: true, eager: false })
|
||||
@JoinColumn({ name: 'cargo_type_id' })
|
||||
cargoType?: CargoType | null;
|
||||
|
||||
@Column({ name: 'trade_direction', type: 'varchar', length: 10, nullable: true })
|
||||
tradeDirection?: string | null;
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { Rate } from './rate.entity';
|
||||
|
||||
const TRIGGER_CONDITIONS = [
|
||||
'CARGO_FLAG_HAZARDOUS',
|
||||
'CARGO_FLAG_REEFER',
|
||||
'VGM_EXCEEDS_LIMIT',
|
||||
'SHIPPING_LINE_MAPPED',
|
||||
'CONSOLIDATION_ENABLED',
|
||||
] as const;
|
||||
|
||||
export type TriggerCondition = typeof TRIGGER_CONDITIONS[number];
|
||||
|
||||
@Entity({ schema: 'freight', name: 'surcharge_types' })
|
||||
@Index(['code'])
|
||||
@Index(['isActive'])
|
||||
@Index(['rateId'])
|
||||
export class SurchargeType extends BaseEntity {
|
||||
@Column({ name: 'code', type: 'varchar', length: 40, unique: true })
|
||||
code!: string;
|
||||
|
||||
@Column({ name: 'label', type: 'varchar', length: 100, nullable: true })
|
||||
label!: string;
|
||||
|
||||
@Column({ name: 'trigger_condition', type: 'varchar', length: 50, nullable: true })
|
||||
triggerCondition!: TriggerCondition;
|
||||
|
||||
@Column({ name: 'rate_id', type: 'uuid', nullable: true })
|
||||
rateId!: string;
|
||||
|
||||
@ManyToOne(() => Rate, { eager: false })
|
||||
@JoinColumn({ name: 'rate_id' })
|
||||
rate?: Rate;
|
||||
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive!: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user