/** * Seeds the wagon-allocation test bookings into edr_dev (idempotent) and * prints their ids. Rows are kept — they are the evidence for the report. * * References are prefixed WGT- (Wagon Gate Test) so they are easy to find: * SELECT * FROM freight.bookings WHERE reference LIKE 'WGT-%'; */ const { Client } = require('pg'); const YARD = { DORALEH_FREEZONE: 'fc558b95-da28-4fc3-8348-311a290c34ae', DIRE_DAWA: 'f7b1686f-d43e-42aa-bc6a-d3d5849296c9', KALITY: '61ae1e66-c229-4dcd-9851-b2b9424f3a95', DMP: '7b658678-ce2b-41aa-8dc5-b38ba4a76e9b', }; const CARGO = { PERISHABLE: 'a5991d3a-d690-4b7e-98fd-ea3333aa16e7', // NW5 30T / PW2 20T STEEL_BILLET: '8291ccc3-0dd9-4d78-aa44-284d028a19ce', // NW5 40T / PW2 10T (inverted) BEANS: '9afd8eb3-975b-4ea7-a7db-04eb2c20a04d', // PW2 only, no cap SAND: '8942884d-9991-42bb-87f7-a70930f8c43c', // CW3/CW4 only, no cap }; const CONTAINER = { '20FT': '77cf24ec-e74b-4bdb-b1cc-6f336379cc58', '40FT': '349072e7-8a90-4c03-b682-08976abfd7e8', }; const COMPANY = '300d5510-e3a5-4858-bc28-6e3beda8ca80'; /** Test bookings: 4 corridors, bulk + container, contested and uncontested. */ const BOOKINGS = [ // --- Case A: DORALEH_FREEZONE -> KALITY (full leg), bulk vs container contest { ref: 'WGT-A1', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'PERISHABLE', tons: 695, note: 'A: 695T Perishable, contends with containers for NW5' }, { ref: 'WGT-A2', dir: 'IMPORT', type: 'CONTAINER', from: 'DORALEH_FREEZONE', to: 'KALITY', containers: [{ type: '40FT', qty: 12, vgm: 26 }], note: 'A: 12x40ft, needs 12 container wagons' }, { ref: 'WGT-A3', dir: 'IMPORT', type: 'CONTAINER', from: 'DORALEH_FREEZONE', to: 'KALITY', containers: [{ type: '20FT', qty: 16, vgm: 12 }], note: 'A: 16x20ft = 8 wagons (TEU paired)' }, // --- Case B: sub-corridor legs on the same 3-stop route (leg overlap) { ref: 'WGT-B1', dir: 'IMPORT', type: 'CONTAINER', from: 'DORALEH_FREEZONE', to: 'DIRE_DAWA', containers: [{ type: '20FT', qty: 6, vgm: 14 }], note: 'B: leg 1 only (DCT->Dire), 3 wagons' }, { ref: 'WGT-B2', dir: 'IMPORT', type: 'BULK', from: 'DIRE_DAWA', to: 'KALITY', cargo: 'PERISHABLE', tons: 120, note: 'B: leg 2 only (Dire->Kality) - may reuse leg-1 wagons' }, // --- Case C: cap inversion - Steel Billet is CHEAPER on NW5 (40T) than PW2 (10T) { ref: 'WGT-C1', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'STEEL_BILLET', tons: 400, note: 'C: inverted caps - must NOT blindly take PW2' }, // --- Case D: exclusive-type cargo (Beans=PW2 only, Sand=CW3/CW4 only) { ref: 'WGT-D1', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'BEANS', tons: 200, note: 'D: PW2-only cargo, no alternative' }, { ref: 'WGT-D2', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'SAND', tons: 300, note: 'D: CW3/CW4-only cargo' }, // --- Case E: overload one leg (way beyond any train) { ref: 'WGT-E1', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'PERISHABLE', tons: 3000, note: 'E: 3000T - must overflow the loco pull limit' }, { ref: 'WGT-E2', dir: 'IMPORT', type: 'BULK', from: 'DORALEH_FREEZONE', to: 'KALITY', cargo: 'PERISHABLE', tons: 45, note: 'E: small bulk - must NOT share a wagon with A1' }, ]; async function main() { const client = new Client({ host: process.env.DB_HOST || '10.18.7.207', port: Number(process.env.DB_PORT || 5432), database: process.env.DB_NAME || 'edr_dev', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'dcba@1234', }); await client.connect(); const out = []; for (const b of BOOKINGS) { const isBulk = b.type === 'BULK'; const totalVgm = isBulk ? b.tons : b.containers.reduce((s, l) => s + l.qty * l.vgm, 0); const existing = await client.query( `SELECT id FROM freight.bookings WHERE reference = $1`, [b.ref], ); let id; if (existing.rows.length) { id = existing.rows[0].id; await client.query( `UPDATE freight.bookings SET cargo_total_weight_vgm = $2, cargo_type_id = $3, origin_yard_id = $4, destination_yard_id = $5, freight_type = $6, trade_direction = $7, status = 'CLEARANCE_READY', payment_status = 'PENDING', scheduling_status = 'ELIGIBLE', train_schedule_id = NULL, cargo_free_text = $8, updated_at = now() WHERE id = $1`, [id, totalVgm, isBulk ? CARGO[b.cargo] : null, YARD[b.from], YARD[b.to], b.type, b.dir, b.note], ); } else { const res = await client.query( `INSERT INTO freight.bookings ( reference, status, total_amount, payment_status, contract_type, trade_direction, equipment_return, cargo_total_weight_vgm, is_hazardous, payment_currency, version_number, priority_score, origin_yard_id, destination_yard_id, cargo_type_id, cargo_free_text, freight_type, scheduling_status, is_government, customs_clearing_enabled, is_reefer, booking_type, is_split, company_id, created_at, updated_at ) VALUES ($1,'CLEARANCE_READY',0,'PENDING','NEW',$2,'WITHOUT_RETURN',$3, false,'ETB',1,0,$4,$5,$6,$7,$8,'ELIGIBLE',false, false,false,'ONE_TIME',false,$9, now(), now()) RETURNING id`, [b.ref, b.dir, totalVgm, YARD[b.from], YARD[b.to], isBulk ? CARGO[b.cargo] : null, b.note, b.type, COMPANY], ); id = res.rows[0].id; } // Container lines await client.query(`DELETE FROM freight.booking_container WHERE booking_id = $1`, [id]); if (!isBulk) { for (const line of b.containers) { await client.query( `INSERT INTO freight.booking_container (booking_id, container_type_id, quantity, vgm_per_unit_tons, total_vgm_tons, wagons_required, container_size, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7, now(), now())`, [id, CONTAINER[line.type], line.qty, line.vgm, line.qty * line.vgm, line.type === '40FT' ? line.qty : Math.ceil(line.qty / 2), line.type === '40FT' ? '40ft' : '20ft'], ); } } out.push({ ref: b.ref, id, note: b.note }); } console.table(out); await client.end(); } main().catch((err) => { console.error(err.message); process.exit(1); });