@@ -8,7 +8,7 @@ use crate::format::UnknownLocationFallback;
88use crate :: get_unique_locations;
99use crate :: utils:: get_approx_transportation;
1010use std:: collections:: HashSet ;
11- use vrp_core:: construction:: enablers:: create_typed_actor_groups;
11+ use vrp_core:: construction:: enablers:: { FirstJobArrivalFloorDimension , create_typed_actor_groups} ;
1212use vrp_core:: construction:: features:: { VehicleCapacityDimension , VehicleSkillsDimension } ;
1313use vrp_core:: models:: common:: * ;
1414use vrp_core:: models:: problem:: * ;
@@ -111,31 +111,36 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co
111111 let index = * profile_indices. get ( & vehicle. profile . matrix ) . unwrap ( ) ;
112112 let profile = Profile :: new ( index, vehicle. profile . scale ) ;
113113
114- let tour_size = vehicle. limits . as_ref ( ) . and_then ( |l| l. tour_size ) ;
114+ let tour_size = vehicle. limits . as_ref ( ) . and_then ( |limits| limits. tour_size ) ;
115+
116+ let allow_out_of_hours_depot_travel =
117+ vehicle. limits . as_ref ( ) . and_then ( |limits| limits. allow_out_of_hours_depot_travel ) . unwrap_or ( false ) ;
115118
116119 for ( shift_index, shift) in vehicle. shifts . iter ( ) . enumerate ( ) {
117- let start = {
118- let location = coord_index. get_by_loc ( & shift. start . location ) . unwrap ( ) ;
119- let earliest = parse_time ( & shift. start . earliest ) ;
120- let latest = shift. start . latest . as_ref ( ) . map ( |time| parse_time ( time) ) ;
121- ( location, earliest, latest)
120+ let shift_start_earliest = parse_time ( & shift. start . earliest ) ;
121+ let shift_start_latest = shift. start . latest . as_ref ( ) . map ( |time| parse_time ( time) ) ;
122+
123+ // When `allow_out_of_hours_depot_travel` is set, the shift start applies to the first
124+ // job's arrival rather than to the depot departure. Relax the depot start window;
125+ // the floor is re-applied in `apply_first_job_arrival_floor` (via `FirstJobArrivalFloor`
126+ // dim) and `start.latest` is enforced in the travel-limit constraint. The end side
127+ // is NOT relaxed — the back-propagated `latest_arrival` bounds the last-job departure.
128+ let start_time = if allow_out_of_hours_depot_travel {
129+ TimeInterval { earliest : None , latest : None }
130+ } else {
131+ TimeInterval { earliest : Some ( shift_start_earliest) , latest : shift_start_latest }
122132 } ;
133+ let start_location = coord_index. get_by_loc ( & shift. start . location ) . unwrap ( ) ;
123134
124135 let end = shift. end . as_ref ( ) . map ( |end| {
125136 let location = coord_index. get_by_loc ( & end. location ) . unwrap ( ) ;
126- let time = parse_time ( & end. latest ) ;
127- ( location, time)
137+ let time = TimeInterval { earliest : None , latest : Some ( parse_time ( & end. latest ) ) } ;
138+ VehiclePlace { location, time }
128139 } ) ;
129140
130141 let details = vec ! [ VehicleDetail {
131- start: Some ( VehiclePlace {
132- location: start. 0 ,
133- time: TimeInterval { earliest: Some ( start. 1 ) , latest: start. 2 } ,
134- } ) ,
135- end: end. map( |( location, time) | VehiclePlace {
136- location,
137- time: TimeInterval { earliest: None , latest: Some ( time) } ,
138- } ) ,
142+ start: Some ( VehiclePlace { location: start_location, time: start_time } ) ,
143+ end,
139144 } ] ;
140145
141146 vehicle. vehicle_ids . iter ( ) . for_each ( |vehicle_id| {
@@ -150,6 +155,13 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co
150155 dimens. set_tour_size ( tour_size) ;
151156 }
152157
158+ if allow_out_of_hours_depot_travel {
159+ dimens. set_first_job_arrival_floor ( shift_start_earliest) ;
160+ if let Some ( latest) = shift_start_latest {
161+ dimens. set_shift_start_latest ( latest) ;
162+ }
163+ }
164+
153165 if props. has_multi_dimen_capacity {
154166 dimens. set_vehicle_capacity ( MultiDimLoad :: new ( vehicle. capacity . clone ( ) ) ) ;
155167 } else {
0 commit comments