88using Resgrid . Model ;
99using Resgrid . Model . Services ;
1010using Resgrid . Providers . Claims ;
11+ using Resgrid . Framework ;
1112using Resgrid . Web . Areas . User . Models . Routes ;
1213
1314namespace Resgrid . Web . Areas . User . Controllers
@@ -19,13 +20,15 @@ public class RoutesController : SecureBaseController
1920 private readonly IUnitsService _unitsService ;
2021 private readonly ICallsService _callsService ;
2122 private readonly IContactsService _contactsService ;
23+ private readonly IDepartmentsService _departmentsService ;
2224
23- public RoutesController ( IRouteService routeService , IUnitsService unitsService , ICallsService callsService , IContactsService contactsService )
25+ public RoutesController ( IRouteService routeService , IUnitsService unitsService , ICallsService callsService , IContactsService contactsService , IDepartmentsService departmentsService )
2426 {
2527 _routeService = routeService ;
2628 _unitsService = unitsService ;
2729 _callsService = callsService ;
2830 _contactsService = contactsService ;
31+ _departmentsService = departmentsService ;
2932 }
3033
3134 [ HttpGet ]
@@ -52,6 +55,21 @@ public async Task<IActionResult> New()
5255 [ Authorize ( Policy = ResgridResources . Route_Create ) ]
5356 public async Task < IActionResult > New ( RouteNewView model , CancellationToken cancellationToken )
5457 {
58+ // Deserialize stops before saving anything so a bad payload does not leave an orphaned plan.
59+ List < PendingStopDto > pendingStops = null ;
60+ if ( ! string . IsNullOrWhiteSpace ( model . PendingStopsJson ) )
61+ {
62+ try
63+ {
64+ var options = new System . Text . Json . JsonSerializerOptions { PropertyNameCaseInsensitive = true } ;
65+ pendingStops = System . Text . Json . JsonSerializer . Deserialize < List < PendingStopDto > > ( model . PendingStopsJson , options ) ;
66+ }
67+ catch ( System . Text . Json . JsonException )
68+ {
69+ ModelState . AddModelError ( nameof ( model . PendingStopsJson ) , "Stop data is invalid and could not be parsed." ) ;
70+ }
71+ }
72+
5573 if ( ModelState . IsValid )
5674 {
5775 model . Plan . DepartmentId = DepartmentId ;
@@ -61,43 +79,52 @@ public async Task<IActionResult> New(RouteNewView model, CancellationToken cance
6179
6280 await _routeService . SaveRoutePlanAsync ( model . Plan , cancellationToken ) ;
6381
64- if ( ! string . IsNullOrWhiteSpace ( model . PendingStopsJson ) )
82+ var dept = await _departmentsService . GetDepartmentByIdAsync ( DepartmentId ) ;
83+ var deptTimeZone = ! string . IsNullOrWhiteSpace ( dept ? . TimeZone )
84+ ? DateTimeHelpers . WindowsToIana ( dept . TimeZone )
85+ : "UTC" ;
86+
87+ if ( pendingStops != null )
6588 {
66- var options = new System . Text . Json . JsonSerializerOptions { PropertyNameCaseInsensitive = true } ;
67- var pendingStops = System . Text . Json . JsonSerializer . Deserialize < List < PendingStopDto > > ( model . PendingStopsJson , options ) ;
68- if ( pendingStops != null )
89+ for ( int i = 0 ; i < pendingStops . Count ; i ++ )
6990 {
70- for ( int i = 0 ; i < pendingStops . Count ; i ++ )
91+ var ps = pendingStops [ i ] ;
92+
93+ string resolvedContactId = null ;
94+ if ( ! string . IsNullOrWhiteSpace ( ps . ContactId ) )
7195 {
72- var ps = pendingStops [ i ] ;
73- var stop = new RouteStop
74- {
75- RoutePlanId = model . Plan . RoutePlanId ,
76- Name = ps . Name ,
77- Description = ps . Description ,
78- StopType = ps . StopType ,
79- Priority = ps . Priority ,
80- Latitude = ps . Latitude ,
81- Longitude = ps . Longitude ,
82- Address = ps . Address ,
83- CallId = ps . CallId ,
84- EstimatedDwellMinutes = ps . DwellMinutes ,
85- ContactName = ps . ContactName ,
86- ContactNumber = ps . ContactNumber ,
87- ContactId = ps . ContactId ,
88- Notes = ps . Notes ,
89- StopOrder = i + 1 ,
90- AddedOn = DateTime . UtcNow ,
91- IsDeleted = false
92- } ;
93-
94- if ( ! string . IsNullOrWhiteSpace ( ps . PlannedArrival ) && DateTime . TryParse ( ps . PlannedArrival , out var arrivalDt ) )
95- stop . PlannedArrivalTime = arrivalDt . ToUniversalTime ( ) ;
96- if ( ! string . IsNullOrWhiteSpace ( ps . PlannedDeparture ) && DateTime . TryParse ( ps . PlannedDeparture , out var departureDt ) )
97- stop . PlannedDepartureTime = departureDt . ToUniversalTime ( ) ;
98-
99- await _routeService . SaveRouteStopAsync ( stop , cancellationToken ) ;
96+ var contact = await _contactsService . GetContactByIdAsync ( ps . ContactId ) ;
97+ if ( contact != null && contact . DepartmentId == DepartmentId )
98+ resolvedContactId = ps . ContactId ;
10099 }
100+
101+ var stop = new RouteStop
102+ {
103+ RoutePlanId = model . Plan . RoutePlanId ,
104+ Name = ps . Name ,
105+ Description = ps . Description ,
106+ StopType = ps . StopType ,
107+ Priority = ps . Priority ,
108+ Latitude = ps . Latitude ,
109+ Longitude = ps . Longitude ,
110+ Address = ps . Address ,
111+ CallId = ps . CallId ,
112+ EstimatedDwellMinutes = ps . DwellMinutes ,
113+ ContactName = ps . ContactName ,
114+ ContactNumber = ps . ContactNumber ,
115+ ContactId = resolvedContactId ,
116+ Notes = ps . Notes ,
117+ StopOrder = i + 1 ,
118+ AddedOn = DateTime . UtcNow ,
119+ IsDeleted = false
120+ } ;
121+
122+ if ( ! string . IsNullOrWhiteSpace ( ps . PlannedArrival ) && DateTime . TryParse ( ps . PlannedArrival , out var arrivalDt ) )
123+ stop . PlannedArrivalTime = DateTimeHelpers . ConvertToUtc ( DateTime . SpecifyKind ( arrivalDt , DateTimeKind . Unspecified ) , deptTimeZone ) ;
124+ if ( ! string . IsNullOrWhiteSpace ( ps . PlannedDeparture ) && DateTime . TryParse ( ps . PlannedDeparture , out var departureDt ) )
125+ stop . PlannedDepartureTime = DateTimeHelpers . ConvertToUtc ( DateTime . SpecifyKind ( departureDt , DateTimeKind . Unspecified ) , deptTimeZone ) ;
126+
127+ await _routeService . SaveRouteStopAsync ( stop , cancellationToken ) ;
101128 }
102129 }
103130
@@ -249,6 +276,15 @@ public async Task<IActionResult> AddStop(string routePlanId, string name, string
249276 return Json ( new { success = false , message = "Not found" } ) ;
250277
251278 var existingStops = await _routeService . GetRouteStopsForPlanAsync ( routePlanId ) ;
279+
280+ string resolvedContactId = null ;
281+ if ( ! string . IsNullOrWhiteSpace ( contactId ) )
282+ {
283+ var contact = await _contactsService . GetContactByIdAsync ( contactId ) ;
284+ if ( contact != null && contact . DepartmentId == DepartmentId )
285+ resolvedContactId = contactId ;
286+ }
287+
252288 var stop = new RouteStop
253289 {
254290 RoutePlanId = routePlanId ,
@@ -264,17 +300,22 @@ public async Task<IActionResult> AddStop(string routePlanId, string name, string
264300 EstimatedDwellMinutes = dwellMinutes ,
265301 ContactName = contactName ,
266302 ContactNumber = contactNumber ,
267- ContactId = contactId ,
303+ ContactId = resolvedContactId ,
268304 Notes = notes ,
269305 StopOrder = existingStops . Count + 1 ,
270306 AddedOn = DateTime . UtcNow ,
271307 IsDeleted = false
272308 } ;
273309
310+ var dept2 = await _departmentsService . GetDepartmentByIdAsync ( DepartmentId ) ;
311+ var deptTimeZone2 = ! string . IsNullOrWhiteSpace ( dept2 ? . TimeZone )
312+ ? DateTimeHelpers . WindowsToIana ( dept2 . TimeZone )
313+ : "UTC" ;
314+
274315 if ( ! string . IsNullOrWhiteSpace ( plannedArrival ) && DateTime . TryParse ( plannedArrival , out var arrivalDt ) )
275- stop . PlannedArrivalTime = arrivalDt . ToUniversalTime ( ) ;
316+ stop . PlannedArrivalTime = DateTimeHelpers . ConvertToUtc ( DateTime . SpecifyKind ( arrivalDt , DateTimeKind . Unspecified ) , deptTimeZone2 ) ;
276317 if ( ! string . IsNullOrWhiteSpace ( plannedDeparture ) && DateTime . TryParse ( plannedDeparture , out var departureDt ) )
277- stop . PlannedDepartureTime = departureDt . ToUniversalTime ( ) ;
318+ stop . PlannedDepartureTime = DateTimeHelpers . ConvertToUtc ( DateTime . SpecifyKind ( departureDt , DateTimeKind . Unspecified ) , deptTimeZone2 ) ;
278319
279320 await _routeService . SaveRouteStopAsync ( stop , cancellationToken ) ;
280321 return Json ( new { success = true } ) ;
0 commit comments