mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 19:30:57 +00:00
enhance gate pass and freight payment handling in train scheduling
- Updated the logic in to ensure that a booking only earns its gate pass once the freight charges are settled. - Added logging for bookings that have not settled freight payment when securing gate passes. - Modified seeders to ensure that bookings have associated company profiles to prevent data inconsistencies. - Updated freight permissions to include new clearance actions for bookings. - Enhanced the UI to reflect changes in the clearance process, including new shipment request pages and improved status handling in the clearance action panel. - Adjusted the contract clearance list to accommodate both customs contracts and shipment bookings. - Improved the handling of GENERAL contracts in various components to ensure proper booking flow and visibility.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { TrainSchedulingService } from './train-scheduling.service';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { ClearanceMilestone } from '../contracts/entities/clearance-milestone.entity';
|
||||
|
||||
type Row = Pick<ClearanceMilestone, 'bookingId' | 'milestoneCode' | 'status'> & {
|
||||
metadata?: Record<string, unknown> | null;
|
||||
triggeredAt?: Date | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* The gate pass is secured once per train schedule, but each booking only earns
|
||||
* its GATEPASS_GRANTED milestone after settling freight payment. An unpaid
|
||||
* booking must not ride a paid neighbour's grant — the train proceeds, that
|
||||
* booking stays pending.
|
||||
*/
|
||||
function makeService(bookings: Array<Partial<Booking>>, rows: Row[]) {
|
||||
const milestoneRepo = {
|
||||
find: jest.fn().mockResolvedValue(rows),
|
||||
save: jest.fn((row: Row) => Promise.resolve(row)),
|
||||
};
|
||||
const bookingRepo = { find: jest.fn().mockResolvedValue(bookings) };
|
||||
const dataSource = {
|
||||
getRepository: (entity: unknown) =>
|
||||
entity === Booking ? bookingRepo : milestoneRepo,
|
||||
};
|
||||
|
||||
const service = Object.create(
|
||||
TrainSchedulingService.prototype,
|
||||
) as TrainSchedulingService;
|
||||
Object.assign(service, {
|
||||
dataSource,
|
||||
logger: { warn: jest.fn(), log: jest.fn() },
|
||||
});
|
||||
return { service, milestoneRepo };
|
||||
}
|
||||
|
||||
/** Reach the private bridge write under test. */
|
||||
function grant(service: TrainSchedulingService, at: Date): Promise<void> {
|
||||
return (
|
||||
service as unknown as {
|
||||
completeGatepassMilestoneForSchedule(id: string, at: Date): Promise<void>;
|
||||
}
|
||||
).completeGatepassMilestoneForSchedule('sched-1', at);
|
||||
}
|
||||
|
||||
const securedAt = new Date('2026-07-09T08:00:00.000Z');
|
||||
|
||||
describe('gate pass is withheld from bookings that have not paid freight', () => {
|
||||
it('grants the paid booking and leaves the unpaid one pending', async () => {
|
||||
const rows: Row[] = [
|
||||
{ bookingId: 'paid', milestoneCode: 'FREIGHT_PAYMENT_SETTLED', status: 'COMPLETED' },
|
||||
{ bookingId: 'paid', milestoneCode: 'GATEPASS_GRANTED', status: 'PENDING' },
|
||||
{ bookingId: 'unpaid', milestoneCode: 'FREIGHT_PAYMENT_SETTLED', status: 'PENDING' },
|
||||
{ bookingId: 'unpaid', milestoneCode: 'GATEPASS_GRANTED', status: 'PENDING' },
|
||||
];
|
||||
const { service, milestoneRepo } = makeService(
|
||||
[
|
||||
{ id: 'paid', status: 'CONFIRMED', paymentStatus: 'PENDING' },
|
||||
{ id: 'unpaid', status: 'CONFIRMED', paymentStatus: 'PENDING' },
|
||||
],
|
||||
rows,
|
||||
);
|
||||
|
||||
await grant(service, securedAt);
|
||||
|
||||
const saved = milestoneRepo.save.mock.calls.map(([r]: [Row]) => r);
|
||||
expect(saved).toHaveLength(1);
|
||||
expect(saved[0]!.bookingId).toBe('paid');
|
||||
expect(saved[0]!.status).toBe('COMPLETED');
|
||||
expect(saved[0]!.triggeredAt).toBe(securedAt);
|
||||
|
||||
const unpaid = rows.find(
|
||||
(r) => r.bookingId === 'unpaid' && r.milestoneCode === 'GATEPASS_GRANTED',
|
||||
);
|
||||
expect(unpaid!.status).toBe('PENDING');
|
||||
});
|
||||
|
||||
it('treats a booking paid outside the milestone path as paid', async () => {
|
||||
// Some payment paths settle the invoice without writing the milestone; the
|
||||
// clearance views self-heal it on read, so the gate pass must not lag.
|
||||
const rows: Row[] = [
|
||||
{ bookingId: 'b-1', milestoneCode: 'FREIGHT_PAYMENT_SETTLED', status: 'PENDING' },
|
||||
{ bookingId: 'b-1', milestoneCode: 'GATEPASS_GRANTED', status: 'PENDING' },
|
||||
];
|
||||
const { service, milestoneRepo } = makeService(
|
||||
[{ id: 'b-1', status: 'CONFIRMED', paymentStatus: 'PAID' }],
|
||||
rows,
|
||||
);
|
||||
|
||||
await grant(service, securedAt);
|
||||
|
||||
expect(milestoneRepo.save).toHaveBeenCalledTimes(1);
|
||||
expect(milestoneRepo.save.mock.calls[0]![0].bookingId).toBe('b-1');
|
||||
});
|
||||
|
||||
it('leaves an already-granted milestone untouched', async () => {
|
||||
const rows: Row[] = [
|
||||
{ bookingId: 'b-1', milestoneCode: 'FREIGHT_PAYMENT_SETTLED', status: 'COMPLETED' },
|
||||
{ bookingId: 'b-1', milestoneCode: 'GATEPASS_GRANTED', status: 'COMPLETED' },
|
||||
];
|
||||
const { service, milestoneRepo } = makeService(
|
||||
[{ id: 'b-1', status: 'PAID', paymentStatus: 'PAID' }],
|
||||
rows,
|
||||
);
|
||||
|
||||
await grant(service, securedAt);
|
||||
|
||||
expect(milestoneRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing when the schedule carries no customs bookings', async () => {
|
||||
const { service, milestoneRepo } = makeService([], []);
|
||||
|
||||
await grant(service, securedAt);
|
||||
|
||||
expect(milestoneRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user