mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 22:18:12 +00:00
Merge pull request #782 from Tria-plc/freight_feature/usermanagement
add lashing surcharge for cargo types with hasLashing flag
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Add a global "booking close offset" — how long BEFORE departure a schedule's
|
||||
* booking window shuts — configurable separately for import and export.
|
||||
*
|
||||
* When an offset is set, the window's close instant is `departure − offset`
|
||||
* (e.g. departure 17:00 with a 3-hour import offset closes at 14:00; departure
|
||||
* Jul-10 16:00 with a 1-day export offset closes Jul-9 16:00). It caps the whole
|
||||
* booking lifecycle: the first window close, every reopen cycle, and the export
|
||||
* FCFS close all land at/at-or-before this cutoff instead of at departure.
|
||||
*
|
||||
* NULL / 0 preserves the previous behaviour exactly (import closes at
|
||||
* open+duration clamped to departure; export closes at departure), so existing
|
||||
* installs are unaffected until an offset is entered.
|
||||
*
|
||||
* `*_close_offset_minutes` on the global-rules singleton is the live config; the
|
||||
* matching `rule_*_close_offset_minutes` snapshot on each schedule freezes it at
|
||||
* creation so the batch board keeps drawing the window the customer was shown
|
||||
* even after a later global-rules edit. Both are nullable with no backfill —
|
||||
* absent means "no offset", the safe default.
|
||||
*/
|
||||
export class AddBookingCloseOffset2330000000000 implements MigrationInterface {
|
||||
name = "AddBookingCloseOffset2330000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_scheduling_global_rules
|
||||
ADD COLUMN IF NOT EXISTS import_close_offset_minutes integer,
|
||||
ADD COLUMN IF NOT EXISTS export_close_offset_minutes integer;
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
ADD COLUMN IF NOT EXISTS rule_import_close_offset_minutes integer,
|
||||
ADD COLUMN IF NOT EXISTS rule_export_close_offset_minutes integer;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
DROP COLUMN IF EXISTS rule_import_close_offset_minutes,
|
||||
DROP COLUMN IF EXISTS rule_export_close_offset_minutes;
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_scheduling_global_rules
|
||||
DROP COLUMN IF EXISTS import_close_offset_minutes,
|
||||
DROP COLUMN IF EXISTS export_close_offset_minutes;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Add `has_lashing` to cargo types.
|
||||
*
|
||||
* When true, every booking of that cargo type incurs the flat LASHING
|
||||
* surcharge (a rate with trigger = 'LASHING'). Defaults to false so existing
|
||||
* cargo ships without the fee until the flag is turned on.
|
||||
*/
|
||||
export class AddCargoTypeHasLashing2340000000000 implements MigrationInterface {
|
||||
name = "AddCargoTypeHasLashing2340000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.cargo_types
|
||||
ADD COLUMN IF NOT EXISTS has_lashing boolean NOT NULL DEFAULT false;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.cargo_types
|
||||
DROP COLUMN IF EXISTS has_lashing;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Add an opt-in "reverse wagon order" flag to a train schedule.
|
||||
*
|
||||
* When true, the built wagon plan is flipped at build time so the physically-last
|
||||
* wagon sits at position 1. Only the order (sequence_no) changes — composition and
|
||||
* booking allocations travel with their slot. The flag is frozen on the schedule
|
||||
* at creation and re-applied every time the wagon plan is rebuilt, so the stored
|
||||
* train order and the schedule order always match.
|
||||
*
|
||||
* Defaults to false; existing schedules keep their as-built order.
|
||||
*/
|
||||
export class AddReverseWagonOrder2340000000000 implements MigrationInterface {
|
||||
name = "AddReverseWagonOrder2340000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
ADD COLUMN IF NOT EXISTS reverse_wagon_order boolean NOT NULL DEFAULT false;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
DROP COLUMN IF EXISTS reverse_wagon_order;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
import { CONTRACT_TEMPLATE_DEFAULTS } from '../seed/data/contract-template-defaults';
|
||||
|
||||
/**
|
||||
* Refresh the "pricing" article of each seeded contract template so it points
|
||||
* at the live Rate Schedule instead of hardcoded price figures (USD 400/wagon,
|
||||
* USD 919/40ft, …). The original CreateContractTemplates migration seeded the
|
||||
* old prose with ON CONFLICT DO NOTHING, so those figures are frozen in the DB
|
||||
* rows and would otherwise contradict the rate-config-driven schedule table now
|
||||
* rendered under the pricing article.
|
||||
*
|
||||
* Only the article whose id = 'pricing' is touched, and only when its body
|
||||
* still matches the originally-seeded prose — so any admin edit to the pricing
|
||||
* article is left untouched. Idempotent: re-running is a no-op once refreshed.
|
||||
*/
|
||||
export class RefreshContractPricingArticles2350000000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const seed of CONTRACT_TEMPLATE_DEFAULTS) {
|
||||
const pricing = seed.articles.find((article) => article.id === 'pricing');
|
||||
if (!pricing) continue;
|
||||
|
||||
// jsonb_set the title + body of the element whose id = 'pricing', matched
|
||||
// by array index. Guarded so admin-edited bodies are never overwritten.
|
||||
await queryRunner.query(
|
||||
`
|
||||
UPDATE freight.contract_templates ct
|
||||
SET articles = (
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN elem->>'id' = 'pricing'
|
||||
THEN elem || jsonb_build_object('title', $2::text, 'body', $3::text)
|
||||
ELSE elem
|
||||
END
|
||||
)
|
||||
FROM jsonb_array_elements(ct.articles) elem
|
||||
)
|
||||
WHERE ct.code = $1
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(ct.articles) e
|
||||
WHERE e->>'id' = 'pricing'
|
||||
AND e->>'body' LIKE ANY (ARRAY[
|
||||
'%USD 59.4 per metric ton%',
|
||||
'%USD 696 (six hundred ninety-six) per wagon%',
|
||||
'%USD 400 (four hundred) per wagon%',
|
||||
'%From SGTD to Dire Dawa dry port, the rate is USD 919%',
|
||||
'%Railway transportation charges from GMP to SGTD: USD 819%',
|
||||
'%prevailing EDR domestic container tariff, as set out in the commercial schedule%'
|
||||
])
|
||||
);
|
||||
`,
|
||||
[seed.code, pricing.title, pricing.body],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// No-op: the refreshed pricing prose is the correct forward state; reverting
|
||||
// to hardcoded figures would reintroduce the rate-schedule contradiction.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user