Skip to content

Commit 1155e72

Browse files
committed
RE1-T105 PR#303 Fixes
1 parent 3885756 commit 1155e72

7 files changed

Lines changed: 213 additions & 81 deletions

File tree

Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,4 +483,25 @@
483483
<data name="EditContact" xml:space="preserve">
484484
<value>Edit Contact</value>
485485
</data>
486+
<data name="Routes" xml:space="preserve">
487+
<value>Routes</value>
488+
</data>
489+
<data name="Route" xml:space="preserve">
490+
<value>Route</value>
491+
</data>
492+
<data name="StopName" xml:space="preserve">
493+
<value>Stop Name</value>
494+
</data>
495+
<data name="PriorityNormal" xml:space="preserve">
496+
<value>Normal</value>
497+
</data>
498+
<data name="PriorityHigh" xml:space="preserve">
499+
<value>High</value>
500+
</data>
501+
<data name="PriorityCritical" xml:space="preserve">
502+
<value>Critical</value>
503+
</data>
504+
<data name="PriorityOptional" xml:space="preserve">
505+
<value>Optional</value>
506+
</data>
486507
</root>

Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,74 @@
339339
<data name="StopLabel" xml:space="preserve">
340340
<value>Stop</value>
341341
</data>
342+
<!-- Add Stop Modal -->
343+
<data name="AddStop" xml:space="preserve">
344+
<value>Add Stop</value>
345+
</data>
346+
<data name="StopTypeManual" xml:space="preserve">
347+
<value>Manual</value>
348+
</data>
349+
<data name="StopTypeScheduledCall" xml:space="preserve">
350+
<value>Scheduled Call</value>
351+
</data>
352+
<data name="StopTypeWaypoint" xml:space="preserve">
353+
<value>Waypoint</value>
354+
</data>
355+
<data name="LinkedCall" xml:space="preserve">
356+
<value>Linked Call</value>
357+
</data>
358+
<data name="LoadingCalls" xml:space="preserve">
359+
<value>Loading calls...</value>
360+
</data>
361+
<data name="PriorityNormal" xml:space="preserve">
362+
<value>Normal</value>
363+
</data>
364+
<data name="PriorityHigh" xml:space="preserve">
365+
<value>High</value>
366+
</data>
367+
<data name="PriorityCritical" xml:space="preserve">
368+
<value>Critical</value>
369+
</data>
370+
<data name="PriorityOptional" xml:space="preserve">
371+
<value>Optional</value>
372+
</data>
373+
<data name="Find" xml:space="preserve">
374+
<value>Find</value>
375+
</data>
376+
<data name="What3Words" xml:space="preserve">
377+
<value>What3Words</value>
378+
</data>
379+
<data name="Location" xml:space="preserve">
380+
<value>Location</value>
381+
</data>
382+
<data name="ClickMapToSetLocation" xml:space="preserve">
383+
<value>Click the map to set location, or use address / What3Words above.</value>
384+
</data>
385+
<data name="PlannedArrival" xml:space="preserve">
386+
<value>Planned Arrival</value>
387+
</data>
388+
<data name="PlannedDeparture" xml:space="preserve">
389+
<value>Planned Departure</value>
390+
</data>
391+
<data name="EstDwellMinutes" xml:space="preserve">
392+
<value>Est. Dwell (min)</value>
393+
</data>
394+
<data name="SelectContact" xml:space="preserve">
395+
<value>Select Contact</value>
396+
</data>
397+
<data name="SelectContactPlaceholder" xml:space="preserve">
398+
<value>-- Type manually or select existing --</value>
399+
</data>
400+
<data name="ContactAutofillHelp" xml:space="preserve">
401+
<value>Selecting a contact auto-fills name and number below.</value>
402+
</data>
403+
<data name="ContactName" xml:space="preserve">
404+
<value>Contact Name</value>
405+
</data>
406+
<data name="ContactNumber" xml:space="preserve">
407+
<value>Contact Number</value>
408+
</data>
409+
<data name="Notes" xml:space="preserve">
410+
<value>Notes</value>
411+
</data>
342412
</root>

Web/Resgrid.Web/Areas/User/Controllers/ContactsController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public async Task<IActionResult> View(string contactId)
130130

131131
model.Notes = await _contactsService.GetContactNotesByContactIdAsync(contactId, DepartmentId);
132132

133-
model.RouteStops = await _routeService.GetRouteStopsForContactAsync(contactId, DepartmentId);
133+
model.RouteStops = await _routeService.GetRouteStopsForContactAsync(contactId, DepartmentId) ?? new List<RouteStop>();
134134
if (model.RouteStops.Count > 0)
135135
{
136136
var planIds = model.RouteStops.Select(s => s.RoutePlanId).Distinct().ToList();

Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Resgrid.Model;
99
using Resgrid.Model.Services;
1010
using Resgrid.Providers.Claims;
11+
using Resgrid.Framework;
1112
using Resgrid.Web.Areas.User.Models.Routes;
1213

1314
namespace 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 });

Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
{
3333
<li class="nav-item">
3434
<a class="nav-link" data-toggle="tab" href="#routes" role="tab">
35-
<i class="fa fa-map-signs"></i> Routes
35+
<i class="fa fa-map-signs"></i> @localizer["Routes"]
3636
</a>
3737
</li>
3838
}
@@ -464,11 +464,11 @@
464464
<table class="table table-striped table-hover">
465465
<thead>
466466
<tr>
467-
<th>Route</th>
468-
<th>Stop Name</th>
469-
<th>Address</th>
470-
<th>Priority</th>
471-
<th>Notes</th>
467+
<th>@localizer["Route"]</th>
468+
<th>@localizer["StopName"]</th>
469+
<th>@localizer["AddressLabel"]</th>
470+
<th>@localizer["Priority"]</th>
471+
<th>@localizer["Notes"]</th>
472472
</tr>
473473
</thead>
474474
<tbody>
@@ -487,10 +487,10 @@
487487
<td>
488488
@switch (stop.Priority)
489489
{
490-
case 1: <text>High</text> break;
491-
case 2: <text>Critical</text> break;
492-
case 3: <text>Optional</text> break;
493-
default: <text>Normal</text> break;
490+
case 1: <text>@localizer["PriorityHigh"]</text> break;
491+
case 2: <text>@localizer["PriorityCritical"]</text> break;
492+
case 3: <text>@localizer["PriorityOptional"]</text> break;
493+
default: <text>@localizer["PriorityNormal"]</text> break;
494494
}
495495
</td>
496496
<td>@stop.Notes</td>

Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@
293293
var osmTileAttribution = '@Resgrid.Config.MappingConfig.LeafletAttribution';
294294
var editStops =@Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Stops.OrderBy(s => s.StopOrder).Select(s => new { id = s.RouteStopId, s.Name, lat = s.Latitude, lng = s.Longitude })));
295295
var routePlanId = '@Model.Plan.RoutePlanId';
296-
var availableContacts = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Contacts.Select(c => new { id = c.ContactId, name = c.GetName(), phone = c.CellPhoneNumber ?? c.OfficePhoneNumber ?? c.HomePhoneNumber ?? "" })));
296+
var availableContacts = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject((Model.Contacts ?? Enumerable.Empty<Resgrid.Model.Contact>()).Select(c => new { id = c.ContactId ?? "", name = c.GetName() ?? "", phone = c.CellPhoneNumber ?? c.OfficePhoneNumber ?? c.HomePhoneNumber ?? "" }), new Newtonsoft.Json.JsonSerializerSettings { StringEscapeHandling = Newtonsoft.Json.StringEscapeHandling.EscapeHtml }));
297297
</script>
298298
<script src="~/js/app/internal/routes/resgrid.routes.edit.js"></script>
299299
}

0 commit comments

Comments
 (0)