diff --git a/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts b/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts index 7312df2da..1fc0da319 100644 --- a/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts +++ b/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts @@ -5007,6 +5007,76 @@ export class TrainSchedulingService { await manager.getRepository(ScheduleWagonAdjustmentLog).save(coupleLogRows); } } + + // Leg slots (booking legs boarding/alighting mid-corridor — see + // stampSlotLegs) reaching their board/alight yard here: logged same as + // planned couples/cuts above, so the marshalling document can show + // what coupled ALREADY LOADED / uncoupled WITH cargo at this stop. + // Purely observational — their physical wagon was already pinned to + // the slot at schedule-build time (assignPhysicalWagonsToSlots), so + // nothing here changes wagon state, only the log. Dedupe against + // existing rows (not a wagon-state flag, unlike the couple/cut blocks + // above) since passedYardIds re-includes earlier stops on every call. + const legSlotsHere = (schedule.trainSet?.wagons ?? []).filter( + (slot) => + slot.physicalWagonId && + ((slot.boardYardId && passedYardIds.includes(slot.boardYardId)) || + (slot.alightYardId && passedYardIds.includes(slot.alightYardId))), + ); + if (legSlotsHere.length && builtTrainId) { + const legWagonIds = [ + ...new Set(legSlotsHere.map((slot) => slot.physicalWagonId!)), + ]; + const legWagonById = new Map( + ( + await manager.getRepository(Wagon).find({ where: { id: In(legWagonIds) } }) + ).map((w) => [w.id, w]), + ); + const alreadyLogged = new Set( + ( + await manager.getRepository(ScheduleWagonAdjustmentLog).find({ + where: { + trainScheduleId: scheduleId, + wagonId: In(legWagonIds), + action: In(['ADD', 'REMOVE']), + }, + }) + ).map((row) => `${row.wagonId}:${row.action}:${row.yardId}`), + ); + const legLogRows: ScheduleWagonAdjustmentLog[] = []; + for (const slot of legSlotsHere) { + const wagon = legWagonById.get(slot.physicalWagonId!); + if (!wagon) continue; + const events: Array<{ action: 'ADD' | 'REMOVE'; yardId: string }> = []; + if (slot.boardYardId && passedYardIds.includes(slot.boardYardId)) { + events.push({ action: 'ADD', yardId: slot.boardYardId }); + } + if (slot.alightYardId && passedYardIds.includes(slot.alightYardId)) { + events.push({ action: 'REMOVE', yardId: slot.alightYardId }); + } + for (const { action, yardId } of events) { + const key = `${wagon.id}:${action}:${yardId}`; + if (alreadyLogged.has(key)) continue; + alreadyLogged.add(key); + legLogRows.push( + manager.getRepository(ScheduleWagonAdjustmentLog).create({ + trainScheduleId: scheduleId, + trainId: builtTrainId, + action, + wagonId: wagon.id, + wagonNumber: wagon.wagonNumber, + adjustedByUserId: null, + yardId, + occurredAt, + }), + ); + } + } + if (legLogRows.length) { + await manager.getRepository(ScheduleWagonAdjustmentLog).save(legLogRows); + } + } + await manager .getRepository(Wagon) .createQueryBuilder()