using System.Text.RegularExpressions; using CareFix.Api.Infrastructure; namespace CareFix.Api.Fixes; /// Who must approve a fix. Kept separate from the service so it can be tested on its own. public static class ApprovalRules { public static string RuleText(string risk) => risk switch { "Low" => "1 approval from a Support Lead or Support Head", "Medium" => "2 approvals from different people: a Support Lead (or Head) and a Product Owner (or Head)", _ => "Support Head approval plus the hospital's written consent file", }; /// Whether this role may record an approval at all. Four-eyes and duplicate checks live in the service. public static bool CanApprove(string role, string risk) => risk switch { "Low" => role is Roles.Lead or Roles.Head, "Medium" => role is Roles.Lead or Roles.ProductOwner or Roles.Head, _ => role is Roles.Head, }; /// Whether the approvals collected so far are enough to run the fix. public static bool IsSatisfied(string risk, IReadOnlyList approvals, bool hasConsent) { var ok = approvals.Where(a => a.Decision == "Approved").ToList(); return risk switch { "Low" => ok.Any(a => a.ApproverRole is Roles.Lead or Roles.Head), "Medium" => ok.Where(a => a.ApproverRole is Roles.Lead or Roles.Head) .Any(l => ok.Any(p => p.ApproverRole is Roles.ProductOwner or Roles.Head && p.ApproverId != l.ApproverId)), _ => hasConsent && ok.Any(a => a.ApproverRole == Roles.Head), }; } } /// How a fix's risk level is worked out. public static partial class RiskRules { private static readonly string[] Order = ["Low", "Medium", "High"]; [GeneratedRegex("(AMT|AMOUNT|RATE|PRICE|TOTAL|NETAMT|NET_AMT|DISC|PAID|REFUND|BALANCE|DUE|QTY|QUANTITY|TAX|GST|CGST|SGST|DEPOSIT|ADVANCE)", RegexOptions.IgnoreCase)] private static partial Regex AmountColumn(); /// Columns holding money or quantities are always High risk, whatever the dictionary says. public static bool IsAmountColumn(string column) => AmountColumn().IsMatch(column); public static string Max(string a, string b) => Array.IndexOf(Order, a) >= Array.IndexOf(Order, b) ? a : b; }