Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions Core/Resgrid.Chatbot/Handlers/DepartmentActionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,45 @@ public class DepartmentActionHandler : IChatbotActionHandler
private readonly IUserProfileService _userProfileService;
private readonly IUsersService _usersService;
private readonly ILimitsService _limitsService;
private readonly IChatbotDepartmentConfigService _departmentConfigService;

public DepartmentActionHandler(
IDepartmentsService departmentsService,
IUserProfileService userProfileService,
IUsersService usersService,
ILimitsService limitsService)
ILimitsService limitsService,
IChatbotDepartmentConfigService departmentConfigService)
{
_departmentsService = departmentsService;
_userProfileService = userProfileService;
_usersService = usersService;
_limitsService = limitsService;
_departmentConfigService = departmentConfigService;
}

/// <summary>
/// The user's non-deleted memberships restricted to departments that support SMS (i.e. are on a
/// non-free plan). SMS operations — including switching — are only offered for these, so the list
/// the user sees and the indexes they pick from are the SMS-supporting departments, in a stable order.
/// The user's non-deleted memberships restricted to departments the CHATBOT can serve here: on a
/// plan that supports SMS AND with the chatbot enabled for the current platform (per-department
/// config; no config row means enabled). Listing/switching to a department whose chatbot is
/// disabled would strand the user behind the ingress config gate with no in-band way back. The
/// legacy SMS SWITCH command uses the unfiltered plan-based list — switching is account-wide and
/// a chatbot-disabled department is still valid for legacy text commands.
/// </summary>
private async Task<System.Collections.Generic.List<DepartmentMember>> GetSmsSupportingMembershipsAsync(string userId)
private async Task<System.Collections.Generic.List<DepartmentMember>> GetSmsSupportingMembershipsAsync(string userId, ChatbotPlatform platform)
{
var allMemberRecords = await _departmentsService.GetAllDepartmentsForUserAsync(userId);
if (allMemberRecords == null || allMemberRecords.Count == 0)
return new System.Collections.Generic.List<DepartmentMember>();

var candidates = allMemberRecords
.Where(m => !m.IsDeleted)
.OrderByDescending(m => m.IsActive)
.ThenBy(m => m.DepartmentId)
.ToList();

var supported = new System.Collections.Generic.List<DepartmentMember>();
foreach (var membership in candidates)
// Same base list (and order) as the legacy SMS surfaces, then chatbot-eligibility filtering.
// The ingress restricted mode applies the identical filter, so the numbered options it shows
// map to the same memberships this handler resolves.
var supported = await _departmentsService.GetSmsSupportedMembershipsForUserAsync(userId);

var usable = new System.Collections.Generic.List<DepartmentMember>();
foreach (var membership in supported)
{
if (await _limitsService.CanDepartmentProvisionNumberAsync(membership.DepartmentId))
supported.Add(membership);
if (await _departmentConfigService.IsChatbotUsableForDepartmentAsync(membership.DepartmentId, platform))
usable.Add(membership);
}

return supported;
return usable;
}

public ChatbotIntentType IntentType => ChatbotIntentType.ListDepartments;
Expand Down Expand Up @@ -86,8 +87,9 @@ private async Task<ChatbotResponse> ListDepartmentsAsync(ChatbotSession session)
var culture = session.Culture;
try
{
// Only SMS-supporting (non-free plan) departments are switchable, so only list those.
var activeMemberships = await GetSmsSupportingMembershipsAsync(session.UserId);
// Only departments the chatbot can serve here (non-free plan + chatbot enabled for this
// platform) are switchable, so only list those.
var activeMemberships = await GetSmsSupportingMembershipsAsync(session.UserId, session.Platform);

if (activeMemberships.Count == 0)
return new ChatbotResponse { Text = ChatbotResources.Get("Dept_NoActiveMemberships", culture), Processed = true };
Expand Down Expand Up @@ -185,9 +187,10 @@ private async Task<ChatbotResponse> SwitchDepartmentAsync(ChatbotIntent intent,
if (string.IsNullOrWhiteSpace(departmentIdentifier))
return new ChatbotResponse { Text = ChatbotResources.Get("Dept_SwitchSpecify", culture), Processed = true };

// Switching is limited to SMS-supporting (non-free plan) departments — the same set, in the
// same order, that ListDepartments shows, so a numeric pick maps to the displayed list.
var activeMemberships = await GetSmsSupportingMembershipsAsync(session.UserId);
// Switching is limited to chatbot-usable (non-free plan, chatbot enabled for this platform)
// departments — the same set, in the same order, that ListDepartments shows, so a numeric
// pick maps to the displayed list.
var activeMemberships = await GetSmsSupportingMembershipsAsync(session.UserId, session.Platform);

if (activeMemberships.Count == 0)
return new ChatbotResponse { Text = ChatbotResources.Get("Dept_NoActiveMemberships", culture), Processed = true };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ public interface IChatbotDepartmentConfigService
/// </summary>
Task<ChatbotDepartmentConfig> SaveConfigAsync(ChatbotDepartmentConfig config, string newPlaintextLlmKey = null);

/// <summary>
/// Whether the chatbot is usable for a department on the given platform: no config row means
/// system defaults (enabled, all platforms); otherwise the row's IsEnabled and AllowedPlatforms
/// both have to permit it. Used to keep switch/list targets to departments the chatbot can
/// actually serve on the requesting platform.
/// </summary>
Task<bool> IsChatbotUsableForDepartmentAsync(int departmentId, ChatbotPlatform platform);

Task InvalidateCacheAsync(int departmentId);
}
}
29 changes: 29 additions & 0 deletions Core/Resgrid.Chatbot/Services/ChatbotDepartmentConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,35 @@ public async Task<ChatbotDepartmentConfig> GetConfigAsync(int departmentId, bool
return await fetch();
}

public async Task<bool> IsChatbotUsableForDepartmentAsync(int departmentId, ChatbotPlatform platform)
{
var config = await GetConfigAsync(departmentId);
if (config == null)
return true; // No row: system defaults — enabled, all platforms.

if (!config.IsEnabled)
return false;

return IsPlatformAllowed(config.AllowedPlatforms, platform);
}

// Mirrors ChatbotIngressService.IsPlatformAllowed: null/blank/"*" = all platforms, otherwise a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules low

Duplicated logic in the IsPlatformAllowed method (lines 66-79) verbatim copies the identical private static method in ChatbotIngressService.cs (lines 556-569). Extract this parsing logic into a shared utility or extension method so both services reference a single implementation.

Kody rule violation: Extract duplicated logic into functions

// In a shared utility, e.g. ChatbotPlatformHelper:
public static bool IsPlatformAllowed(string allowedPlatforms, ChatbotPlatform platform)
{
    if (string.IsNullOrWhiteSpace(allowedPlatforms) || allowedPlatforms.Trim() == "*")
        return true;

    var platformName = platform.ToString();
    foreach (var entry in allowedPlatforms.Split(','))
    {
        if (string.Equals(entry.Trim(), platformName, StringComparison.OrdinalIgnoreCase))
            return true;
    }

    return false;
}

// Both ChatbotIngressService and ChatbotDepartmentConfigService call ChatbotPlatformHelper.IsPlatformAllowed(...)
Prompt for LLM

File Core/Resgrid.Chatbot/Services/ChatbotDepartmentConfigService.cs:

Line 64:

Violates rule 'Extract duplicated logic into functions': the IsPlatformAllowed method (lines 66-79) is a verbatim copy of the identical private static method in ChatbotIngressService.cs (lines 556-569). The comment on line 64 explicitly acknowledges the duplication ('Mirrors ChatbotIngressService.IsPlatformAllowed'). This parsing logic should be extracted to a shared utility or extension method so both services reference a single implementation.

Suggested Code:

// In a shared utility, e.g. ChatbotPlatformHelper:
public static bool IsPlatformAllowed(string allowedPlatforms, ChatbotPlatform platform)
{
    if (string.IsNullOrWhiteSpace(allowedPlatforms) || allowedPlatforms.Trim() == "*")
        return true;

    var platformName = platform.ToString();
    foreach (var entry in allowedPlatforms.Split(','))
    {
        if (string.Equals(entry.Trim(), platformName, StringComparison.OrdinalIgnoreCase))
            return true;
    }

    return false;
}

// Both ChatbotIngressService and ChatbotDepartmentConfigService call ChatbotPlatformHelper.IsPlatformAllowed(...)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// comma-separated list of ChatbotPlatform names.
private static bool IsPlatformAllowed(string allowedPlatforms, ChatbotPlatform platform)
{
if (string.IsNullOrWhiteSpace(allowedPlatforms) || allowedPlatforms.Trim() == "*")
return true;

var platformName = platform.ToString();
foreach (var entry in allowedPlatforms.Split(','))
{
if (string.Equals(entry.Trim(), platformName, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

public async Task<DepartmentLlmOverride> GetLlmOverrideAsync(int departmentId)
{
var config = await GetConfigAsync(departmentId);
Expand Down
68 changes: 63 additions & 5 deletions Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,43 @@ await _userIdentityService.LinkUserAsync(
message.Platform, new ChatbotIntent { Type = ChatbotIntentType.Unknown });
}

// 3b. Check department plan supports chatbot features
// 3b. Check the ACTIVE department's plan supports chatbot features. When it doesn't but the
// user belongs to other departments that do, don't hard-block: they must still be able to
// list departments and SWITCH their active department (restricted mode, enforced after
// intent classification below). Only when there is no supported alternative is this a dead end.
var isAuthorized = await _limitsService.CanDepartmentProvisionNumberAsync(department.DepartmentId);
List<Model.DepartmentMember> switchableDepartments = null;
if (!isAuthorized)
{
return await _templateRenderer.RenderResponseAsync("error",
new Services.ErrorModel { Message = "Your department's plan does not support chatbot features. Please upgrade your plan." },
message.Platform, new ChatbotIntent { Type = ChatbotIntentType.Unknown });
// A switch target must be one the chatbot can actually serve on this platform (plan
// supports SMS AND chatbot enabled per department config) — the same filter
// DepartmentActionHandler applies, so the numbered options shown below map to the
// memberships its switch handler resolves. Plan-only filtering could strand the user in
// a department whose chatbot config blocks everything, with no in-band way back.
var smsSupported = await _departmentsService.GetSmsSupportedMembershipsForUserAsync(identity.UserId)
?? new List<Model.DepartmentMember>();

switchableDepartments = new List<Model.DepartmentMember>();
foreach (var membership in smsSupported)
{
if (await _departmentConfigService.IsChatbotUsableForDepartmentAsync(membership.DepartmentId, message.Platform))
switchableDepartments.Add(membership);
}

if (switchableDepartments.Count == 0)
{
return await _templateRenderer.RenderResponseAsync("error",
new Services.ErrorModel { Message = "Your department's plan does not support chatbot features. Please upgrade your plan." },
message.Platform, new ChatbotIntent { Type = ChatbotIntentType.Unknown });
}
}

// 3c. Per-department configuration gates (when a config row exists; otherwise the
// system defaults apply and the chatbot stays enabled for backward compatibility).
// Skipped in restricted (switch-only) mode: the unsupported department's config must not
// block the user from switching OUT of it.
var deptConfig = await _departmentConfigService.GetConfigAsync(department.DepartmentId);
if (deptConfig != null)
if (deptConfig != null && isAuthorized)
Comment on lines +156 to +192

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use chatbot eligibility, not only SMS plan eligibility, for restricted switching.

  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs#L156-L178: filter alternatives by target chatbot enablement and current platform.
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs#L237-L268: dispatch restricted list/switch intents using that filtered set.
  • Core/Resgrid.Chatbot/Handlers/DepartmentActionHandler.cs#L40-L42: ensure listing and target resolution apply the same chatbot-specific eligibility.

Otherwise users can switch into a department that immediately rejects every subsequent chatbot request.

📍 Affects 2 files
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs#L156-L178 (this comment)
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs#L237-L268
  • Core/Resgrid.Chatbot/Handlers/DepartmentActionHandler.cs#L40-L42
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs` around lines 156 -
178, The restricted-department flow must use chatbot-specific eligibility rather
than SMS plan support alone. In
Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs lines 156-178, filter
switchable departments by target chatbot enablement and the current platform; in
lines 237-268, pass that filtered set through restricted list and switch intent
dispatch. In Core/Resgrid.Chatbot/Handlers/DepartmentActionHandler.cs lines
40-42, apply the same chatbot-specific eligibility when listing departments and
resolving switch targets.

{
if (!deptConfig.IsEnabled)
{
Expand Down Expand Up @@ -224,6 +248,40 @@ await _userIdentityService.LinkUserAsync(
if (session.RecentMessages.Count > 20)
session.RecentMessages.RemoveAt(0);

// Restricted (switch-only) mode: the active department's plan doesn't support the chatbot
// but the user belongs to other departments that do. Only department list/switch/active
// intents are honored — so the user can move to a supported department — and anything else
// gets the switch options. Confirmation/PIN/continuation states are intentionally skipped:
// no destructive intent can be pending for a department that can't dispatch them.
if (!isAuthorized)
{
var restrictedIntent = await _intentRouter.ClassifyIntentAsync(message, session);
if (restrictedIntent.Type == ChatbotIntentType.ListDepartments
|| restrictedIntent.Type == ChatbotIntentType.SwitchDepartment
|| restrictedIntent.Type == ChatbotIntentType.GetActiveDepartment)
{
var restrictedHandler = _actionHandlers.FirstOrDefault(h => h.CanHandle(restrictedIntent.Type));
if (restrictedHandler != null)
{
var restrictedResponse = await restrictedHandler.HandleAsync(message, restrictedIntent, session);
restrictedResponse.Intent = restrictedIntent;
await _sessionManager.SaveSessionAsync(session);
return restrictedResponse;
}
}

var optionsBuilder = new System.Text.StringBuilder();
optionsBuilder.AppendLine("Your active department's plan does not support chatbot features, but you belong to other departments that do:");
for (int i = 0; i < switchableDepartments.Count; i++)
{
var switchableDept = await _departmentsService.GetDepartmentByIdAsync(switchableDepartments[i].DepartmentId);
optionsBuilder.AppendLine($"{i + 1}: {switchableDept?.Name ?? ("Department " + switchableDepartments[i].DepartmentId)}");
}
optionsBuilder.Append("Reply SWITCH <number or name> to change your active department.");

return new ChatbotResponse { Text = optionsBuilder.ToString(), Processed = true };
}

// 5. Handle session state machine via ConversationEngine (Phase 2)

// 5a. Confirmation of a destructive action (CloseCall, DispatchCall, SetUnitStatus, ...).
Expand Down
17 changes: 11 additions & 6 deletions Core/Resgrid.Model/Services/IDepartmentsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,20 @@ Task<DepartmentMember> AddUserToDepartmentAsync(int departmentId, string userId,
Task<Department> GetDepartmentByUserIdAsync(string userId, bool bypassCache = false);

/// <summary>
/// Resolves the department to use for a user's SMS/chatbot operations: their active (then default,
/// then first) membership, preferring one whose plan supports SMS (non-free). If the preferred
/// department is on a free plan but another of the user's departments supports SMS, that one is
/// returned so a user isn't dead-ended; if none support SMS the preferred one is returned so the
/// downstream plan gate can produce the proper "plan doesn't support" message. Null when the user has
/// no non-deleted memberships.
/// Resolves the department to use for a user's SMS/chatbot operations: their ACTIVE (then default,
/// then first) membership. No plan-based auto-picking — if the active department's plan doesn't
/// support SMS the downstream plan gate handles it (offering a department switch when the user has
/// other supported memberships). Null when the user has no non-deleted memberships.
/// </summary>
Task<Department> GetActiveSmsDepartmentForUserAsync(string userId, bool bypassCache = false);

/// <summary>
/// The user's non-deleted memberships restricted to departments whose plan supports SMS/chatbot
/// (non-free), in a stable order (active first, then by department id) so a numeric pick from a
/// displayed list maps to the same membership across stateless SMS requests.
/// </summary>
Task<List<DepartmentMember>> GetSmsSupportedMembershipsForUserAsync(string userId);

Task<ValidateUserForDepartmentResult> GetValidateUserForDepartmentInfoAsync(string userName,
bool bypassCache = true);

Expand Down
30 changes: 30 additions & 0 deletions Core/Resgrid.Model/Services/ITextDepartmentSwitchService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;

namespace Resgrid.Model.Services
{
/// <summary>
/// Shared handling for the inbound-SMS department SWITCH flow — used by both the Twilio and
/// SignalWire controllers so the plan-gate/STOP/switch behavior and reply wording stay identical
/// across providers. Methods return the reply body text; the caller wraps it in the
/// provider-specific response (TwiML or LaML).
/// </summary>
public interface ITextDepartmentSwitchService
{
/// <summary>
/// The identified user's ACTIVE department failed the inbound-text plan gate. STOP is honored
/// first (opt-out must always work), a SWITCH command is processed, any other text gets the
/// switch options, and with no supported alternatives the plan message. Never returns null.
/// </summary>
Task<string> BuildUnsupportedActiveDepartmentResponseAsync(string messageText, int departmentId, UserProfile profile,
string logPrefix, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Processes a SWITCH [name/number] command: changes the user's active department when the
/// identifier resolves to one of their SMS-supported memberships (list index, department id,
/// then exact/partial name or code). Empty identifier returns the options list. Never returns null.
/// </summary>
Task<string> BuildSwitchCommandResponseAsync(UserProfile profile, string departmentIdentifier,
string logPrefix, CancellationToken cancellationToken = default(CancellationToken));
}
}
3 changes: 2 additions & 1 deletion Core/Resgrid.Model/TextCommandTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum TextCommandTypes
Units = 7,
MyStatus = 8,
CustomAction = 9,
CustomStaffing = 10
CustomStaffing = 10,
SwitchDepartment = 11
}
}
44 changes: 29 additions & 15 deletions Core/Resgrid.Services/DepartmentsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,32 +443,46 @@ public async Task<Department> GetActiveSmsDepartmentForUserAsync(string userId,
if (allMembers == null || allMembers.Count == 0)
return null;

// Prefer the member marked IsActive, then IsDefault, then first.
var ordered = allMembers
// The user's ACTIVE department (IsActive, then IsDefault, then first) — no plan-based
// auto-picking. If the active department's plan doesn't support SMS the caller's plan gate
// handles it (offering a switch when the user has other supported departments) rather than
// this resolver silently acting in a department the user didn't select.
var preferred = allMembers
.Where(m => !m.IsDeleted)
.OrderByDescending(m => m.IsActive)
.ThenByDescending(m => m.IsDefault)
.ToList();
.FirstOrDefault();

if (ordered.Count == 0)
if (preferred == null)
return null;

var preferred = ordered[0];
return await GetDepartmentByIdAsync(preferred.DepartmentId, bypassCache);
}

// If the preferred department supports SMS (non-free plan) use it; otherwise auto-pick the first
// of the user's departments that does, so a user whose active department is on the free plan still
// lands in an SMS-capable department. If none support SMS, fall back to the preferred one so the
// caller's plan gate returns the proper "plan doesn't support" message.
var canProvisionNumber = await Task.WhenAll(ordered.Select(membership =>
_limitsService.CanDepartmentProvisionNumberAsync(membership.DepartmentId)));
public async Task<List<DepartmentMember>> GetSmsSupportedMembershipsForUserAsync(string userId)
{
var allMembers = await GetAllDepartmentsForUserAsync(userId);
if (allMembers == null || allMembers.Count == 0)
return new List<DepartmentMember>();

// Stable order (active first, then by id) so a numeric pick from a displayed list maps to the
// same membership when the follow-up "switch N" message arrives in a later, stateless request.
// Disabled memberships (IsDisabled is nullable; null means not disabled) are excluded — a user
// deactivated in a department must not be offered it as a switch target.
var candidates = allMembers
.Where(m => !m.IsDeleted && m.IsDisabled != true)
.OrderByDescending(m => m.IsActive)
.ThenBy(m => m.DepartmentId)
.ToList();

for (var i = 0; i < ordered.Count; i++)
var supported = new List<DepartmentMember>();
foreach (var membership in candidates)
{
if (canProvisionNumber[i])
return await GetDepartmentByIdAsync(ordered[i].DepartmentId, bypassCache);
if (await _limitsService.CanDepartmentProvisionNumberAsync(membership.DepartmentId))
supported.Add(membership);
Comment on lines +472 to +482

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Exclude disabled memberships from switch authorization.

Every SMS/chatbot switch handler trusts this list. With only IsDeleted filtered, a disabled membership can be activated; the legacy Twilio path then executes commands without another membership-validity check.

Proposed fix
 var candidates = allMembers
-	.Where(m => !m.IsDeleted)
+	.Where(m => !m.IsDeleted && !m.IsDisabled.GetValueOrDefault())
 	.OrderByDescending(m => m.IsActive)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var candidates = allMembers
.Where(m => !m.IsDeleted)
.OrderByDescending(m => m.IsActive)
.ThenBy(m => m.DepartmentId)
.ToList();
var supported = new List<DepartmentMember>();
foreach (var membership in candidates)
{
if (canProvisionNumber[i])
return await GetDepartmentByIdAsync(ordered[i].DepartmentId, bypassCache);
if (await _limitsService.CanDepartmentProvisionNumberAsync(membership.DepartmentId))
supported.Add(membership);
var candidates = allMembers
.Where(m => !m.IsDeleted && !m.IsDisabled.GetValueOrDefault())
.OrderByDescending(m => m.IsActive)
.ThenBy(m => m.DepartmentId)
.ToList();
var supported = new List<DepartmentMember>();
foreach (var membership in candidates)
{
if (await _limitsService.CanDepartmentProvisionNumberAsync(membership.DepartmentId))
supported.Add(membership);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Services/DepartmentsService.cs` around lines 470 - 480, Update
the candidate filtering in the DepartmentMember selection flow to exclude
disabled memberships in addition to deleted ones before switch authorization.
Preserve the existing ordering and provisioning-limit checks so only active,
non-deleted memberships reach the supported list used by the SMS/chatbot switch
handlers.

}

return await GetDepartmentByIdAsync(preferred.DepartmentId, bypassCache);
return supported;
}


Expand Down
1 change: 1 addition & 0 deletions Core/Resgrid.Services/ServicesModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType<SystemAuditsService>().As<ISystemAuditsService>().InstancePerLifetimeScope();
builder.RegisterType<ContactVerificationService>().As<IContactVerificationService>().InstancePerLifetimeScope();
builder.RegisterType<SecurityPinService>().As<ISecurityPinService>().InstancePerLifetimeScope();
builder.RegisterType<TextDepartmentSwitchService>().As<ITextDepartmentSwitchService>().InstancePerLifetimeScope();
builder.RegisterType<AutofillsService>().As<IAutofillsService>().InstancePerLifetimeScope();
builder.RegisterType<UnitStatesService>().As<IUnitStatesService>().InstancePerLifetimeScope();
builder.Register(_ =>
Expand Down
Loading
Loading