mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 15:25:45 +00:00
feat(contracts): lazily expire lapsed contracts and price fuel surcharge
This commit is contained in:
@@ -157,13 +157,9 @@ export class ContractBookingService {
|
||||
actorPermissions != null &&
|
||||
hasFreightPermission(actorPermissions, FREIGHT_PERMS.contracts.createBooking);
|
||||
|
||||
await this.assertNotExpired(contract);
|
||||
const createdByRole = await this.assertGate(contract, isGlActor);
|
||||
|
||||
// Validity window must still be open.
|
||||
if (contract.contractValidUntil && contract.contractValidUntil.getTime() < Date.now()) {
|
||||
throw new BadRequestException('Contract validity has expired — no new bookings.');
|
||||
}
|
||||
|
||||
// ONE_TIME: a single shipment at a time. The slot frees only if the prior
|
||||
// booking reached a terminal state (e.g. payment expired without shipping),
|
||||
// letting the customer re-book within contract validity (doc §10.4).
|
||||
@@ -441,12 +437,9 @@ export class ContractBookingService {
|
||||
// The customer initiates his own shipment instance on ONE_TIME contracts
|
||||
// (customs or self-clearance); GL may also initiate on a customs contract.
|
||||
// GENERAL customs instances come from a shipment request, not from here.
|
||||
await this.assertNotExpired(contract);
|
||||
const createdByRole = await this.assertGate(contract, isGlActor, true);
|
||||
|
||||
if (contract.contractValidUntil && contract.contractValidUntil.getTime() < Date.now()) {
|
||||
throw new BadRequestException('Contract validity has expired — no new bookings.');
|
||||
}
|
||||
|
||||
// ONE_TIME carries a single shipment at a time; a bare instance occupies the
|
||||
// slot from the moment it is initiated (it is not a terminal status). The
|
||||
// split chain is the one exception — a paid partial frees the slot and
|
||||
@@ -545,9 +538,7 @@ export class ContractBookingService {
|
||||
'Shipment-request initiation applies only to general customs contracts.',
|
||||
);
|
||||
}
|
||||
if (contract.contractValidUntil && contract.contractValidUntil.getTime() < Date.now()) {
|
||||
throw new BadRequestException('Contract validity has expired — no new bookings.');
|
||||
}
|
||||
await this.assertNotExpired(contract);
|
||||
|
||||
const route = await this.resolveRoute(contract, opts.contractRouteId);
|
||||
|
||||
@@ -681,9 +672,11 @@ export class ContractBookingService {
|
||||
if (!dto.scheduledDate) {
|
||||
throw new BadRequestException('A binding shipment day is required');
|
||||
}
|
||||
if (contract.contractValidUntil && contract.contractValidUntil.getTime() < Date.now()) {
|
||||
throw new BadRequestException('Contract validity has expired — no new bookings.');
|
||||
}
|
||||
// No expiry gate here on purpose: this booking was already initiated
|
||||
// before the contract lapsed (createUnderContract/initiateUnderContract
|
||||
// already checked expiry at start). Finishing an in-flight booking must
|
||||
// proceed even if the contract expires meanwhile — only starting a NEW
|
||||
// booking is blocked (see assertNotExpired).
|
||||
|
||||
// Completion is booking time: the route's booking window must be open —
|
||||
// the same config-driven gate a direct one-time booking passes at create.
|
||||
@@ -1019,6 +1012,22 @@ export class ContractBookingService {
|
||||
* Returns the role to stamp on the booking, or throws if the caller is not
|
||||
* allowed to create one for this contract's execution path.
|
||||
*/
|
||||
/**
|
||||
* Blocks starting a NEW booking (create/initiate) once the contract has
|
||||
* lapsed, and lazily flips the stored status to EXPIRED so it doesn't wait
|
||||
* for the nightly sweep. Only for the "start something new" entry points —
|
||||
* a booking already underway (completeUnderContract) must be allowed to
|
||||
* finish even if the contract expires mid-flight.
|
||||
*/
|
||||
private async assertNotExpired(contract: Contract): Promise<void> {
|
||||
if (!isEffectivelyExpired(contract)) return;
|
||||
if (contract.status !== 'EXPIRED') {
|
||||
const flipped = await this.contractsRepository.expireIfLapsed(contract.id);
|
||||
if (flipped) contract.status = 'EXPIRED';
|
||||
}
|
||||
throw new BadRequestException('Contract validity has expired — no new bookings.');
|
||||
}
|
||||
|
||||
private async assertGate(
|
||||
contract: Contract,
|
||||
isGlActor: boolean,
|
||||
|
||||
Reference in New Issue
Block a user