Merge pull request #391 from Tria-plc/freight/feat/script

Freight/feat/script
This commit is contained in:
Nathnael Wondisha
2026-07-01 14:39:03 +03:00
committed by GitHub
7 changed files with 1637 additions and 98 deletions

View File

@@ -32,7 +32,8 @@
"iam:migration:revert": "pnpm run iam:typeorm:cli migration:revert",
"iam:migration:show": "pnpm run iam:typeorm:cli migration:show",
"iam:seed:run": "cross-env APP_MODULE_PATH=./dist/app.module dotenv -- node ./node_modules/@tria-plc/iamapi-common/dist/db/seed.cli.js",
"migrate": "ts-node -r tsconfig-paths/register src/scripts/run-migrations.ts"
"migrate": "ts-node -r tsconfig-paths/register src/scripts/run-migrations.ts",
"script": "ts-node -r tsconfig-paths/register src/scripts/main.ts"
},
"dependencies": {
"@edr/api-common": "workspace:*",
@@ -84,13 +85,15 @@
"@types/node": "^20.14.0",
"@types/pg": "^8.6.7",
"@types/supertest": "^6.0.2",
"@types/vorpal": "^1.12.8",
"jest": "^29.7.0",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"vorpal": "^1.12.0"
},
"jest": {
"moduleFileExtensions": [

View File

@@ -0,0 +1,10 @@
import type Vorpal from "vorpal";
import type { CommandContext } from "./types";
import { registerSeedTestContracts } from "./seed-test-contracts.cmd";
import { registerSeedTestSchedules } from "./seed-test-schedules.cmd";
export function registerCommands(vorpal: Vorpal, ctx: CommandContext): void {
registerSeedTestContracts(vorpal, ctx);
registerSeedTestSchedules(vorpal, ctx);
}

View File

@@ -0,0 +1,358 @@
import type Vorpal from "vorpal";
import { DataSource } from "typeorm";
import type { CommandContext } from "./types";
import { Company, CompanyType, CompanyKind, CompanyStatus, CompanyNationality } from "../../modules/companies/entities/company.entity";
import { CompanyProfile, ProfileStatus, ProfileType } from "../../modules/companies/entities/company-profile.entity";
import { ExternalProfile } from "../../modules/companies/entities/external-profile.entity";
import { Yard } from "../../modules/rule-engine/entities/yard.entity";
import { ServiceType } from "../../modules/rule-engine/entities/service-type.entity";
import { CargoType } from "../../modules/rule-engine/entities/cargo-type.entity";
import { Rate } from "../../modules/rule-engine/entities/rate.entity";
import { Contract } from "../../modules/contracts/entities/contract.entity";
import { ContractRoute } from "../../modules/contracts/entities/contract-route.entity";
import { ContractCargoScope } from "../../modules/contracts/entities/contract-cargo-scope.entity";
import { ContractRateSnapshot } from "../../modules/contracts/entities/contract-rate-snapshot.entity";
const COMPANY_IDS = [
"a0000001-0000-4000-8000-000000000001",
"a0000001-0000-4000-8000-000000000002",
"a0000001-0000-4000-8000-000000000003",
"a0000001-0000-4000-8000-000000000004",
];
const PROFILE_IDS = [
"b0000001-0000-4000-8000-000000000001",
"b0000001-0000-4000-8000-000000000002",
"b0000001-0000-4000-8000-000000000003",
"b0000001-0000-4000-8000-000000000004",
"b0000001-0000-4000-8000-000000000005",
"b0000001-0000-4000-8000-000000000006",
"b0000001-0000-4000-8000-000000000007",
"b0000001-0000-4000-8000-000000000008",
];
const EXTERNAL_PROFILE_IDS = [
"c0000001-0000-4000-8000-000000000001",
"c0000001-0000-4000-8000-000000000002",
"c0000001-0000-4000-8000-000000000003",
"c0000001-0000-4000-8000-000000000004",
];
function generateRef(index: number): string {
return `TST-CTR-${String(index).padStart(5, "0")}`;
}
export function registerSeedTestContracts(
vorpal: Vorpal,
ctx: CommandContext,
): void {
vorpal
.command("seed:test-contracts", "Generate test contracts with companies and all deps")
.option("-n, --count <n>", "Number of contracts to create (default: 4)")
.option("--status <statuses>", "Comma-separated contract statuses (default: DRAFT,SUBMITTED,APPROVED,CONTRACT_ACTIVE)")
.option("--freight <types>", "Freight types: CONTAINER,BULK (default: both)")
.option("--direction <dirs>", "Trade directions: IMPORT,EXPORT (default: both)")
.option("--company <name>", "Only create contracts for company matching name/TIN")
.action(async function (this: any, args: any) {
const { app } = ctx;
const ds = app.get(DataSource);
const count = Math.max(1, Math.min(20, parseInt(args.options?.count ?? "4", 10)));
const statusList = (args.options?.status ?? "DRAFT,SUBMITTED,APPROVED,CONTRACT_ACTIVE")
.split(",").map((s: string) => s.trim()).filter(Boolean);
const freightList = (args.options?.freight ?? "CONTAINER,BULK")
.split(",").map((s: string) => s.toUpperCase().trim())
.filter((s: string) => s === "CONTAINER" || s === "BULK");
const directionList = (args.options?.direction ?? "IMPORT,EXPORT")
.split(",").map((s: string) => s.toUpperCase().trim())
.filter((s: string) => s === "IMPORT" || s === "EXPORT");
const companyFilter = args.options?.company as string | undefined;
if (freightList.length === 0 || directionList.length === 0) {
this.log("error: at least one freight type and trade direction required");
return;
}
this.log(`Seeding ${count} contracts (statuses=${statusList.join(",")}, freight=${freightList.join(",")}, dir=${directionList.join(",")})...`);
const yards = await ds.getRepository(Yard).find({ where: { isActive: true } });
const yardByCode = new Map(yards.map((y) => [y.code, y]));
const djibouti = yardByCode.get("DJIBOUTI");
const addis = yardByCode.get("ADDIS_ABABA");
if (!djibouti || !addis) {
this.log("error: need at least DJIBOUTI and ADDIS_ABABA yards seeded");
return;
}
const serviceTypes = await ds
.getRepository(ServiceType)
.find({ where: { isActive: true } });
const stByCode = new Map(serviceTypes.map((st) => [st.code, st]));
const railContainer = stByCode.get("RAIL_CONTAINER");
const railBulk = stByCode.get("RAIL_BULK");
if (!railContainer && !railBulk) {
this.log("error: need at least RAIL_CONTAINER or RAIL_BULK service type seeded");
return;
}
const cargoTypes = await ds
.getRepository(CargoType)
.find({ where: { isActive: true } });
const cargoByCode = new Map(cargoTypes.map((c) => [c.code, c]));
const grain = cargoByCode.get("GRAIN");
const sugar = cargoByCode.get("SUGAR");
const fertilizer = cargoByCode.get("FERTILIZER");
const rates = await ds.getRepository(Rate).find({ where: { status: "LIVE" } });
const companyRepo = ds.getRepository(Company);
let companies = await companyRepo.find({});
if (companyFilter) {
companies = companies.filter(
(c) =>
c.name.toLowerCase().includes(companyFilter.toLowerCase()) ||
c.tin.includes(companyFilter),
);
}
if (companies.length === 0) {
this.log("No existing companies found — seeding test companies...");
companies = await seedTestCompanies(ds, (msg) => this.log(msg));
} else {
this.log(`Using ${companies.length} existing companies from DB`);
}
const contractRepo = ds.getRepository(Contract);
let contractCount = 0;
for (let i = 0; i < count; i++) {
const statusIdx = i % statusList.length;
const ftIdx = i % freightList.length;
const dirIdx = i % directionList.length;
const companyIdx = i % companies.length;
const status = statusList[statusIdx];
const freightType = freightList[ftIdx];
const direction = directionList[dirIdx];
const company = companies[companyIdx];
const profile = await ds.getRepository(CompanyProfile).findOne({
where: {
companyId: company.id,
type: direction === "IMPORT" ? ProfileType.importer : ProfileType.exporter,
},
});
if (!profile) continue;
const ref = generateRef(i + 1);
const exists = await contractRepo.findOne({ where: { reference: ref } });
if (exists) {
this.log(` Contract ${ref} already exists — skipping`);
contractCount++;
continue;
}
const serviceTypeId =
freightType === "BULK" && railBulk
? railBulk.id
: railContainer
? railContainer.id
: serviceTypes[0].id;
const originId = direction === "IMPORT" ? djibouti.id : addis.id;
const destId = direction === "IMPORT" ? addis.id : djibouti.id;
const contract = contractRepo.create({
reference: ref,
companyId: company.id,
companyProfileId: profile.id,
contractKind: "ONE_TIME" as const,
tradeDirection: direction,
freightType,
serviceTypeId,
paymentCurrency: "USD",
customsClearingEnabled: false,
equipmentReturn: "without_return",
status,
versionNumber: 1,
});
const saved = await contractRepo.save(contract);
await ds.getRepository(ContractRoute).save(
ds.getRepository(ContractRoute).create({
contractId: saved.id,
originYardId: originId,
destinationYardId: destId,
sortOrder: 1,
}),
);
if (freightType === "CONTAINER") {
for (const size of ["20FT", "40FT"] as const) {
await ds.getRepository(ContractCargoScope).save(
ds.getRepository(ContractCargoScope).create({
contractId: saved.id,
containerSize: size,
}),
);
}
} else {
const bulkCargo = grain || sugar || fertilizer;
if (bulkCargo) {
await ds.getRepository(ContractCargoScope).save(
ds.getRepository(ContractCargoScope).create({
contractId: saved.id,
cargoTypeId: bulkCargo.id,
quantityCap: 10000,
}),
);
}
}
const matchingRates = rates.filter((r) => {
if (r.appliesTo === "CONTAINER" && freightType !== "CONTAINER") return false;
if (r.appliesTo === "BULK" && freightType !== "BULK") return false;
if (r.tradeDirection && r.tradeDirection !== direction) return false;
return r.status === "LIVE" && r.trigger === "ALWAYS";
});
const seen = new Set<string>();
for (const rate of matchingRates.slice(0, 3)) {
const sig = `${rate.rateType}|${rate.currency}|${rate.rateValue}`;
if (seen.has(sig)) continue;
seen.add(sig);
await ds.getRepository(ContractRateSnapshot).save(
ds.getRepository(ContractRateSnapshot).create({
contractId: saved.id,
rateId: rate.id,
rateCode: rate.rateType,
unitPrice: Number(rate.rateValue),
unitOfMeasure: rate.rateUnit,
currency: rate.currency ?? "USD",
containerSize: freightType === "CONTAINER" ? "20FT" : null,
isSurcharge: rate.trigger !== "ALWAYS",
conditionalOn: rate.trigger !== "ALWAYS" ? rate.trigger : null,
}),
);
}
contractCount++;
this.log(` Created ${status} ${freightType} ${direction} contract: ${ref} (${company.name})`);
}
this.log(`Done — ${contractCount} contracts ensured`);
});
}
interface CompanySeed {
id: string;
name: string;
tin: string;
profiles: Array<{ id: string; type: ProfileType; reference: string }>;
externalProfile: { id: string; userId: string; firstName: string; lastName: string };
}
const TEST_COMPANIES: CompanySeed[] = [
{
id: COMPANY_IDS[0], name: "Test Importer Co.", tin: "TST000001",
profiles: [
{ id: PROFILE_IDS[0], type: ProfileType.importer, reference: "TST-IM-001" },
{ id: PROFILE_IDS[1], type: ProfileType.exporter, reference: "TST-EX-001" },
],
externalProfile: { id: EXTERNAL_PROFILE_IDS[0], userId: "00000000-0000-0000-0000-000000000001", firstName: "Abebe", lastName: "Kebede" },
},
{
id: COMPANY_IDS[1], name: "Test Exporter Ltd.", tin: "TST000002",
profiles: [
{ id: PROFILE_IDS[2], type: ProfileType.importer, reference: "TST-IM-002" },
{ id: PROFILE_IDS[3], type: ProfileType.exporter, reference: "TST-EX-002" },
],
externalProfile: { id: EXTERNAL_PROFILE_IDS[1], userId: "00000000-0000-0000-0000-000000000002", firstName: "Bekele", lastName: "Alemu" },
},
{
id: COMPANY_IDS[2], name: "Bulk Commodities PLC", tin: "TST000003",
profiles: [
{ id: PROFILE_IDS[4], type: ProfileType.importer, reference: "TST-IM-003" },
{ id: PROFILE_IDS[5], type: ProfileType.exporter, reference: "TST-EX-003" },
],
externalProfile: { id: EXTERNAL_PROFILE_IDS[2], userId: "00000000-0000-0000-0000-000000000003", firstName: "Chala", lastName: "Tesfaye" },
},
{
id: COMPANY_IDS[3], name: "Hazardous Logistics Inc.", tin: "TST000004",
profiles: [
{ id: PROFILE_IDS[6], type: ProfileType.importer, reference: "TST-IM-004" },
{ id: PROFILE_IDS[7], type: ProfileType.exporter, reference: "TST-EX-004" },
],
externalProfile: { id: EXTERNAL_PROFILE_IDS[3], userId: "00000000-0000-0000-0000-000000000004", firstName: "Desta", lastName: "Hailu" },
},
];
async function seedTestCompanies(ds: DataSource, log: (msg: string) => void): Promise<Company[]> {
const companyRepo = ds.getRepository(Company);
const profileRepo = ds.getRepository(CompanyProfile);
const extProfileRepo = ds.getRepository(ExternalProfile);
const result: Company[] = [];
for (const seed of TEST_COMPANIES) {
let company = await companyRepo.findOne({ where: { id: seed.id } });
if (!company) {
company = await companyRepo.save(
companyRepo.create({
id: seed.id,
name: seed.name,
type: CompanyType.Customer,
kind: CompanyKind.Commercial,
status: CompanyStatus.Active,
tin: seed.tin,
country: "Ethiopia",
nationality: CompanyNationality.Ethiopian,
email: `info@${seed.name.toLowerCase().replace(/\s+/g, "")}.com`,
phone: "+251911000001",
}),
);
log(` Created company: ${seed.name}`);
} else {
log(` Company already exists: ${seed.name}`);
}
for (const p of seed.profiles) {
const existing = await profileRepo.findOne({ where: { id: p.id } });
if (!existing) {
await profileRepo.save(
profileRepo.create({
id: p.id,
companyId: seed.id,
type: p.type,
reference: p.reference,
status: ProfileStatus.Active,
}),
);
log(` Created ${p.type} profile: ${p.reference}`);
}
}
const ext = seed.externalProfile;
const existingExt = await extProfileRepo.findOne({ where: { id: ext.id } });
if (!existingExt) {
await extProfileRepo.save(
extProfileRepo.create({
id: ext.id,
userId: ext.userId,
companyId: seed.id,
firstName: ext.firstName,
lastName: ext.lastName,
isPrimaryContact: true,
onboardingCompleted: true,
}),
);
log(` Created external profile: ${ext.firstName} ${ext.lastName}`);
}
result.push(company);
}
return result;
}

View File

@@ -0,0 +1,298 @@
import type Vorpal from "vorpal";
import { DataSource } from "typeorm";
import { WagonStatus } from "@edr/types";
import type { CommandContext } from "./types";
import { Yard } from "../../modules/rule-engine/entities/yard.entity";
import { Route } from "../../modules/routes/entities/route.entity";
import { RouteMilestone } from "../../modules/routes/entities/route-milestone.entity";
import { Locomotive } from "../../modules/locomotives/entities/locomotive.entity";
import { Wagon } from "../../modules/wagons/entities/wagon.entity";
import { WagonType } from "../../modules/wagon-types/entities/wagon-type.entity";
import { TrainSet } from "../../modules/train-sets/entities/train-set.entity";
import { TrainSetLocomotive } from "../../modules/train-sets/entities/train-set-locomotive.entity";
import { TrainSetWagon } from "../../modules/train-sets/entities/train-set-wagon.entity";
import { TrainSchedule } from "../../modules/train-schedules/entities/train-schedule.entity";
import { TrainScheduleBooking } from "../../modules/train-schedules/entities/train-schedule-booking.entity";
import { Booking } from "../../modules/bookings/entities/booking.entity";
export function registerSeedTestSchedules(
vorpal: Vorpal,
ctx: CommandContext,
): void {
vorpal
.command("seed:test-schedules", "Seed train schedules with routes, wagons, and all deps for booking")
.option("-n, --count <n>", "Number of schedules to create (default: 3)")
.option("--direction <dirs>", "IMPORT,EXPORT (default: both)")
.option("--status <statuses>", "DRAFT,SCHEDULED,DISPATCHED (default: SCHEDULED)")
.option("--days-ahead <n>", "Days from now for departure (default: 3)")
.action(async function (this: any, args: any) {
const { app } = ctx;
const ds = app.get(DataSource);
const count = Math.max(1, Math.min(10, parseInt(args.options?.count ?? "3", 10)));
const directionList = (args.options?.direction ?? "IMPORT,EXPORT")
.split(",").map((s: string) => s.toUpperCase().trim())
.filter((s: string) => s === "IMPORT" || s === "EXPORT");
const statusList = (args.options?.status ?? "SCHEDULED")
.split(",").map((s: string) => s.toUpperCase().trim())
.filter((s: string) => s === "DRAFT" || s === "SCHEDULED" || s === "DISPATCHED");
const daysAhead = Math.max(0, parseInt(args.options?.daysAhead ?? "3", 10));
if (directionList.length === 0 || statusList.length === 0) {
this.log("error: at least one direction and status required");
return;
}
this.log(`Seeding ${count} schedules (direction=${directionList.join(",")}, status=${statusList.join(",")}, days_ahead=${daysAhead})...`);
const yards = await ds.getRepository(Yard).find({ where: { isActive: true } });
const yardByCode = new Map(yards.map((y) => [y.code.toUpperCase(), y]));
const djibouti = yardByCode.get("DJIBOUTI") ?? yards.find((y) => y.country === "Djibouti");
const addis = yardByCode.get("ADDIS_ABABA") ?? yards.find((y) => y.country === "Ethiopia");
if (!djibouti || !addis) {
this.log("error: need at least one Djibouti and one Ethiopia yard");
return;
}
const wagonTypes = await ds.getRepository(WagonType).find({ where: { isActive: true } });
if (wagonTypes.length === 0) {
this.log("error: no wagon types found — seed reference data first");
return;
}
const route = await ensureTestRoute(ds, yards, djibouti, addis, (msg) => this.log(msg));
const scheduleRepo = ds.getRepository(TrainSchedule);
const trainSetRepo = ds.getRepository(TrainSet);
let seeded = 0;
for (let i = 0; i < count; i++) {
const dir = directionList[i % directionList.length];
const status = statusList[i % statusList.length];
const trainNumber = `TST-SCH-${String(i + 1).padStart(3, "0")}`;
const existing = await scheduleRepo.findOne({ where: { trainNumber } });
if (existing) {
this.log(` Schedule ${trainNumber} already exists — skipping`);
seeded++;
continue;
}
const originYard = dir === "IMPORT" ? djibouti : addis;
const destYard = dir === "IMPORT" ? addis : djibouti;
const isDispatched = status === "DISPATCHED";
const now = new Date();
const departure = new Date(now);
departure.setDate(departure.getDate() + daysAhead);
departure.setHours(7, 0, 0, 0);
const travelHours = dir === "IMPORT" ? 12 : 10;
const arrival = new Date(departure.getTime() + travelHours * 60 * 60 * 1000);
const locomotive = await ensureTestLocomotive(ds, originYard.id, (msg) => this.log(msg));
const wagonCount = 4;
const wagonType = wagonTypes[0];
const wagonCapacity = Number(wagonType.capacityTons) || 70;
const wagonLength = Number(wagonType.lengthMeters) || 14;
const tareWeight = Number(wagonType.tareWeightTons) || 14;
const totalWagonWeight = wagonCount * (tareWeight + 20);
const trainSet = await trainSetRepo.save(
trainSetRepo.create({
locomotiveId: locomotive.id,
totalWeightTons: totalWagonWeight,
totalLengthMeters: wagonLength * wagonCount,
wagonCount,
status: isDispatched ? "DISPATCHED" : status === "DRAFT" ? "DRAFT" : "ASSIGNED",
}),
);
await ds.getRepository(TrainSetLocomotive).save(
ds.getRepository(TrainSetLocomotive).create({
trainSetId: trainSet.id,
locomotiveId: locomotive.id,
sequenceNo: 0,
}),
);
const schedule = await scheduleRepo.save(
scheduleRepo.create({
trainSetId: trainSet.id,
routeId: route.id,
originStationId: originYard.id,
destinationStationId: destYard.id,
scheduledDepartureDate: departure,
scheduledArrivalDate: arrival,
actualDepartureAt: isDispatched ? departure : null,
status,
trainNumber,
direction: dir,
maxWagons: 53,
bookingWindowStatus: isDispatched ? "CLOSED" : "OPEN",
}),
);
for (let w = 0; w < wagonCount; w++) {
const seq = w + 1;
const wagonNumber = `${trainNumber}-W${String(seq).padStart(2, "0")}`;
const physicalWagon = await ensureTestWagon(ds, {
wagonNumber,
wagonTypeId: wagonType.id,
yardId: originYard.id,
trainScheduleId: schedule.id,
tareWeight,
capacityTons: wagonCapacity,
dispatched: isDispatched,
});
await ds.getRepository(TrainSetWagon).save(
ds.getRepository(TrainSetWagon).create({
trainSetId: trainSet.id,
wagonTypeId: wagonType.id,
physicalWagonId: physicalWagon.id,
sequenceNo: seq,
capacityTons: wagonCapacity,
lengthMeters: wagonLength,
assignedWeightTons: 20,
status: isDispatched ? "DEPARTED" : "PLANNED",
}),
);
}
const scheduleBookings = await ds.getRepository(TrainScheduleBooking).find({
where: { trainScheduleId: schedule.id },
});
if (scheduleBookings.length === 0) {
const booking = await ds.getRepository(Booking).findOne({ where: {}, order: { createdAt: "DESC" } });
if (booking) {
await ds.getRepository(TrainScheduleBooking).save(
ds.getRepository(TrainScheduleBooking).create({
trainScheduleId: schedule.id,
bookingId: booking.id,
}),
);
}
}
seeded++;
this.log(` Created ${status} ${dir} schedule: ${trainNumber} (${originYard.label}${destYard.label})`);
}
this.log(`Done — ${seeded} train schedules ensured`);
});
}
async function ensureTestRoute(
ds: DataSource,
yards: Yard[],
originYard: Yard,
destYard: Yard,
log: (msg: string) => void,
): Promise<Route> {
const repo = ds.getRepository(Route);
const name = `TST-RTE-${originYard.code}_${destYard.code}`;
let route = await repo.findOne({ where: { name } });
if (route) return route;
route = await repo.save(
repo.create({
name,
originYardId: originYard.id,
destinationYardId: destYard.id,
isActive: true,
}),
);
log(` Created route: ${name}`);
const milestoneRepo = ds.getRepository(RouteMilestone);
const intermediateYards = yards.filter(
(y) => y.id !== originYard.id && y.id !== destYard.id,
);
await milestoneRepo.save(
milestoneRepo.create({
routeId: route.id,
yardId: originYard.id,
sequenceNo: 0,
}),
);
for (const [idx, y] of intermediateYards.entries()) {
await milestoneRepo.save(
milestoneRepo.create({
routeId: route.id,
yardId: y.id,
sequenceNo: (idx + 1) * 2,
}),
);
}
await milestoneRepo.save(
milestoneRepo.create({
routeId: route.id,
yardId: destYard.id,
sequenceNo: (intermediateYards.length + 1) * 2,
}),
);
log(` Added ${intermediateYards.length + 2} milestones`);
return route;
}
async function ensureTestLocomotive(
ds: DataSource,
yardId: string,
log: (msg: string) => void,
): Promise<Locomotive> {
const repo = ds.getRepository(Locomotive);
const code = "TST-LOCO-01";
let loco = await repo.findOne({ where: { code } });
if (loco) return loco;
loco = await repo.save(
repo.create({
code,
name: "Test Locomotive",
locomotiveType: "DIESEL",
maxPullWeightTons: 4200,
maxTrainLengthMeters: 760,
status: "AVAILABLE",
currentYardId: yardId,
}),
);
log(` Created locomotive: ${code}`);
return loco;
}
async function ensureTestWagon(
ds: DataSource,
input: {
wagonNumber: string;
wagonTypeId: string;
yardId: string;
trainScheduleId: string;
tareWeight: number;
capacityTons: number;
dispatched: boolean;
},
): Promise<Wagon> {
const repo = ds.getRepository(Wagon);
const existing = await repo.findOne({ where: { wagonNumber: input.wagonNumber } });
if (existing) return existing;
const wagon = repo.create({
wagonNumber: input.wagonNumber,
wagonTypeId: input.wagonTypeId,
currentYardId: input.yardId,
currentTrainScheduleId: input.trainScheduleId,
tareWeight: input.tareWeight,
maxPayloadWeight: input.capacityTons,
status: input.dispatched ? WagonStatus.Assigned : WagonStatus.Available,
notes: "Test seed wagon",
});
const saved = await repo.save(wagon as any);
return Array.isArray(saved) ? saved[0] : saved;
}

View File

@@ -0,0 +1,6 @@
import type Vorpal from "vorpal";
import type { INestApplicationContext } from "@nestjs/common";
export type CommandContext = {
app: INestApplicationContext;
};

View File

@@ -0,0 +1,36 @@
import "reflect-metadata";
import { config } from "dotenv";
config();
import Vorpal from "vorpal";
import { registerCommands } from "./cmds/index";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "../app.module";
const vorpal = new Vorpal();
async function main() {
const app = await NestFactory.createApplicationContext(AppModule, {
logger: false,
});
try {
registerCommands(vorpal, { app });
const args = process.argv.slice(2);
if (args.length > 0) {
await vorpal.exec(args.join(" "));
} else {
vorpal.parse(process.argv);
}
} finally {
await app.close();
}
}
main().catch((err) => {
console.error("Script failed:", err);
process.exit(1);
});

1020
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff