mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 02:00:56 +00:00
128 lines
4.7 KiB
TypeScript
128 lines
4.7 KiB
TypeScript
import { BookingOrdersService } from './booking-orders.service';
|
|
|
|
/**
|
|
* Phase-0 spine: a drawdown order spawns a PRICED, UNPAID child booking that
|
|
* waits for Marketing review (or the customs clearance gate first) — it does
|
|
* NOT auto-enter the train batch pool, and the contract is not charged.
|
|
*/
|
|
describe('BookingOrdersService — child spawn on order create', () => {
|
|
function makeService(opts: { includesCustoms: boolean; roadKm?: number | null }) {
|
|
const contract = {
|
|
id: 'c-1',
|
|
bookingType: 'GENERAL_CONTRACT',
|
|
status: 'CONTRACT_ACTIVE',
|
|
expiresAt: new Date('2030-01-01T00:00:00.000Z'),
|
|
freightType: 'BULK',
|
|
originYardId: 'o-1',
|
|
destinationYardId: 'd-1',
|
|
companyId: null,
|
|
paymentCurrency: 'ETB',
|
|
serviceType: { includesCustoms: opts.includesCustoms, code: 'RAIL_BULK' },
|
|
bookingContainers: [],
|
|
};
|
|
|
|
// Capture what status the child is created with.
|
|
const created: Record<string, unknown>[] = [];
|
|
const managerUpdates: Record<string, unknown>[] = [];
|
|
const fakeManager = {
|
|
create: (_entity: unknown, data: Record<string, unknown>) => {
|
|
created.push(data);
|
|
return { id: 'child-1', ...data };
|
|
},
|
|
save: async (row: Record<string, unknown>) => ({ id: 'child-1', ...row }),
|
|
getRepository: () => ({
|
|
findOne: async () => ({ id: 'child-1', paymentCurrency: 'ETB', bookingContainers: [] }),
|
|
update: async (_id: string, data: Record<string, unknown>) => {
|
|
managerUpdates.push(data);
|
|
},
|
|
}),
|
|
};
|
|
|
|
const dataSource = {
|
|
transaction: async (cb: (m: unknown) => Promise<unknown>) => cb(fakeManager),
|
|
getRepository: () => ({ update: jest.fn() }),
|
|
};
|
|
const ordersRepository = {
|
|
countByYear: jest.fn().mockResolvedValue(0),
|
|
findById: jest.fn().mockResolvedValue({ id: 'order-1', lines: [] }),
|
|
};
|
|
const bookingsRepository = {
|
|
findById: jest.fn().mockResolvedValue(contract),
|
|
countByYear: jest.fn().mockResolvedValue(0),
|
|
};
|
|
const generalContractService = {
|
|
isGeneralContract: () => true,
|
|
getRouteLines: jest.fn().mockResolvedValue([]),
|
|
getQuantityLines: jest
|
|
.fn()
|
|
.mockResolvedValue([
|
|
{ containerTypeId: null, remainingQuantity: 100, containerTypeName: null },
|
|
]),
|
|
isExhausted: jest.fn().mockResolvedValue(false),
|
|
};
|
|
const pricingService = {
|
|
computePriceForBooking: jest.fn().mockResolvedValue({
|
|
totalAmount: 500,
|
|
priorityScore: 10,
|
|
lineItems: [],
|
|
currency: 'ETB',
|
|
}),
|
|
};
|
|
const ratesService = { findLiveRates: jest.fn().mockResolvedValue([]) };
|
|
const trainSchedulingService = {
|
|
existsOpenScheduleOnRouteDay: jest.fn().mockResolvedValue(true),
|
|
};
|
|
const companiesService = {};
|
|
|
|
const service = new BookingOrdersService(
|
|
dataSource as never,
|
|
ordersRepository as never,
|
|
bookingsRepository as never,
|
|
companiesService as never,
|
|
generalContractService as never,
|
|
pricingService as never,
|
|
ratesService as never,
|
|
trainSchedulingService as never,
|
|
);
|
|
return { service, created, managerUpdates, pricingService };
|
|
}
|
|
|
|
const dto = {
|
|
contractBookingId: 'c-1',
|
|
scheduledDate: '2026-07-01T00:00:00.000Z',
|
|
lines: [{ quantity: 10, hazardousQuantity: 4, reeferQuantity: 0 }],
|
|
};
|
|
|
|
it('spawns the child at OPERATION_REQUEST_PENDING (no customs), priced + unpaid', async () => {
|
|
const { service, created, managerUpdates, pricingService } = makeService({
|
|
includesCustoms: false,
|
|
});
|
|
await service.create(dto as never);
|
|
|
|
const child = created.find((c) => c.bookingType === 'ONE_TIME')!;
|
|
expect(child.status).toBe('OPERATION_REQUEST_PENDING');
|
|
expect(child.paymentStatus).toBe('PENDING');
|
|
expect(child.isHazardous).toBe(true); // line has hazardousQuantity > 0
|
|
expect(pricingService.computePriceForBooking).toHaveBeenCalled();
|
|
// The computed price is persisted onto the child.
|
|
expect(managerUpdates.some((u) => u.totalAmount === 500)).toBe(true);
|
|
});
|
|
|
|
it('spawns the child at AWAITING_DOCUMENTS when the service includes customs', async () => {
|
|
const { service, created } = makeService({ includesCustoms: true });
|
|
await service.create(dto as never);
|
|
const child = created.find((c) => c.bookingType === 'ONE_TIME')!;
|
|
expect(child.status).toBe('AWAITING_DOCUMENTS');
|
|
});
|
|
|
|
it('rejects when hazardous quantity exceeds the line quantity', async () => {
|
|
const { service } = makeService({ includesCustoms: false });
|
|
await expect(
|
|
service.create({
|
|
...dto,
|
|
lines: [{ quantity: 5, hazardousQuantity: 9, reeferQuantity: 0 }],
|
|
} as never),
|
|
).rejects.toThrow(/exceed the line quantity/);
|
|
});
|
|
});
|