import type { TFunction } from 'i18next'; import { compact } from 'lodash'; import type { Dispatch } from 'redux'; import type { PathfindingItem, PostTimetableByIdStdcmApiArg } from 'common/api/osrdEditoastApi'; import getStepLocation from 'modules/pathfinding/helpers/getStepLocation'; import { setFailure } from 'reducers/main'; import type { OsrdStdcmConfState, StandardAllowance } from 'reducers/osrdconf/types'; import { dateTimeFormatting } from 'utils/date'; import { kmhToMs, tToKg } from 'utils/physics'; import { minToMs, sec2ms } from 'utils/timeManipulation'; import createMargin from './createMargin'; import { StdcmStopTypes } from '../types'; type ValidStdcmConfig = { rollingStockId: number; towedRollingStockID?: number; timetableId: number; infraId: number; path: PathfindingItem[]; speedLimitByTag?: string; totalMass?: number; totalLength?: number; maxSpeed?: number; margin?: StandardAllowance; gridMarginBefore?: number; gridMarginAfter?: number; workScheduleGroupId?: number; temporarySpeedLimitGroupId?: number; electricalProfileSetId?: number; }; export const checkStdcmConf = ( dispatch: Dispatch, t: TFunction, osrdconf: OsrdStdcmConfState ): ValidStdcmConfig | null => { const { stdcmPathSteps: pathSteps, timetableID, speedLimitByTag, infraID, rollingStockID, towedRollingStockID, margins: { standardAllowance, gridMarginBefore, gridMarginAfter }, searchDatetimeWindow, workScheduleGroupId, temporarySpeedLimitGroupId, electricalProfileSetId, totalLength, totalMass, maxSpeed, } = osrdconf; let error = false; const origin = pathSteps.at(0)!; if (origin.isVia) { throw new Error('First step should not be a via'); } const destination = pathSteps.at(-1)!; if (destination.isVia) { throw new Error('First step should not be a via'); } if (!origin.location) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.noOrigin'), }) ); } if (!destination.location) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.noDestination'), }) ); } if (!rollingStockID) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.noRollingStock'), }) ); } if (!infraID) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.noName'), }) ); } if (!timetableID) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.noTimetable'), }) ); } const originArrival = origin.arrival; const destinationArrival = destination.arrival; const isDepartureScheduled = origin.arrivalType === 'preciseTime'; const startDateTime = isDepartureScheduled ? new Date(originArrival!) : new Date(destinationArrival!); if ( searchDatetimeWindow && startDateTime && (startDateTime < searchDatetimeWindow.begin || searchDatetimeWindow.end < startDateTime) ) { error = true; dispatch( setFailure({ name: t('operationalStudies/manageTrainSchedule:errorMessages.trainScheduleTitle'), message: t('operationalStudies/manageTrainSchedule:errorMessages.originTimeOutsideWindow', { low: dateTimeFormatting(searchDatetimeWindow.begin, false), high: dateTimeFormatting(searchDatetimeWindow.end, false), }), }) ); } if (pathSteps.some((step) => !step.location)) { error = true; dispatch( setFailure({ name: t('stdcm:form.incompleteForm'), message: t('stdcm:form.viaNotDefined'), }) ); } if (error) return null; const path = compact(osrdconf.stdcmPathSteps).map((step) => { const location = getStepLocation(step.location!); let timingData: PathfindingItem['timing_data'] | undefined; let duration: number | undefined; if (step.isVia) { const { stopFor } = step; if (step.stopType !== StdcmStopTypes.PASSAGE_TIME && stopFor !== undefined) { duration = minToMs(stopFor); } } else { // if the step is either the origin or the destination, // it must have a duration (because it's a stop) duration = 0; const { arrival, tolerances, arrivalType } = step; if (arrivalType === 'preciseTime' && arrival) { timingData = { arrival_time: arrival.toISOString(), arrival_time_tolerance_before: tolerances.before.ms, arrival_time_tolerance_after: tolerances.after.ms, }; } } return { duration, location, timing_data: timingData, }; }); return { infraId: infraID!, rollingStockId: rollingStockID!, timetableId: timetableID!, path, speedLimitByTag, totalMass, totalLength, maxSpeed, towedRollingStockID, margin: standardAllowance, gridMarginBefore, gridMarginAfter, workScheduleGroupId, temporarySpeedLimitGroupId, electricalProfileSetId, }; }; const toMsOrUndefined = (value: number | undefined): number | undefined => value ? sec2ms(value) : undefined; export const formatStdcmPayload = ( validConfig: ValidStdcmConfig ): PostTimetableByIdStdcmApiArg => ({ infra: validConfig.infraId, id: validConfig.timetableId, body: { comfort: 'STANDARD', margin: createMargin(validConfig.margin), rolling_stock_id: validConfig.rollingStockId, towed_rolling_stock_id: validConfig.towedRollingStockID, speed_limit_tags: validConfig.speedLimitByTag, total_mass: validConfig.totalMass ? tToKg(validConfig.totalMass) : undefined, max_speed: validConfig.maxSpeed ? kmhToMs(validConfig.maxSpeed) : undefined, total_length: validConfig.totalLength, steps: validConfig.path, time_gap_after: toMsOrUndefined(validConfig.gridMarginBefore), time_gap_before: toMsOrUndefined(validConfig.gridMarginAfter), work_schedule_group_id: validConfig.workScheduleGroupId, temporary_speed_limit_group_id: validConfig.temporarySpeedLimitGroupId, electrical_profile_set_id: validConfig.electricalProfileSetId, loading_gauge_type: 'GA', // default value as the user can't select one }, });