mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
Merge branch 'dev' into freight/feat/fixes-v1
This commit is contained in:
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { Booking } from '../modules/bookings/entities/booking.entity';
|
||||
import { CompanyProfile } from '../modules/companies/entities/company-profile.entity';
|
||||
import { ServiceType } from '../modules/rule-engine/entities/service-type.entity';
|
||||
import { Yard } from '../modules/rule-engine/entities/yard.entity';
|
||||
import { Warehouse } from '../modules/warehouses/entities/warehouse.entity';
|
||||
@@ -91,12 +92,26 @@ export class Batch5TestDataSeeder {
|
||||
return;
|
||||
}
|
||||
|
||||
// bookings.company_id AND bookings.company_profile_id are both NOT NULL, so a
|
||||
// seed booking needs an owning company profile. Resolve the profile and take
|
||||
// its company from it, so the two columns can never disagree. Without this the
|
||||
// seeder aborted on its first insert.
|
||||
const companyProfile = await this.dataSource
|
||||
.getRepository(CompanyProfile)
|
||||
.findOne({ where: {} });
|
||||
if (!companyProfile) {
|
||||
this.logger.warn('No company profile found; skipping Batch 5 seed');
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
for (const seed of SEEDS) {
|
||||
const booking = await bookingRepo.save(
|
||||
bookingRepo.create({
|
||||
reference: seed.ref,
|
||||
companyId: companyProfile.companyId,
|
||||
companyProfileId: companyProfile.id,
|
||||
originYardId: originYard.id,
|
||||
destinationYardId: destYard.id,
|
||||
serviceTypeId: serviceType.id,
|
||||
|
||||
@@ -211,7 +211,6 @@ export class DemoBookingsSeeder {
|
||||
name: "Flat Wagon",
|
||||
capacityTons: 70,
|
||||
lengthMeters: 14,
|
||||
maxWagonsPerTrain: 53,
|
||||
supportedLoadTypes: ["CONTAINER"],
|
||||
isActive: true,
|
||||
equatedLengthM: 14,
|
||||
@@ -224,7 +223,6 @@ export class DemoBookingsSeeder {
|
||||
name: "Covered Hopper",
|
||||
capacityTons: 60,
|
||||
lengthMeters: 12,
|
||||
maxWagonsPerTrain: 55,
|
||||
supportedLoadTypes: ["BULK"],
|
||||
isActive: true,
|
||||
equatedLengthM: 12,
|
||||
@@ -236,7 +234,6 @@ export class DemoBookingsSeeder {
|
||||
name: "Powder Wagon",
|
||||
capacityTons: 55,
|
||||
lengthMeters: 12,
|
||||
maxWagonsPerTrain: 55,
|
||||
supportedLoadTypes: ["BULK"],
|
||||
isActive: true,
|
||||
equatedLengthM: 12,
|
||||
@@ -248,7 +245,6 @@ export class DemoBookingsSeeder {
|
||||
name: "Open Wagon",
|
||||
capacityTons: 65,
|
||||
lengthMeters: 13,
|
||||
maxWagonsPerTrain: 53,
|
||||
supportedLoadTypes: ["BULK"],
|
||||
isActive: true,
|
||||
equatedLengthM: 13,
|
||||
@@ -508,8 +504,6 @@ export class DemoBookingsSeeder {
|
||||
wagonTypeId: nw5.id,
|
||||
trainId: null,
|
||||
sequenceNumber: null,
|
||||
tareWeight: 20,
|
||||
maxPayloadWeight: 70,
|
||||
status: WagonStatus.Available,
|
||||
currentYardId: index % 2 === 0 ? djibouti.id : addis.id,
|
||||
notes: "Demo wagon for train scheduling",
|
||||
|
||||
@@ -76,15 +76,11 @@ export class DemoFreightDataSeeder {
|
||||
}
|
||||
|
||||
const toCreate = MIN_WAGONS_PER_TYPE - existing;
|
||||
const tare = Number(type.tareWeightTons ?? 20);
|
||||
const maxPayload = Number(type.capacityTons ?? 60);
|
||||
const rows = Array.from({ length: toCreate }, (_, i) => {
|
||||
const seq = existing + i + 1;
|
||||
return wagonRepo.create({
|
||||
wagonNumber: `${type.code}-${String(seq).padStart(4, '0')}`,
|
||||
wagonTypeId: type.id,
|
||||
tareWeight: tare,
|
||||
maxPayloadWeight: maxPayload,
|
||||
status: WagonStatus.Available,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -205,6 +205,7 @@ export const FLEET_ROAD_PERMISSIONS: FreightPermissionSeed[] = [
|
||||
perm('e2b00001-0001-4000-8000-000000000003', 'edr_freight_app:drivers:update', 'Update driver'),
|
||||
perm('e2b00001-0001-4000-8000-000000000004', 'edr_freight_app:drivers:delete', 'Delete driver'),
|
||||
perm('e2c00001-0001-4000-8000-000000000001', 'edr_freight_app:tracking:view', 'Track vehicles'),
|
||||
perm('e2c00001-0001-4000-8000-000000000002', 'edr_freight_app:tracking:manage', 'Manage GPS trackers'),
|
||||
perm('e2d00001-0001-4000-8000-000000000001', 'edr_freight_app:fuel:view', 'View fuel purchases'),
|
||||
perm('e2d00001-0001-4000-8000-000000000002', 'edr_freight_app:fuel:create', 'Create fuel purchase'),
|
||||
perm('e2d00001-0001-4000-8000-000000000003', 'edr_freight_app:fuel:update', 'Update fuel purchase'),
|
||||
@@ -479,6 +480,7 @@ export const FREIGHT_PERMS = {
|
||||
},
|
||||
tracking: {
|
||||
view: 'edr_freight_app:tracking:view',
|
||||
manage: 'edr_freight_app:tracking:manage',
|
||||
},
|
||||
fuel: {
|
||||
view: 'edr_freight_app:fuel:view',
|
||||
@@ -662,9 +664,13 @@ export const ROLE_PERMISSION_PRESETS = {
|
||||
FREIGHT_PERMS.fleet.view,
|
||||
FREIGHT_PERMS.fleet.manage,
|
||||
// Path A (no customs): Operations reviews the customer's self-clearance docs
|
||||
// on the contract before the customer may create a shipment booking.
|
||||
// — on the contract for ONE_TIME contracts, and PER BOOKING for GENERAL
|
||||
// contracts (booking-level document review → finalize → CLEARANCE_READY).
|
||||
FREIGHT_PERMS.contracts.view,
|
||||
FREIGHT_PERMS.contracts.opsClearanceReview,
|
||||
FREIGHT_PERMS.bookings.clearanceView,
|
||||
FREIGHT_PERMS.bookings.reviewDocuments,
|
||||
FREIGHT_PERMS.bookings.finalizeClearance,
|
||||
...allRuleEngineViewKeys(),
|
||||
],
|
||||
director: [
|
||||
|
||||
@@ -203,7 +203,6 @@ export class MarshallingDemoTrainsSeeder {
|
||||
const totalWeight = bookingWeights.reduce((sum, weight) => sum + weight, 0);
|
||||
const wagonCapacity = Number(refs.wagonType.capacityTons) || 70;
|
||||
const wagonLength = Number(refs.wagonType.lengthMeters) || 14;
|
||||
const tareWeight = Number(refs.wagonType.tareWeightTons) || 14;
|
||||
|
||||
const trainSet = await trainSetRepo.save(
|
||||
trainSetRepo.create({
|
||||
@@ -275,8 +274,6 @@ export class MarshallingDemoTrainsSeeder {
|
||||
wagonTypeId: refs.wagonType.id,
|
||||
yardId: originYard.id,
|
||||
trainScheduleId: schedule.id,
|
||||
tareWeight,
|
||||
capacityTons: wagonCapacity,
|
||||
dispatched: hasDeparted,
|
||||
});
|
||||
|
||||
@@ -420,8 +417,6 @@ export class MarshallingDemoTrainsSeeder {
|
||||
wagonTypeId: string;
|
||||
yardId: string;
|
||||
trainScheduleId: string;
|
||||
tareWeight: number;
|
||||
capacityTons: number;
|
||||
dispatched: boolean;
|
||||
}): Promise<Wagon> {
|
||||
const repo = this.dataSource.getRepository(Wagon);
|
||||
@@ -433,8 +428,6 @@ export class MarshallingDemoTrainsSeeder {
|
||||
wagonTypeId: input.wagonTypeId,
|
||||
currentYardId: input.yardId,
|
||||
currentTrainScheduleId: input.trainScheduleId,
|
||||
tareWeight: input.tareWeight,
|
||||
maxPayloadWeight: input.capacityTons,
|
||||
status: WagonStatus.Assigned,
|
||||
notes: 'Marshalling demo seed wagon',
|
||||
}),
|
||||
|
||||
@@ -12,6 +12,7 @@ import { WeightLimitRule } from "../modules/rule-engine/entities/weight-limit-ru
|
||||
import { Route } from "../modules/routes/entities/route.entity";
|
||||
import { RouteMilestone } from "../modules/routes/entities/route-milestone.entity";
|
||||
import { Yard } from "../modules/rule-engine/entities/yard.entity";
|
||||
import { deriveTradeDirection } from "../common/derive-trade-direction.util";
|
||||
|
||||
const STAFF_USER_ID = "00000000-0000-0000-0000-000000000001";
|
||||
const CEO_USER_ID = "00000000-0000-0000-0000-000000000002";
|
||||
@@ -295,6 +296,7 @@ export class PricingDataSeeder {
|
||||
originYardId: addis.id,
|
||||
destinationYardId: direDawa.id,
|
||||
status: 'AVAILABLE',
|
||||
direction: deriveTradeDirection(addis, direDawa),
|
||||
}),
|
||||
);
|
||||
await milestoneRepo.save([
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DataSource } from 'typeorm';
|
||||
|
||||
import { Booking } from '../modules/bookings/entities/booking.entity';
|
||||
import { CargoType } from '../modules/rule-engine/entities/cargo-type.entity';
|
||||
import { CompanyProfile } from '../modules/companies/entities/company-profile.entity';
|
||||
import { Locomotive } from '../modules/locomotives/entities/locomotive.entity';
|
||||
import { ServiceType } from '../modules/rule-engine/entities/service-type.entity';
|
||||
import { Yard } from '../modules/rule-engine/entities/yard.entity';
|
||||
@@ -60,10 +61,16 @@ export class WarehouseDemoSeeder {
|
||||
(await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ??
|
||||
(await serviceTypeRepo.findOne({ where: { isActive: true } }));
|
||||
const cargoType = await cargoTypeRepo.findOne({ where: { isActive: true } });
|
||||
// bookings.company_id AND company_profile_id are both NOT NULL — a demo booking
|
||||
// still needs an owner. Take the company from the profile so they always agree.
|
||||
const companyProfile = await this.dataSource
|
||||
.getRepository(CompanyProfile)
|
||||
.findOne({ where: {} });
|
||||
|
||||
if (!djibYard || !ethYard || !serviceType) {
|
||||
if (!djibYard || !ethYard || !serviceType || !companyProfile) {
|
||||
this.logger.warn(
|
||||
`Missing yards/service type (djib=${djibYard?.code}, eth=${ethYard?.code}, svc=${serviceType?.code}); skipping`,
|
||||
`Missing yards/service type/company profile (djib=${djibYard?.code}, eth=${ethYard?.code}, ` +
|
||||
`svc=${serviceType?.code}, companyProfile=${companyProfile?.id ?? 'none'}); skipping`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -89,7 +96,7 @@ export class WarehouseDemoSeeder {
|
||||
): Promise<Booking> =>
|
||||
bookingRepo.save(
|
||||
bookingRepo.create({
|
||||
...this.demoBookingDefaults(),
|
||||
...this.demoBookingDefaults(companyProfile),
|
||||
reference,
|
||||
originYardId: direction === 'EXPORT' ? ethYard.id : djibYard.id,
|
||||
destinationYardId: direction === 'EXPORT' ? djibYard.id : ethYard.id,
|
||||
@@ -182,7 +189,15 @@ export class WarehouseDemoSeeder {
|
||||
}
|
||||
|
||||
// 6) Import Arrive Queue — an ARRIVED import train with IN_TRANSIT bookings, no inventory yet.
|
||||
await this.seedArrivedImportTrain(djibYard, ethYard, serviceType, cargoType, ago(60), ago(360));
|
||||
await this.seedArrivedImportTrain(
|
||||
djibYard,
|
||||
ethYard,
|
||||
serviceType,
|
||||
cargoType,
|
||||
companyProfile,
|
||||
ago(60),
|
||||
ago(360),
|
||||
);
|
||||
created += 1;
|
||||
|
||||
this.logger.log(`✅ Warehouse demo seeded: ${created} buckets populated across every queue`);
|
||||
@@ -199,6 +214,7 @@ export class WarehouseDemoSeeder {
|
||||
ethYard: Yard,
|
||||
serviceType: ServiceType,
|
||||
cargoType: CargoType | null,
|
||||
owner: CompanyProfile,
|
||||
arrival: Date,
|
||||
departure: Date,
|
||||
): Promise<void> {
|
||||
@@ -238,7 +254,7 @@ export class WarehouseDemoSeeder {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const b = await bookingRepo.save(
|
||||
bookingRepo.create({
|
||||
...this.demoBookingDefaults(),
|
||||
...this.demoBookingDefaults(owner),
|
||||
reference: `WH-DEMO-ARR-${i}`,
|
||||
originYardId: djibYard.id,
|
||||
destinationYardId: ethYard.id,
|
||||
@@ -258,8 +274,10 @@ export class WarehouseDemoSeeder {
|
||||
}
|
||||
}
|
||||
|
||||
private demoBookingDefaults(): Partial<Booking> {
|
||||
private demoBookingDefaults(owner: CompanyProfile): Partial<Booking> {
|
||||
return {
|
||||
companyId: owner.companyId,
|
||||
companyProfileId: owner.id,
|
||||
scheduledDate: new Date(),
|
||||
contractType: 'SPOT',
|
||||
equipmentReturn: 'TERMINAL',
|
||||
|
||||
Reference in New Issue
Block a user