Skip to content

Commit 4c9d7ba

Browse files
authored
Merge pull request #327 from Resgrid/develop
RE1-T112 Fixes
2 parents 2699f08 + 7f99e3d commit 4c9d7ba

10 files changed

Lines changed: 363 additions & 213 deletions

File tree

Core/Resgrid.Config/InfoConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static class InfoConfig
4444
LocationInfo =
4545
"This is the Resgrid system hosted in Central Europe (on OVH). This system services Resgrid customers in the European Union to help with data (GDPR) compliance requirements.",
4646
IsDefault = false,
47-
AppUrl = "https://app.eu-central.resgrid.com",
47+
AppUrl = "https://app-eu-central.resgrid.com",
4848
ApiUrl = "https://api-eu-central.resgrid.com",
4949
AllowsFreeAccounts = false
5050
}

Core/Resgrid.Model/DepartmentSettingTypes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ public enum DepartmentSettingTypes
4545
WeatherAlertAutoMessageSeverity = 41,
4646
WeatherAlertCallIntegration = 42,
4747
WeatherAlertCacheMinutes = 43,
48+
WeatherAlertAutoMessageSchedule = 44,
4849
}
4950
}

Core/Resgrid.Services/WeatherAlertService.cs

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using Newtonsoft.Json;
67
using Resgrid.Framework;
78
using Resgrid.Model;
89
using Resgrid.Model.Helpers;
@@ -319,15 +320,27 @@ public async Task SendPendingNotificationsAsync(CancellationToken ct = default)
319320
continue;
320321
}
321322

322-
// Get the auto-message severity threshold setting
323-
var thresholdSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
324-
departmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
323+
// Load per-severity schedule (if configured)
324+
List<AutoMessageSeveritySchedule> schedule = null;
325+
var scheduleSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
326+
departmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSchedule);
327+
if (scheduleSetting != null && !string.IsNullOrWhiteSpace(scheduleSetting.Setting))
328+
{
329+
try { schedule = JsonConvert.DeserializeObject<List<AutoMessageSeveritySchedule>>(scheduleSetting.Setting); }
330+
catch { }
331+
}
325332

326-
int threshold = (int)WeatherAlertSeverity.Severe; // Default: Severe=1
327-
if (thresholdSetting != null && int.TryParse(thresholdSetting.Setting, out var parsed))
328-
threshold = parsed;
333+
// Fall back to legacy threshold if no schedule configured
334+
int legacyThreshold = (int)WeatherAlertSeverity.Severe;
335+
if (schedule == null || schedule.Count == 0)
336+
{
337+
var thresholdSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
338+
departmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
339+
if (thresholdSetting != null && int.TryParse(thresholdSetting.Setting, out var parsed))
340+
legacyThreshold = parsed;
341+
}
329342

330-
// Load department for sender info
343+
// Load department for sender info and time conversion
331344
Department department = null;
332345
try
333346
{
@@ -340,9 +353,8 @@ public async Task SendPendingNotificationsAsync(CancellationToken ct = default)
340353

341354
foreach (var alert in group)
342355
{
343-
// Only send notifications for alerts meeting severity threshold
344-
// Lower enum value = higher severity (Extreme=0, Severe=1, etc.)
345-
if (alert.Severity <= threshold)
356+
bool shouldSend = ShouldSendAutoMessage(alert.Severity, schedule, legacyThreshold, department);
357+
if (shouldSend)
346358
{
347359
try
348360
{
@@ -594,5 +606,50 @@ private static string Truncate(string value, int maxLength)
594606

595607
return value.Substring(0, maxLength);
596608
}
609+
610+
private static bool ShouldSendAutoMessage(int severity, List<AutoMessageSeveritySchedule> schedule, int legacyThreshold, Department department)
611+
{
612+
if (schedule != null && schedule.Count > 0)
613+
{
614+
var entry = schedule.FirstOrDefault(s => s.Severity == severity);
615+
616+
// Severity not in schedule — don't send
617+
if (entry == null || !entry.Enabled)
618+
return false;
619+
620+
// Check time window (StartHour == 0 && EndHour == 0 means 24h/always)
621+
if (entry.StartHour == 0 && entry.EndHour == 0)
622+
return true;
623+
624+
// Get department local time
625+
var now = DateTime.UtcNow;
626+
if (department != null)
627+
now = now.TimeConverter(department);
628+
629+
int currentHour = now.Hour;
630+
631+
if (entry.StartHour <= entry.EndHour)
632+
{
633+
// Same-day window: e.g. 6-18
634+
return currentHour >= entry.StartHour && currentHour < entry.EndHour;
635+
}
636+
else
637+
{
638+
// Overnight window: e.g. 18-6 (6pm to 6am)
639+
return currentHour >= entry.StartHour || currentHour < entry.EndHour;
640+
}
641+
}
642+
643+
// Legacy: simple severity threshold
644+
return severity <= legacyThreshold;
645+
}
646+
647+
private class AutoMessageSeveritySchedule
648+
{
649+
public int Severity { get; set; }
650+
public bool Enabled { get; set; }
651+
public int StartHour { get; set; }
652+
public int EndHour { get; set; }
653+
}
597654
}
598655
}

Web/Resgrid.Web.Services/Controllers/v4/WeatherAlertsController.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,20 @@ public async Task<ActionResult<GetWeatherAlertSettingsResult>> SaveSettings([Fro
357357
await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, input.AutoMessageSeverity.ToString(), DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
358358
await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, input.CallIntegrationEnabled.ToString(), DepartmentSettingTypes.WeatherAlertCallIntegration);
359359

360+
if (input.AutoMessageSchedule != null)
361+
{
362+
var scheduleJson = Newtonsoft.Json.JsonConvert.SerializeObject(input.AutoMessageSchedule);
363+
await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, scheduleJson, DepartmentSettingTypes.WeatherAlertAutoMessageSchedule);
364+
}
365+
360366
var result = new GetWeatherAlertSettingsResult();
361367
result.Data = new WeatherAlertSettingsData
362368
{
363369
WeatherAlertsEnabled = input.WeatherAlertsEnabled,
364370
MinimumSeverity = input.MinimumSeverity,
365371
AutoMessageSeverity = input.AutoMessageSeverity,
366-
CallIntegrationEnabled = input.CallIntegrationEnabled
372+
CallIntegrationEnabled = input.CallIntegrationEnabled,
373+
AutoMessageSchedule = input.AutoMessageSchedule
367374
};
368375

369376
ResponseHelper.PopulateV4ResponseData(result);
@@ -378,6 +385,7 @@ private async Task<WeatherAlertSettingsData> GetWeatherAlertSettingsDataAsync()
378385
var minSeveritySetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertMinimumSeverity);
379386
var autoMsgSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
380387
var callIntSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertCallIntegration);
388+
var scheduleSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSchedule);
381389

382390
if (enabledSetting != null && !string.IsNullOrWhiteSpace(enabledSetting.Setting))
383391
settings.WeatherAlertsEnabled = bool.TryParse(enabledSetting.Setting, out var enabled) && enabled;
@@ -391,6 +399,15 @@ private async Task<WeatherAlertSettingsData> GetWeatherAlertSettingsDataAsync()
391399
if (callIntSetting != null && !string.IsNullOrWhiteSpace(callIntSetting.Setting))
392400
settings.CallIntegrationEnabled = bool.TryParse(callIntSetting.Setting, out var callInt) && callInt;
393401

402+
if (scheduleSetting != null && !string.IsNullOrWhiteSpace(scheduleSetting.Setting))
403+
{
404+
try
405+
{
406+
settings.AutoMessageSchedule = Newtonsoft.Json.JsonConvert.DeserializeObject<System.Collections.Generic.List<WeatherAlertSeverityScheduleData>>(scheduleSetting.Setting);
407+
}
408+
catch { }
409+
}
410+
394411
return settings;
395412
}
396413

Web/Resgrid.Web.Services/Models/v4/WeatherAlerts/SaveWeatherAlertSettingsInput.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Collections.Generic;
2+
13
namespace Resgrid.Web.Services.Models.v4.WeatherAlerts
24
{
35
public class SaveWeatherAlertSettingsInput
@@ -6,5 +8,6 @@ public class SaveWeatherAlertSettingsInput
68
public int MinimumSeverity { get; set; }
79
public int AutoMessageSeverity { get; set; }
810
public bool CallIntegrationEnabled { get; set; }
11+
public List<WeatherAlertSeverityScheduleData> AutoMessageSchedule { get; set; }
912
}
1013
}

Web/Resgrid.Web.Services/Models/v4/WeatherAlerts/WeatherAlertSettingsData.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Collections.Generic;
2+
13
namespace Resgrid.Web.Services.Models.v4.WeatherAlerts
24
{
35
public class WeatherAlertSettingsData
@@ -6,5 +8,14 @@ public class WeatherAlertSettingsData
68
public int MinimumSeverity { get; set; }
79
public int AutoMessageSeverity { get; set; }
810
public bool CallIntegrationEnabled { get; set; }
11+
public List<WeatherAlertSeverityScheduleData> AutoMessageSchedule { get; set; }
12+
}
13+
14+
public class WeatherAlertSeverityScheduleData
15+
{
16+
public int Severity { get; set; }
17+
public bool Enabled { get; set; }
18+
public int StartHour { get; set; }
19+
public int EndHour { get; set; }
920
}
1021
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@using Microsoft.AspNetCore.Http
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>@ViewData["Title"]</title>
8+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
9+
10+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/css/bootstrap.min.css"
11+
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
12+
asp-fallback-test-class="sr-only"
13+
asp-fallback-test-property="position"
14+
asp-fallback-test-value="absolute" />
15+
16+
<link rel="stylesheet" href="/css/int-bundle.css" />
17+
18+
<link rel="shortcut icon" href="~/favicon.ico" />
19+
20+
@if (IsSectionDefined("Styles"))
21+
{
22+
@RenderSection("Styles", required: false)
23+
}
24+
</head>
25+
<body class="gray-bg">
26+
<div class="container" style="padding-top: 20px;">
27+
@RenderBody()
28+
</div>
29+
30+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.3/jquery.min.js"
31+
asp-fallback-src="lib/jquery/jquery-1.12.3.min.js"
32+
asp-fallback-test="window.jQuery"
33+
crossorigin="anonymous"
34+
integrity="sha384-ugqypGWrzPLdx2zEQTF17cVktjb01piRKaDNnbYGRSxyEoeAm+MKZVtbDUYjxfZ6">
35+
</script>
36+
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js"
37+
asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
38+
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
39+
crossorigin="anonymous"
40+
integrity="sha384-vhJnz1OVIdLktyixHY4Uk3OHEwdQqPppqYR8+5mjsauETgLOcEynD9oPHhhz18Nw">
41+
</script>
42+
<script src="~/lib/sweetalert/dist/sweetalert.min.js"></script>
43+
44+
<script>
45+
var resgrid = resgrid || {};
46+
resgrid.absoluteBaseUrl = "@Resgrid.Config.SystemBehaviorConfig.ResgridBaseUrl";
47+
</script>
48+
49+
@if (IsSectionDefined("Scripts"))
50+
{
51+
@RenderSection("Scripts", required: false)
52+
}
53+
</body>
54+
</html>

0 commit comments

Comments
 (0)