Skip to content

Commit d1a991b

Browse files
committed
RE1-T91 RE1-T107 Fixing timers, voice number verification.
1 parent d414d3f commit d1a991b

26 files changed

Lines changed: 940 additions & 26 deletions

File tree

.claude/settings.local.json

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
{
22
"permissions": {
33
"allow": [
4-
"mcp__dual-graph__graph_scan",
54
"mcp__dual-graph__graph_continue",
6-
"Bash(find G:ResgridResgridCoreResgrid.ModelBilling -type f -name *.cs)",
7-
"Bash(python3:*)",
5+
"mcp__dual-graph__graph_read",
6+
"Bash(dotnet build:*)",
7+
"mcp__dual-graph__graph_register_edit",
8+
"mcp__dual-graph__graph_scan",
9+
"Bash(find /g/Resgrid/Resgrid -type d \\\\\\(-name *mobile* -o -name *Mobile* -o -name *app* -o -name *App* -o -name *apps* -o -name *Apps* \\\\\\))",
810
"Bash(xargs grep:*)",
9-
"Bash(grep -E \"\\\\.cs$|Program\")",
10-
"Bash(grep -l \"DepartmentMembers\\\\|DepartmentMember\" \"G:\\\\Resgrid\\\\Resgrid/Repositories/Resgrid.Repositories.DataRepository/\"*.cs)",
11-
"Bash(find /g/Resgrid/Resgrid/Repositories/Resgrid.Repositories.DataRepository/Servers -name *.cs)",
12-
"Bash(find G:/Resgrid/Resgrid -newer G:/Resgrid/Resgrid/CLAUDE.md -name *.cs -not -path */obj/* -not -path */.git/*)",
13-
"Bash(dotnet build:*)"
11+
"Bash(grep -r \"mapbox\\\\|react-native\\\\|rnmapbox\" /g/Resgrid/Resgrid --include=package.json --include=*.ts --include=*.tsx)",
12+
"Bash(find G:ResgridResgridWebResgrid.WebAreasUserViews -type f -name *.cshtml)",
13+
"Bash(grep -r \"google.maps\\\\|mapboxgl\\\\|leaflet\\\\|openstreetmap\" G:/Resgrid/Resgrid/Web/Resgrid.Web/Areas/User/Apps/src --include=*.ts)",
14+
"Bash(grep -r \"leaflet\\\\|L\\\\.tileLayer\\\\|OpenStreetMap\" /g/Resgrid/Resgrid/Web/Resgrid.Web.Services --include=*.cs)",
15+
"Bash(find /g/Resgrid/Resgrid -type f -name *.swift -o -name *.kt -o -name *.java)"
1416
]
1517
},
1618
"hooks": {

Core/Resgrid.Config/NumberProviderConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static class NumberProviderConfig
1515
public static string TwilioResgridNumber = "";
1616
public static string TwilioApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/IncomingMessage";
1717
public static string TwilioVoiceCallApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceCall?userId={0}&callId={1}";
18+
public static string TwilioVoiceVerificationApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceVerification?userId={0}&contactType={1}";
1819
public static string TwilioVoiceApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/InboundVoice";
1920

2021
// Diafaan (https://www.diafaan.com)

Core/Resgrid.Localization/Areas/User/Home/EditProfile.en.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,9 @@
402402
<data name="VerificationCodeSent" xml:space="preserve">
403403
<value>A verification code has been sent.</value>
404404
</data>
405+
<data name="VerificationCodeVoiceCallSent" xml:space="preserve">
406+
<value>A verification call is being placed. Please answer and note the code spoken to you.</value>
407+
</data>
405408
<data name="VerificationSuccessful" xml:space="preserve">
406409
<value>Verification successful.</value>
407410
</data>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
3+
namespace Resgrid.Model
4+
{
5+
/// <summary>
6+
/// Represents the check-in timer status for a single dispatched person on a call
7+
/// that has personnel check-in timers enabled.
8+
/// </summary>
9+
public class PersonnelCallCheckInStatus
10+
{
11+
/// <summary>The ASP.NET Identity user identifier.</summary>
12+
public string UserId { get; set; }
13+
14+
/// <summary>
15+
/// The user's display name (first + last name from their profile).
16+
/// May be null if the profile could not be resolved.
17+
/// </summary>
18+
public string FullName { get; set; }
19+
20+
/// <summary>UTC timestamp of the user's most-recent personnel check-in on this call, or null if never checked in.</summary>
21+
public DateTime? LastCheckIn { get; set; }
22+
23+
/// <summary>True when the user must check in immediately (timer has expired).</summary>
24+
public bool NeedsCheckIn { get; set; }
25+
26+
/// <summary>
27+
/// Minutes remaining until the next check-in is required.
28+
/// Positive = time still available, negative = how many minutes overdue.
29+
/// Zero or negative implies <see cref="NeedsCheckIn"/> is true.
30+
/// </summary>
31+
public double MinutesRemaining { get; set; }
32+
33+
/// <summary>
34+
/// Colour-coded status string: "Green" (within timer), "Warning" (within warning
35+
/// threshold), or "Critical" (timer expired).
36+
/// </summary>
37+
public string Status { get; set; }
38+
39+
/// <summary>
40+
/// The resolved duration (in minutes) of the personnel check-in timer for this call.
41+
/// </summary>
42+
public int DurationMinutes { get; set; }
43+
44+
/// <summary>
45+
/// The warning threshold (in minutes) of the personnel check-in timer for this call.
46+
/// </summary>
47+
public int WarningThresholdMinutes { get; set; }
48+
}
49+
}

Core/Resgrid.Model/Providers/IOutboundVoiceProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ namespace Resgrid.Model.Providers
55
public interface IOutboundVoiceProvider
66
{
77
Task<bool> CommunicateCallAsync(string phoneNumber, UserProfile profile, Call call);
8+
9+
/// <summary>
10+
/// Places a Twilio voice call that speaks the verification code digits to the user.
11+
/// </summary>
12+
Task<bool> SendVoiceVerificationCallAsync(string phoneNumber, string userId, int contactType);
813
}
914
}

Core/Resgrid.Model/Repositories/ICallsRepository.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
44

@@ -81,5 +81,15 @@ public interface ICallsRepository: IRepository<Call>
8181
Task<IEnumerable<Call>> GetAllCallsByContactIdAsync(string contactId, int departmentId);
8282

8383
Task<int> GetCallsCountByDepartmentDateRangeAsync(int departmentId, DateTime startDate, DateTime endDate);
84+
85+
/// <summary>
86+
/// Gets all active calls for a department that have check-in timers enabled
87+
/// and that the specified user has been dispatched on.
88+
/// Optimised as a single JOIN query to avoid N+1 lookups.
89+
/// </summary>
90+
/// <param name="userId">The identity user identifier to filter dispatches by.</param>
91+
/// <param name="departmentId">The department identifier (used to scope the result).</param>
92+
/// <returns>Active calls with check-in timers that the user is dispatched on.</returns>
93+
Task<IEnumerable<Call>> GetActiveCallsWithCheckInTimersForUserAsync(string userId, int departmentId);
8494
}
8595
}

Core/Resgrid.Model/Services/ICallsService.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Threading;
44
using System.Threading.Tasks;
@@ -441,5 +441,14 @@ Task<bool> ClearGroupForDispatchesAsync(int departmentGroupId,
441441
Task<List<CallVideoFeed>> GetCallVideoFeedsByCallIdAsync(int callId);
442442

443443
Task<bool> DeleteCallVideoFeedAsync(CallVideoFeed feed, string deletedByUserId, CancellationToken cancellationToken = default(CancellationToken));
444+
445+
/// <summary>
446+
/// Gets all active calls for a department that have personnel check-in timers
447+
/// enabled and that the specified user has been dispatched on.
448+
/// </summary>
449+
/// <param name="userId">The ASP.NET Identity user identifier.</param>
450+
/// <param name="departmentId">The department to scope the search to.</param>
451+
/// <returns>Active calls with check-in timers dispatched to the user.</returns>
452+
Task<List<Call>> GetActiveCallsWithCheckInTimersForUserAsync(string userId, int departmentId);
444453
}
445454
}

Core/Resgrid.Model/Services/ICheckInTimerService.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,24 @@ public interface ICheckInTimerService
2626

2727
// Timer Status Computation
2828
Task<List<CheckInTimerStatus>> GetActiveTimerStatusesForCallAsync(Call call);
29+
30+
// ── New: per-user and per-call personnel check-in status ────────────────
31+
32+
/// <summary>
33+
/// Returns a check-in summary for every active call (with check-in timers
34+
/// enabled) that <paramref name="userId"/> has been dispatched on.
35+
/// Used by API Endpoint 1.
36+
/// </summary>
37+
/// <param name="userId">The ASP.NET Identity user identifier to query for.</param>
38+
/// <param name="departmentId">Dept scope (from JWT claims) – prevents cross-dept data access.</param>
39+
Task<List<UserCallCheckInSummary>> GetUserActiveCallCheckInSummariesAsync(string userId, int departmentId);
40+
41+
/// <summary>
42+
/// For a call that has a personnel check-in timer active, returns the current
43+
/// check-in status for every person dispatched on that call.
44+
/// Used by API Endpoint 2.
45+
/// </summary>
46+
/// <param name="call">The call to evaluate. Must have <see cref="Call.CheckInTimersEnabled"/> = true.</param>
47+
Task<List<PersonnelCallCheckInStatus>> GetCallPersonnelCheckInStatusesAsync(Call call);
2948
}
3049
}

Core/Resgrid.Model/Services/IContactVerificationService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ public interface IContactVerificationService
2525
Task<bool> SendMobileVerificationCodeAsync(string userId, int departmentId, string departmentNumber, CancellationToken cancellationToken = default);
2626

2727
/// <summary>
28-
/// Generates a verification code and sends it via SMS to the user's home number.
28+
/// Generates a verification code and delivers it via a Twilio voice call to the
29+
/// user's home number, speaking the digits and repeating multiple times.
2930
/// Returns <c>false</c> if the user has no home number, if rate limits are exceeded,
30-
/// or if the send fails.
31+
/// or if the call fails.
3132
/// </summary>
3233
Task<bool> SendHomeVerificationCodeAsync(string userId, int departmentId, string departmentNumber, CancellationToken cancellationToken = default);
3334

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
3+
namespace Resgrid.Model
4+
{
5+
/// <summary>
6+
/// Represents the check-in timer status for a single user on a single active call
7+
/// that has personnel check-in timers enabled.
8+
/// </summary>
9+
public class UserCallCheckInSummary
10+
{
11+
/// <summary>The call identifier.</summary>
12+
public int CallId { get; set; }
13+
14+
/// <summary>The call name / nature of call.</summary>
15+
public string CallName { get; set; }
16+
17+
/// <summary>The human-readable call number (e.g. "2024-0042").</summary>
18+
public string CallNumber { get; set; }
19+
20+
/// <summary>UTC timestamp when the call was logged.</summary>
21+
public DateTime CallStartedOn { get; set; }
22+
23+
/// <summary>True when a personnel-type check-in timer is active for this call.</summary>
24+
public bool HasPersonnelTimer { get; set; }
25+
26+
/// <summary>
27+
/// How long (in minutes) the user has before they must check in.
28+
/// Only meaningful when <see cref="HasPersonnelTimer"/> is true.
29+
/// </summary>
30+
public int DurationMinutes { get; set; }
31+
32+
/// <summary>
33+
/// Number of minutes before the deadline at which a "Warning" status is issued.
34+
/// Only meaningful when <see cref="HasPersonnelTimer"/> is true.
35+
/// </summary>
36+
public int WarningThresholdMinutes { get; set; }
37+
38+
/// <summary>UTC timestamp of the user's most-recent check-in on this call, or null if never checked in.</summary>
39+
public DateTime? LastCheckIn { get; set; }
40+
41+
/// <summary>True when the user must check in immediately (timer has expired).</summary>
42+
public bool NeedsCheckIn { get; set; }
43+
44+
/// <summary>
45+
/// Minutes remaining until the next check-in is required.
46+
/// Positive = time still available, negative = how many minutes overdue.
47+
/// Zero or negative implies <see cref="NeedsCheckIn"/> is true.
48+
/// </summary>
49+
public double MinutesRemaining { get; set; }
50+
51+
/// <summary>
52+
/// Colour-coded status string: "Green", "Warning", "Critical", or "NoTimer"
53+
/// when no personnel timer is configured for the call.
54+
/// </summary>
55+
public string Status { get; set; }
56+
}
57+
}

0 commit comments

Comments
 (0)