@@ -1205,6 +1205,7 @@ pub(in crate::views) struct TrackOccupancyForm {
12051205 train_schedule_ids : Vec < i64 > ,
12061206 operational_point_reference : OperationalPointReference ,
12071207 infra_id : i64 ,
1208+ timetable_id : i64 ,
12081209 electrical_profile_set_id : Option < i64 > ,
12091210 use_simulation : bool ,
12101211}
@@ -1253,6 +1254,7 @@ pub(in crate::views) async fn track_occupancy(
12531254 train_schedule_ids,
12541255 operational_point_reference,
12551256 infra_id,
1257+ timetable_id,
12561258 electrical_profile_set_id,
12571259 use_simulation,
12581260 } ) : Json < TrackOccupancyForm > ,
@@ -1281,17 +1283,32 @@ pub(in crate::views) async fn track_occupancy(
12811283 let conn = & mut db_pool. get ( ) . await ?;
12821284
12831285 let train_schedules: Vec < models:: TrainSchedule > =
1284- models:: TrainSchedule :: retrieve_batch_or_fail ( conn, train_schedule_ids, |missing| {
1285- TrainScheduleError :: BatchNotFound {
1286+ models:: TrainSchedule :: retrieve_batch_or_fail (
1287+ conn,
1288+ train_schedule_ids. clone ( ) ,
1289+ |missing| TrainScheduleError :: BatchNotFound {
12861290 count : missing. len ( ) ,
1287- }
1288- } )
1291+ } ,
1292+ )
12891293 . await ?;
12901294
1291- // Collect all occurrences from all paced trains using iter_occurrences()
1295+ let mut exceptions = TrainScheduleException :: retrieve_exceptions_by_train_schedules (
1296+ conn,
1297+ timetable_id,
1298+ train_schedule_ids,
1299+ )
1300+ . await ?
1301+ . into_iter ( )
1302+ . map_into :: < schemas:: TrainScheduleException > ( )
1303+ . into_group_map_by ( |e| e. timetable_id ) ;
1304+
12921305 let ( train_ids, trains) : ( Vec < _ > , Vec < _ > ) = train_schedules
12931306 . iter ( )
1294- . flat_map ( |train_schedule| train_schedule. iter_occurrences ( ) )
1307+ . flat_map ( |train_schedule| {
1308+ train_schedule
1309+ . iter_occurrences_v2 ( & exceptions. remove ( & train_schedule. id ) . unwrap_or_default ( ) )
1310+ . collect :: < Vec < _ > > ( )
1311+ } )
12951312 . unzip ( ) ;
12961313
12971314 let op_location =
@@ -1649,7 +1666,6 @@ mod tests {
16491666 use schemas:: fixtures:: simple_created_exception_with_change_groups;
16501667 use schemas:: fixtures:: simple_modified_exception_with_change_groups;
16511668 use schemas:: infra:: Direction ;
1652- use schemas:: paced_train:: ExceptionType ;
16531669 use schemas:: paced_train:: InitialSpeedChangeGroup ;
16541670 use schemas:: paced_train:: Paced ;
16551671 use schemas:: paced_train:: PacedTrainException ;
@@ -2751,7 +2767,7 @@ mod tests {
27512767 }
27522768
27532769 async fn init_paced_train_test (
2754- exceptions : Vec < PacedTrainException > ,
2770+ with_exception : bool ,
27552771 path : Vec < PathItem > ,
27562772 schedule : Vec < ScheduleItem > ,
27572773 operational_point_reference : OperationalPointReference ,
@@ -2771,46 +2787,77 @@ mod tests {
27712787 let small_infra = create_small_infra ( & mut db_pool. get_ok ( ) ) . await ;
27722788 let rolling_stock =
27732789 create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , "simulation_rolling_stock" ) . await ;
2774- let train_schedule_set = create_train_schedule_set ( & mut db_pool. get_ok ( ) ) . await ;
2775- let paced_train = models:: TrainSchedule :: default ( )
2790+ let ( timetable, train_schedule_set) =
2791+ create_timetable_with_train_schedule_set ( & mut db_pool. get_ok ( ) ) . await ;
2792+ let train_schedule = models:: TrainSchedule :: default ( )
27762793 . into_changeset ( )
27772794 . train_schedule_set_id ( train_schedule_set. id )
27782795 . rolling_stock_name ( rolling_stock. name )
27792796 . path ( path)
27802797 . schedule ( schedule)
27812798 . interval ( TimeDelta :: try_minutes ( 15 ) )
27822799 . time_window ( TimeDelta :: try_hours ( 1 ) )
2783- . exceptions ( exceptions)
27842800 . create ( & mut db_pool. get_ok ( ) )
27852801 . await
27862802 . expect ( "Failed to create paced train" ) ;
27872803
2804+ if with_exception {
2805+ TrainScheduleException :: changeset ( )
2806+ . timetable_id ( timetable. id )
2807+ . train_schedule_id ( train_schedule. id )
2808+ . change_groups ( TrainScheduleExceptionChangeGroups :: default ( ) )
2809+ . create ( & mut db_pool. get_ok ( ) )
2810+ . await
2811+ . expect ( "Failed to create exception" ) ;
2812+ }
2813+
27882814 let request = app
27892815 . post ( "/train_schedules/track_occupancy" )
27902816 . json ( & TrackOccupancyForm {
2791- train_schedule_ids : vec ! [ paced_train . id] ,
2817+ train_schedule_ids : vec ! [ train_schedule . id] ,
27922818 operational_point_reference,
27932819 infra_id : small_infra. id ,
2820+ timetable_id : timetable. id ,
27942821 electrical_profile_set_id : None ,
27952822 use_simulation,
27962823 } ) ;
27972824
27982825 app. fetch ( request) . await
27992826 }
28002827
2801- #[ rstest]
28022828 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2803- #[ case( vec![ ] , 4 ) ]
2829+ async fn paced_train_track_occupancy_without_exceptions ( ) {
2830+ let response = init_paced_train_test (
2831+ false ,
2832+ vec ! [
2833+ PathItem :: new_operational_point( "Mid_West_station" ) ,
2834+ PathItem :: new_operational_point( "Mid_East_station" ) ,
2835+ ] ,
2836+ vec ! [ ScheduleItem :: new_with_stop(
2837+ "Mid_East_station" ,
2838+ Duration :: new( 0 , 0 ) . expect( "Failed to parse duration" ) ,
2839+ ) ] ,
2840+ OperationalPointReference :: Id {
2841+ operational_point : "Mid_West_station" . into ( ) ,
2842+ } ,
2843+ true ,
2844+ ) ;
2845+ let track_occupancies: Vec < TrackSectionOccupancy > =
2846+ response. await . assert_status ( StatusCode :: OK ) . json_into ( ) ;
2847+
2848+ assert_eq ! ( track_occupancies. len( ) , 1 ) ;
2849+ let item = & track_occupancies[ 0 ] ;
2850+ assert_eq ! (
2851+ item. local_track_name,
2852+ Some ( NonBlankString ( "V2" . to_string( ) ) )
2853+ ) ;
2854+ assert_eq ! ( item. trains. len( ) , 4 ) ;
2855+ }
2856+
28042857 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2805- #[ case( vec![
2806- PacedTrainException { exception_type: ExceptionType :: Created { } , ..Default :: default ( ) } ] , 5 )
2807- ]
2808- async fn paced_train_track_occupancy (
2809- #[ case] exceptions : Vec < PacedTrainException > ,
2810- #[ case] paced_trains : usize ,
2811- ) {
2858+ async fn paced_train_track_occupancy_with_exceptions ( ) {
28122859 let response = init_paced_train_test (
2813- exceptions ,
2860+ true ,
28142861 vec ! [
28152862 PathItem :: new_operational_point( "Mid_West_station" ) ,
28162863 PathItem :: new_operational_point( "Mid_East_station" ) ,
@@ -2833,13 +2880,13 @@ mod tests {
28332880 item. local_track_name,
28342881 Some ( NonBlankString ( "V2" . to_string( ) ) )
28352882 ) ;
2836- assert_eq ! ( item. trains. len( ) , paced_trains ) ;
2883+ assert_eq ! ( item. trains. len( ) , 5 ) ;
28372884 }
28382885
28392886 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
28402887 async fn paced_train_returns_empty_track_occupancies ( ) {
28412888 let response = init_paced_train_test (
2842- Vec :: new ( ) ,
2889+ false ,
28432890 vec ! [
28442891 PathItem :: new_operational_point( "Mid_West_station" ) ,
28452892 PathItem :: new_operational_point( "Mid_East_station" ) ,
@@ -2897,7 +2944,7 @@ mod tests {
28972944 #[ case] use_simulation : bool ,
28982945 ) {
28992946 let response = init_paced_train_test (
2900- Vec :: new ( ) ,
2947+ false ,
29012948 vec ! [
29022949 new_op_with_trigram_and_local_track_name( "Mid_West_station" , "MWS" , None , None ) ,
29032950 new_op_with_trigram_and_local_track_name(
@@ -2959,7 +3006,7 @@ mod tests {
29593006 ) ,
29603007 } ;
29613008 let response = init_paced_train_test (
2962- Vec :: new ( ) ,
3009+ false ,
29633010 vec ! [
29643011 first_path_item,
29653012 PathItem :: new_operational_point( "Mid_East_station" ) ,
@@ -2984,7 +3031,7 @@ mod tests {
29843031 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
29853032 async fn track_occupancy_no_sim_no_arrival_returns_empty ( ) {
29863033 let response = init_paced_train_test (
2987- Vec :: new ( ) ,
3034+ false ,
29883035 vec ! [
29893036 PathItem :: new_operational_point( "Mid_West_station" ) ,
29903037 PathItem :: new_operational_point( "Mid_East_station" ) ,
@@ -3131,7 +3178,7 @@ mod tests {
31313178 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
31323179 async fn track_occupancy_op_in_db_but_not_in_path_items ( ) {
31333180 let response = init_paced_train_test (
3134- Vec :: new ( ) ,
3181+ false ,
31353182 vec ! [
31363183 PathItem :: new_operational_point( "South_West_station" ) ,
31373184 PathItem :: new_operational_point( "Mid_East_station" ) ,
0 commit comments