mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 12:30:58 +00:00
Merge pull request #1006 from Tria-plc/freight_feature/usermanagement
Add ContractCourtBadge component and integrate into contract pages
This commit is contained in:
@@ -32,8 +32,11 @@ import { TrainScheduleBooking } from '../train-schedules/entities/train-schedule
|
||||
import { TrainSchedulesRepository } from '../train-schedules/train-schedules.repository';
|
||||
import { TrainScheduleBookingsRepository } from '../train-schedules/train-schedule-bookings.repository';
|
||||
import { BookingNotifierService } from './booking-notifier.service';
|
||||
import { TrainSchedulingService } from './train-scheduling.service';
|
||||
import { eatDay } from './batch-window.util';
|
||||
import {
|
||||
TrainSchedulingService,
|
||||
effectiveWindowConfig,
|
||||
} from './train-scheduling.service';
|
||||
import { eatDay, listConfigBookingWindows } from './batch-window.util';
|
||||
import {
|
||||
BATCH_BOARD_STATUSES,
|
||||
BatchBoardQueryDto,
|
||||
@@ -167,6 +170,13 @@ export type BookingAllocationStatus =
|
||||
| "FAILED";
|
||||
|
||||
export interface BatchBoardBookingDetail extends BatchBoardBooking {
|
||||
/**
|
||||
* 0-based booking-window cycle this booking entered the pool in (derived from
|
||||
* `fullyExecutedAt` against the schedule's window cycles). Ranking compares
|
||||
* bookings within a cycle only — an earlier cycle always boards before a later
|
||||
* one regardless of score. Null while the contract is still pending.
|
||||
*/
|
||||
windowCycleNo: number | null;
|
||||
fullyExecutedAt: string | null;
|
||||
selectedForBatchAt: string | null;
|
||||
allocationStatus: BookingAllocationStatus;
|
||||
@@ -1321,10 +1331,12 @@ export class BookingBatchService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
const cycleOf = await this.windowCycleIndexer(s);
|
||||
const items: BatchBoardBookingDetail[] = bookings.map((b) => {
|
||||
const need = this.needFor(b, wagonDims);
|
||||
const alloc = allocationByBooking.get(b.id);
|
||||
return {
|
||||
windowCycleNo: b.fullyExecutedAt ? cycleOf(b.fullyExecutedAt) : null,
|
||||
id: b.id,
|
||||
reference: b.reference ?? b.id.slice(0, 8),
|
||||
company: b.isGovernment
|
||||
@@ -1632,7 +1644,7 @@ export class BookingBatchService implements OnModuleInit {
|
||||
// Same bulk re-score as fillRouteDayInternal — the legacy per-schedule fill
|
||||
// must rank bulk bookings by their wagon-derived priority too.
|
||||
await this.recomputeBulkPriorities(pool, wagonDims);
|
||||
this.resortPoolByPriority(pool);
|
||||
this.resortPoolByPriority(pool, await this.windowCycleIndexer(schedule));
|
||||
const units = this.groupConsolidatedPool(pool);
|
||||
let armed = false;
|
||||
let preempted = false;
|
||||
@@ -1847,6 +1859,9 @@ export class BookingBatchService implements OnModuleInit {
|
||||
armed: boolean;
|
||||
changed: boolean;
|
||||
}> = [];
|
||||
// The day group shares one booking window (route+day grouping), so any
|
||||
// member's window grid stands for the pool's cycle derivation.
|
||||
let cycleSchedule: TrainSchedule | null = null;
|
||||
for (const id of scheduleIds) {
|
||||
const schedule =
|
||||
await this.trainSchedulesRepository.findByIdWithFullGraph(id);
|
||||
@@ -1857,6 +1872,7 @@ export class BookingBatchService implements OnModuleInit {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
cycleSchedule ??= schedule;
|
||||
const limits = await this.capacityLimits(locomotive);
|
||||
await this.syncScheduleMaxWagons(schedule, locomotive);
|
||||
const budget = await this.remainingBudget(schedule, limits, wagonDims);
|
||||
@@ -1877,7 +1893,10 @@ export class BookingBatchService implements OnModuleInit {
|
||||
// BULK bookings only get their real (wagon-derived) priority score now, at
|
||||
// batch time — stamp it and re-rank before the fill consumes the pool.
|
||||
await this.recomputeBulkPriorities(pool, wagonDims);
|
||||
this.resortPoolByPriority(pool);
|
||||
this.resortPoolByPriority(
|
||||
pool,
|
||||
cycleSchedule ? await this.windowCycleIndexer(cycleSchedule) : undefined,
|
||||
);
|
||||
// Consolidated partners collapse into one atomic unit (both-or-neither); a
|
||||
// consolidated booking whose partner isn't ready this cycle is skipped.
|
||||
const units = this.groupConsolidatedPool(pool);
|
||||
@@ -3257,11 +3276,66 @@ export class BookingBatchService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore the batch pool ordering (mirrors findBatchPool's ORDER BY) after scores changed. */
|
||||
private resortPoolByPriority(pool: Booking[]): void {
|
||||
/**
|
||||
* Maps a booking's pool-entry time (`fullyExecutedAt`) to the 0-based
|
||||
* booking-window cycle it arrived in: the last window whose open is at/before
|
||||
* the timestamp (a timestamp in the doc-review/payment gap belongs to the
|
||||
* cycle that just closed). The cycle grid comes from the schedule's frozen
|
||||
* window-rule snapshot — the exact windows the cycle engine runs.
|
||||
*/
|
||||
private async windowCycleIndexer(
|
||||
schedule: TrainSchedule,
|
||||
): Promise<(ts: Date | null | undefined) => number> {
|
||||
if (!schedule.scheduledDepartureDate) return () => 0;
|
||||
let starts: number[];
|
||||
try {
|
||||
const liveCfg = await this.trainSchedulingService.getWindowConfig();
|
||||
const cfg = effectiveWindowConfig(schedule, liveCfg);
|
||||
const windows = listConfigBookingWindows(
|
||||
schedule.direction,
|
||||
schedule.scheduledDepartureDate,
|
||||
{
|
||||
...cfg,
|
||||
reopenGapMinutes:
|
||||
schedule.ruleReopenDelayMinutes ??
|
||||
cfg.docReviewMinutes + cfg.paymentWindowMinutes,
|
||||
},
|
||||
);
|
||||
starts = windows.map((w) => w.start.getTime());
|
||||
} catch (err) {
|
||||
// A failed cycle derivation must never block the batch — fall back to one
|
||||
// flat cycle (pure priority order, the old behaviour).
|
||||
this.logger.warn(
|
||||
`Window-cycle derivation failed for schedule ${schedule.id}: ` +
|
||||
`${(err as Error).message}`,
|
||||
);
|
||||
return () => 0;
|
||||
}
|
||||
return (ts) => {
|
||||
if (!ts) return 0;
|
||||
const ms = ts.getTime();
|
||||
let idx = 0;
|
||||
for (let i = 0; i < starts.length; i += 1) {
|
||||
if (ms >= starts[i]) idx = i;
|
||||
}
|
||||
return idx;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rank the batch pool: government first, then WINDOW CYCLE (bookings compete
|
||||
* only within the cycle they arrived in — an earlier cycle's booking always
|
||||
* outranks a later cycle's, whatever the scores), then priority score, then
|
||||
* oldest. `cycleOf` comes from {@link windowCycleIndexer}.
|
||||
*/
|
||||
private resortPoolByPriority(
|
||||
pool: Booking[],
|
||||
cycleOf: (ts: Date | null | undefined) => number = () => 0,
|
||||
): void {
|
||||
pool.sort(
|
||||
(a, b) =>
|
||||
Number(b.isGovernment) - Number(a.isGovernment) ||
|
||||
cycleOf(a.fullyExecutedAt) - cycleOf(b.fullyExecutedAt) ||
|
||||
Number(b.priorityScore ?? 0) - Number(a.priorityScore ?? 0) ||
|
||||
(a.fullyExecutedAt?.getTime() ?? Infinity) -
|
||||
(b.fullyExecutedAt?.getTime() ?? Infinity) ||
|
||||
|
||||
Reference in New Issue
Block a user