-
-
Notifications
You must be signed in to change notification settings - Fork 86
Develop #483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #483
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,7 +41,11 @@ public async Task<Call> GenerateCall(CallEmail email, string managingUser, List< | |
| c.Type = ParseCallType(GetValue(data, 1), callTypes); | ||
| c.Priority = ParseCallPriority(GetValue(data, 2), priority, activePriorities); | ||
| c.MapPage = GetValue(data, 4); | ||
| c.NatureOfCall = GetValue(data, 5); | ||
|
|
||
| // NATURE is a non-nullable column but CADs do send the segment empty. Fall back to | ||
| // the call type and then the subject so the dispatch still lands, GetValue hands | ||
| // back a null for a blank segment and that used to fail the insert. | ||
| c.NatureOfCall = GetValue(data, 5) ?? GetValue(data, 1) ?? email.Subject; | ||
|
Comment on lines
+44
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 'NatureOfCall|CREATE TABLE.*Calls|Calls.*NatureOfCall' \
--glob '*.cs' --glob '*.sql' --glob '*.json'Repository: Resgrid/Core Length of output: 150 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
fd -i 'ResgridEmailTemplate.cs|CallEmailFactory|Call.*\\.cs$' . || true
printf '%s\n' '--- template structure ---'
ast-grep outline Core/Resgrid.Services/CallEmailTemplates/ResgridEmailTemplate.cs 2>/dev/null || true
printf '%s\n' '--- relevant symbols and call sites ---'
rg -n -C 5 'NatureOfCall|FirstWithValue|EnsureRequiredValues|GetValue\\(' \
Core/Resgrid.Services --glob '*.cs' || trueRepository: Resgrid/Core Length of output: 816 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- ResgridEmailTemplate.cs ---'
cat -n Core/Resgrid.Services/CallEmailTemplates/ResgridEmailTemplate.cs
printf '%s\n' '--- CallEmailFactory structure ---'
ast-grep outline Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs 2>/dev/null || true
printf '%s\n' '--- CallEmailFactory.cs relevant sections ---'
rg -n -C 8 -e 'NatureOfCall' -e 'FirstWithValue' -e 'EnsureRequiredValues' -e 'GetValue\(' \
Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs || true
printf '%s\n' '--- model declarations and mappings ---'
rg -n -C 5 -e 'NatureOfCall' -e 'class Call' -e 'record Call' \
Core/Resgrid.Model Core/Resgrid.Services Tests --glob '*.cs' || trueRepository: Resgrid/Core Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- Call model ---'
cat -n Core/Resgrid.Model/Call.cs | sed -n '1,90p'
printf '%s\n' '--- schema and migration candidates ---'
fd -i 'migration|schema|sql|database|config' . -t f | head -200
printf '%s\n' '--- exact NatureOfCall declarations/usages in relevant files ---'
rg -n -C 3 -e 'NatureOfCall' \
Core/Resgrid.Model/Call.cs \
Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs \
Tests/Resgrid.Tests/Services/CallEmailFactoryTests.cs \
--glob '*.cs' || true
printf '%s\n' '--- persisted Calls definitions ---'
rg -n -C 3 -i 'NatureOfCall|CREATE TABLE[[:space:]]+\\[?Calls|ALTER TABLE[[:space:]]+\\[?Calls' \
--glob '*.sql' --glob '*.cs' --glob '*.xml' --glob '*.json' --glob '*.config' . \
| head -300 || trueRepository: Resgrid/Core Length of output: 31388 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- NatureOfCall in migration/schema files ---'
rg -n -C 4 -F 'NatureOfCall' \
Providers/Resgrid.Providers.Migrations \
Providers/Resgrid.Providers.MigrationsPg \
Docker \
--glob '*.cs' --glob '*.sql' || true
printf '%s\n' '--- fallback tests ---'
sed -n '720,815p' Tests/Resgrid.Tests/Services/CallEmailFactoryTests.cs
printf '%s\n' '--- factory implementation ---'
cat -n Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs | sed -n '1,110p'
printf '%s\n' '--- deterministic fallback probe ---'
python3 - <<'PY'
def first_with_value(*values):
for value in values:
if value is not None and value.strip():
return value.strip()[:4000]
return ""
def template_assignment(nature, call_type, subject):
return nature if nature is not None else call_type if call_type is not None else subject
subject = "S" * 5001
assigned = template_assignment(None, None, subject)
factory_result = assigned if assigned.strip() else first_with_value(subject, "body", "text")
print(f"subject_length={len(subject)}")
print(f"assigned_length={len(assigned)}")
print(f"factory_result_length={len(factory_result)}")
print(f"direct_subject_bypasses_normalizer={len(assigned) > 4000 and len(factory_result) == len(assigned)}")
PYRepository: Resgrid/Core Length of output: 9570 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- all 4000-character normalization sites ---'
rg -n -C 3 -F 'Truncate(4000)' Core Tests Providers Web Workers --glob '*.cs' || true
printf '%s\n' '--- long-value tests and relevant helper definitions ---'
rg -n -C 5 -i '4000|truncate|FirstWithValue|EnsureRequiredValues' \
Tests/Resgrid.Tests/Services/CallEmailFactoryTests.cs \
Core/Resgrid.Framework \
Core/Resgrid.Model \
Core/Resgrid.Services/CallEmailTemplates \
--glob '*.cs' || true
printf '%s\n' '--- PostgreSQL Calls schema references ---'
rg -n -C 5 -i 'nature.?of.?call|create table.{0,40}calls' \
Providers/Resgrid.Providers.MigrationsPg --glob '*.cs' --glob '*.sql' || trueRepository: Resgrid/Core Length of output: 20180 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- change scope ---'
git diff --stat -- Core/Resgrid.Services/CallEmailTemplates/ResgridEmailTemplate.cs \
Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs \
Tests/Resgrid.Tests/Services/CallEmailFactoryTests.cs
printf '%s\n' '--- relevant diff ---'
git diff --unified=5 -- \
Core/Resgrid.Services/CallEmailTemplates/ResgridEmailTemplate.cs \
Core/Resgrid.Services/CallEmailTemplates/CallEmailFactory.cs \
Tests/Resgrid.Tests/Services/CallEmailFactoryTests.cs \
| sed -n '1,260p'Repository: Resgrid/Core Length of output: 193 Normalize the subject fallback before assignment. When NATURE and TYPE are blank, 🤖 Prompt for AI Agents |
||
|
|
||
| // Re-join everything from index 6 on, a pipe inside the notes text shouldn't | ||
| // truncate them. When NOTES isn't supplied the raw body stays in Notes, which | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| using System; | ||
| using System; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using Resgrid.Model; | ||
| using Resgrid.Model.Providers; | ||
|
|
||
|
|
@@ -14,33 +17,36 @@ public PhoneNumberResult Process(string phoneNumber, string countryCode = null) | |
| { | ||
| var territory = string.IsNullOrWhiteSpace(countryCode) ? "US" : countryCode.ToUpperInvariant(); | ||
|
|
||
| // Normalize: strip non-digit characters except leading + | ||
| var cleaned = phoneNumber?.Trim() ?? string.Empty; | ||
| // Strip characters the parser cannot see past. Real stored numbers carry invisible | ||
| // bidi/format marks pasted in from other apps, tabs, and non-standard brackets - all of | ||
| // which make an otherwise perfectly good number fail to parse. | ||
| var cleaned = Sanitize(phoneNumber); | ||
|
|
||
| GlobalPhone.Number number; | ||
| // Try with the given territory first | ||
| if (GlobalPhone.GlobalPhone.TryParse(cleaned, out number, territory) && number.IsValid) | ||
| { | ||
| result.IsValid = true; | ||
| result.InternationalNumber = number.InternationalString; | ||
| result.LocalNumber = number.NationalString; | ||
| if (string.IsNullOrWhiteSpace(cleaned)) | ||
| return result; | ||
| } | ||
|
|
||
| // Try with no territory hint (for numbers starting with +) | ||
| if (GlobalPhone.GlobalPhone.TryParse(cleaned, out number, "ZZ") && number.IsValid) | ||
| // In order of confidence. The first two are the original behaviour; the rest only ever | ||
| // run once those have failed, so a number that parsed before still parses the same way. | ||
| foreach (var attempt in Attempts(cleaned, territory)) | ||
| { | ||
| if (!GlobalPhone.GlobalPhone.TryParse(attempt.Value, out var candidate, attempt.Territory) || | ||
| candidate == null || !candidate.IsValid) | ||
| continue; | ||
|
|
||
| result.IsValid = true; | ||
| result.InternationalNumber = number.InternationalString; | ||
| result.LocalNumber = number.NationalString; | ||
| result.InternationalNumber = candidate.InternationalString; | ||
| result.LocalNumber = candidate.NationalString; | ||
| result.Region = candidate.RegionCode; | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| result.IsValid = number != null && number.IsValid; | ||
| if (number != null) | ||
| // Nothing parsed. Report against the original input so the caller sees what it passed in. | ||
| if (GlobalPhone.GlobalPhone.TryParse(cleaned, out var parsed, territory) && parsed != null) | ||
| { | ||
| result.InternationalNumber = number.InternationalString; | ||
| result.LocalNumber = number.NationalString; | ||
| result.InternationalNumber = parsed.InternationalString; | ||
| result.LocalNumber = parsed.NationalString; | ||
| result.Region = parsed.RegionCode; | ||
| } | ||
| } | ||
| catch (Exception e) | ||
|
|
@@ -51,5 +57,72 @@ public PhoneNumberResult Process(string phoneNumber, string countryCode = null) | |
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static (string Value, string Territory)[] Attempts(string cleaned, string territory) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False-positive rule application in Providers/Resgrid.Providers.Number/PhoneNumberProcesserProvider.cs: Rule [103] applies to immutable instance fields or compile-time constants, but Kody rule violation: Use `readonly` or `const` for Immutable Data private static readonly (string Value, string Territory)[] SomePrecomputedAttempts = ...; // if immutable/shared data, mark fields readonly/constPrompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| { | ||
| var digits = new string(cleaned.Where(char.IsDigit).ToArray()); | ||
|
|
||
| return new[] | ||
| { | ||
| // Original behaviour: the caller's region, then no region hint (for "+" numbers). | ||
| (cleaned, territory), | ||
| (cleaned, "ZZ"), | ||
|
|
||
| // "00" is the international access prefix in most of the world - the typed equivalent of | ||
| // "+". Stored values routinely use it ("0040...", "00306..."), and it parses as nothing. | ||
| (cleaned.StartsWith("00", StringComparison.Ordinal) && digits.Length > 4 | ||
| ? "+" + digits.Substring(2) | ||
| : null, "ZZ"), | ||
|
|
||
| // A country code with no "+" at all ("447700900123"). Only worth trying when the length | ||
| // rules out a national number, and only after the region attempts have failed - so a | ||
| // valid national number is never reinterpreted as an international one. | ||
| (digits.Length >= 11 && digits.Length <= 15 && !cleaned.Contains('+') | ||
| ? "+" + digits | ||
| : null, "ZZ") | ||
| } | ||
| .Where(a => !string.IsNullOrWhiteSpace(a.Item1)) | ||
| .Select(a => (a.Item1, a.Item2)) | ||
| .ToArray(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Removes characters that carry no dialling meaning but do stop the number parsing: Unicode | ||
| /// format and control marks (bidi overrides pasted in from other applications), and bracket | ||
| /// styles the parser does not recognise. Digits, "+", and the ordinary separators the parser | ||
| /// already understands are left exactly as they are. | ||
| /// </summary> | ||
| private static string Sanitize(string phoneNumber) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(phoneNumber)) | ||
| return string.Empty; | ||
|
|
||
| var builder = new StringBuilder(phoneNumber.Length); | ||
|
|
||
| foreach (var character in phoneNumber) | ||
| { | ||
| var category = CharUnicodeInfo.GetUnicodeCategory(character); | ||
|
|
||
| if (category == UnicodeCategory.Format || category == UnicodeCategory.Control) | ||
| continue; | ||
|
|
||
| // "{201} 555-0123" is a real stored shape; the parser handles "()" but not "{}" or "[]". | ||
| if (character == '{' || character == '[') | ||
| { | ||
| builder.Append('('); | ||
| continue; | ||
| } | ||
|
|
||
| if (character == '}' || character == ']') | ||
| { | ||
| builder.Append(')'); | ||
| continue; | ||
| } | ||
|
|
||
| builder.Append(character); | ||
| } | ||
|
|
||
| return builder.ToString().Trim(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| using System; | ||
| using Resgrid.Model; | ||
| using Resgrid.Model.Repositories.Queries.Contracts; | ||
| using Resgrid.Repositories.DataRepository.Configs; | ||
| using Resgrid.Repositories.DataRepository.Extensions; | ||
|
|
||
| namespace Resgrid.Repositories.DataRepository.Queries.ActionLogs | ||
| { | ||
| public class SelectLastActionLogsForDepartmentIncHiddenQuery : ISelectQuery | ||
| { | ||
| private readonly SqlConfiguration _sqlConfiguration; | ||
| public SelectLastActionLogsForDepartmentIncHiddenQuery(SqlConfiguration sqlConfiguration) | ||
| { | ||
| // Guarded here so every SqlConfiguration access in GetQuery is provably safe. A missing | ||
| // configuration is a container misregistration, fail at construction rather than handing | ||
| // back a query string that would reach the database malformed. | ||
| _sqlConfiguration = sqlConfiguration ?? throw new ArgumentNullException(nameof(sqlConfiguration)); | ||
| } | ||
|
|
||
| public string GetQuery() | ||
| { | ||
| var queryTemplate = _sqlConfiguration.SelectLastActionLogsForDepartmentIncHiddenQuery; | ||
|
|
||
| if (string.IsNullOrWhiteSpace(queryTemplate)) | ||
| throw new InvalidOperationException( | ||
| $"{nameof(SqlConfiguration.SelectLastActionLogsForDepartmentIncHiddenQuery)} is not set on {_sqlConfiguration.GetType().Name}."); | ||
|
|
||
| var query = queryTemplate | ||
| .ReplaceQueryParameters(_sqlConfiguration, _sqlConfiguration.SchemaName, | ||
| string.Empty, | ||
| _sqlConfiguration.ParameterNotation, | ||
| new string[] { | ||
| "%DID%", | ||
| "%DAA%", | ||
| "%LTS%", | ||
| "%TS%" | ||
| }, | ||
| new string[] { | ||
| "DepartmentId", | ||
| "DisableAutoAvailable", | ||
| "LatestTimestamp", | ||
| "Timestamp" | ||
| }, | ||
| new string[] { | ||
| "%ACTIONLOGSTABLE%", | ||
| "%ASPNETUSERSTABLE%", | ||
| "%DEPARTMENTMEMBERSTABLE%" | ||
| }, | ||
| new string[] { | ||
| _sqlConfiguration.ActionLogsTable, | ||
| _sqlConfiguration.UserTable, | ||
| _sqlConfiguration.DepartmentMembersTable | ||
| } | ||
| ); | ||
|
|
||
| return query; | ||
| } | ||
|
|
||
| public string GetQuery<TEntity>() where TEntity : class, IEntity | ||
| { | ||
| throw new System.NotImplementedException(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking API change in Core/Resgrid.Model/Repositories/IActionLogsRepository.cs:
GetLastActionLogsForDepartmentAsync(int departmentId, bool disableAutoAvailable, DateTime timeStamp, bool includeHiddenAndDisabled = false)changes a public repository interface contract for callers and implementers even with a default value. Add an explicit BREAKING CHANGE note that documents the signature change, affected consumers and implementations, and required migration steps.Kody rule violation: Call out breaking changes explicitly
Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.