add dispute functionality for contract duty and implement collection dates

This commit is contained in:
Marshal
2026-07-26 17:30:00 +00:00
parent 5e10c97294
commit bfea9de660
4 changed files with 185 additions and 30 deletions

View File

@@ -7449,8 +7449,8 @@ export class TrainSchedulingService {
// Target: a slot of this train set, or an empty consist-only wagon of the
// built train (physical wagon with no slot row yet).
const targetSlot = slots.find((w) => w.id === dto.targetWagonId) ?? null;
const consistWagon = targetSlot
const slotById = slots.find((w) => w.id === dto.targetWagonId) ?? null;
const wagonForTarget = slotById
? null
: schedule.trainSet?.trainId
? await this.dataSource.getRepository(Wagon).findOne({
@@ -7458,10 +7458,22 @@ export class TrainSchedulingService {
relations: { wagonType: true },
})
: null;
if (!targetSlot && !consistWagon) {
if (!slotById && !wagonForTarget) {
throw new NotFoundException('Target wagon is not part of this schedule');
}
// A physical wagon holds at most one slot. When the caller addressed the
// wagon directly but a slot is already pinned to it, move into that slot
// rather than minting a second one on the same wagon.
const targetSlot =
slotById ??
(wagonForTarget
? (slots.find((w) => w.physicalWagonId === wagonForTarget.id) ?? null)
: null);
const consistWagon = targetSlot ? null : wagonForTarget;
const targetAllocs = targetSlot ? await loadAllocations(targetSlot.id) : [];
if (targetSlot && targetSlot.id === source.id) {
return this.getTrainScheduleById(scheduleId);
}
const loadTypesOf = (allocs: WagonBookingAllocation[]) => [
...new Set(allocs.map((a) => (a.loadType ?? 'CONTAINER').toUpperCase())),
@@ -7525,19 +7537,6 @@ export class TrainSchedulingService {
const slotRepo = manager.getRepository(TrainSetWagon);
const allocs = manager.getRepository(WagonBookingAllocation);
// Empty consist wagon: repin the loaded slot onto that physical wagon.
// Allocations and load fields stay put; only the wagon identity changes.
if (consistWagon) {
await slotRepo.update(source.id, {
physicalWagonId: consistWagon.id,
wagonTypeId: consistWagon.wagonTypeId,
capacityTons: roundTons(Number(consistWagon.wagonType?.capacityTons ?? source.capacityTons)),
lengthMeters: roundTons(Number(consistWagon.wagonType?.lengthMeters ?? source.lengthMeters)),
});
return;
}
const target = targetSlot as TrainSetWagon;
// Load-coupled slot fields travel with the load; wagon identity stays.
const loadFieldsOf = (slot: TrainSetWagon) => ({
assignedWeightTons: slot.assignedWeightTons,
@@ -7552,6 +7551,47 @@ export class TrainSchedulingService {
alightYardId: null,
};
const sourceLoadFields = loadFieldsOf(source);
// Empty consist wagon with no slot row yet: give it one, then move the
// load into it. Repinning the SOURCE slot onto that wagon would have been
// fewer writes, but it renames the wagons instead of moving the load —
// the loaded slot becomes wagon B and B's identity pops out as an empty
// wagon where A used to be. Staff read that as the train re-ordering
// itself. A wagon must never change place because a container moved.
if (consistWagon) {
const { maxSequenceNo } = (await slotRepo
.createQueryBuilder('slot')
.select('COALESCE(MAX(slot.sequence_no), 0)', 'maxSequenceNo')
.where('slot.train_set_id = :trainSetId', { trainSetId: source.trainSetId })
.getRawOne<{ maxSequenceNo: string | number }>()) ?? { maxSequenceNo: 0 };
const created = await slotRepo.save(
slotRepo.create({
trainSetId: source.trainSetId,
wagonTypeId: consistWagon.wagonTypeId,
physicalWagonId: consistWagon.id,
// Plan-order key only — the consist is drawn in the train's coupling
// order (wagons.sequence_number), so appending here moves nothing.
// It just has to clear the (train_set_id, sequence_no) unique index.
sequenceNo: Number(maxSequenceNo) + 1,
capacityTons: roundTons(
Number(consistWagon.wagonType?.capacityTons ?? source.capacityTons),
),
lengthMeters: roundTons(
Number(consistWagon.wagonType?.lengthMeters ?? source.lengthMeters),
),
...sourceLoadFields,
}),
);
for (const alloc of sourceAllocs) {
await allocs.update(alloc.id, { trainSetWagonId: created.id });
}
await slotRepo.update(source.id, emptyLoadFields);
return;
}
const target = targetSlot as TrainSetWagon;
const targetLoadFields = targetAllocs.length ? loadFieldsOf(target) : emptyLoadFields;
for (const alloc of sourceAllocs) {