mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 00:45:41 +00:00
add Excel import functionality for container bookings
This commit is contained in:
@@ -83,6 +83,24 @@ export class ContractBookingService {
|
||||
const contract = await this.contractsRepository.findByIdWithRelations(contractId);
|
||||
if (!contract) throw new NotFoundException(`Contract ${contractId} not found`);
|
||||
|
||||
// A contract whose quantity cap was fully booked is completed — no further
|
||||
// bookings, even while contract validity and a booking window are still
|
||||
// open. Capacity released after closure (a cancelled/expired booking)
|
||||
// reopens the contract on the next booking attempt.
|
||||
if (contract.status === 'CONTRACT_CLOSED') {
|
||||
const capacity = await this.computeCapacity(contract);
|
||||
const hasRoom = capacity.some((c) => c.remaining == null || c.remaining > 0);
|
||||
if (!hasRoom) {
|
||||
throw new BadRequestException(
|
||||
'This contract is completed — the full contracted quantity has been booked.',
|
||||
);
|
||||
}
|
||||
await this.contractsRepository.update(contract.id, {
|
||||
status: 'CONTRACT_ACTIVE',
|
||||
} as never);
|
||||
contract.status = 'CONTRACT_ACTIVE';
|
||||
}
|
||||
|
||||
// GL Ethiopia is identified by the dedicated contract create-booking permission
|
||||
// (granted to the edr_gl_ethiopia preset).
|
||||
const isGlActor =
|
||||
@@ -291,6 +309,9 @@ export class ContractBookingService {
|
||||
if (!parked.paired) {
|
||||
// Waiting for a partner — stop here. The booking sits in
|
||||
// PENDING_CONSOLIDATION, unbilled and unscheduled, until it pairs.
|
||||
// A parked booking still holds contract capacity, so the cap may
|
||||
// already be exhausted by it.
|
||||
await this.maybeCompleteContract(contract);
|
||||
const pendingResult = await this.bookingsRepository.findByIdWithFiles(
|
||||
booking.id,
|
||||
);
|
||||
@@ -304,6 +325,8 @@ export class ContractBookingService {
|
||||
generalCustoms,
|
||||
);
|
||||
|
||||
await this.maybeCompleteContract(contract);
|
||||
|
||||
const result = await this.bookingsRepository.findByIdWithFiles(booking.id);
|
||||
return { booking: result ?? booking, warnings };
|
||||
}
|
||||
@@ -606,6 +629,44 @@ export class ContractBookingService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the contract once its quantity cap is fully consumed. Runs after
|
||||
* every booking created under a GENERAL contract (including a split remainder
|
||||
* being rebooked): when no capped scope line has capacity left, the contract
|
||||
* moves to CONTRACT_CLOSED even though its validity window is still open —
|
||||
* blocking further bookings and shipment requests, including inside an open
|
||||
* booking window. Never throws: a status hiccup must not undo the booking
|
||||
* that was just created.
|
||||
*/
|
||||
private async maybeCompleteContract(contract: Contract): Promise<void> {
|
||||
try {
|
||||
// ONE_TIME contracts are governed by the single-active-booking slot (and
|
||||
// are promoted to GENERAL on split), so only GENERAL completes by cap.
|
||||
if (contract.contractKind !== 'GENERAL') return;
|
||||
if (!['CONTRACT_ACTIVE', 'FULLY_EXECUTED'].includes(contract.status)) return;
|
||||
const capacity = await this.computeCapacity(contract);
|
||||
if (capacity.length === 0) return; // uncapped — completes only by expiry
|
||||
// 0.001 tolerance absorbs bulk-ton float rounding (split weights round to
|
||||
// 3 decimals); container caps are integers and unaffected.
|
||||
const exhausted = capacity.every(
|
||||
(c) => c.remaining != null && c.remaining <= 0.001,
|
||||
);
|
||||
if (!exhausted) return;
|
||||
await this.contractsRepository.update(contract.id, {
|
||||
status: 'CONTRACT_CLOSED',
|
||||
} as never);
|
||||
this.logger.log(
|
||||
`Contract ${contract.reference} quantity cap fully booked — completed; no further bookings within validity.`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Could not evaluate completion for contract ${contract.id}: ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quantities already booked under a contract that still hold capacity. Excludes
|
||||
* bookings that never shipped (CANCELLED / REJECTED / EXPIRED).
|
||||
|
||||
Reference in New Issue
Block a user