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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,4 @@ opencode.json
/.claude
/Web/Resgrid.Web/wwwroot/js/ng
/tmp
/artifacts
2 changes: 1 addition & 1 deletion Core/Resgrid.Config/NerisConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class NerisConfig
/// <summary>Production API root of the pinned contract.</summary>
public static string BaseUrl = "https://api.neris.fsri.org/v1";

/// <summary>Sandbox root used by profiles whose Environment is sandbox; empty means "same as BaseUrl".</summary>
/// <summary>Sandbox root used by sandbox profiles. Empty disables outbound requests; production is never a fallback.</summary>
public static string SandboxBaseUrl = "";

/// <summary>Contract version the provider was generated against (Providers/Resgrid.Providers.Neris/Contract).</summary>
Expand Down
39 changes: 39 additions & 0 deletions Core/Resgrid.Framework/RecordNarrativeFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using HtmlAgilityPack;

namespace Resgrid.Framework
{
/// <summary>Records permit basic narrative formatting, without links, media, attributes or active content.</summary>
public static class RecordNarrativeFormatter
{
private static readonly HashSet<string> Allowed = new(StringComparer.OrdinalIgnoreCase) { "p", "br", "strong", "b", "em", "i", "u", "ul", "ol", "li", "blockquote", "h1", "h2", "h3" };
private static readonly HashSet<string> Drop = new(StringComparer.OrdinalIgnoreCase) { "script", "style", "iframe", "object", "embed", "svg", "math", "template", "noscript" };
private static bool IsHtml(string value) => Regex.IsMatch(value ?? "", @"<\s*/?[a-zA-Z][^>]*>", RegexOptions.None, TimeSpan.FromMilliseconds(250));
public static string ForStorage(string value) => IsHtml(value) ? Render(value) : value;
public static string Render(string value)
{
if (!IsHtml(value)) return WebUtility.HtmlEncode(value ?? "");
var document = new HtmlDocument(); document.LoadHtml(value); var html = new StringBuilder();
void Append(HtmlNode node, int depth)
{
if (depth > 64) throw new ArgumentException("Narrative formatting is nested too deeply.");
if (node.NodeType == HtmlNodeType.Text) { html.Append(WebUtility.HtmlEncode(HtmlEntity.DeEntitize(node.InnerText))); return; }
if (node.NodeType == HtmlNodeType.Comment || Drop.Contains(node.Name)) return;
var allowed = Allowed.Contains(node.Name);
if (allowed) html.Append('<').Append(node.Name).Append('>');
foreach (var child in node.ChildNodes) Append(child, depth + 1);
if (allowed && node.Name != "br") html.Append("</").Append(node.Name).Append('>');
}
Append(document.DocumentNode, 0); return html.ToString();
}
public static bool HasText(string value)
{
var document = new HtmlDocument(); document.LoadHtml(Render(value));
return !string.IsNullOrWhiteSpace(HtmlEntity.DeEntitize(document.DocumentNode.InnerText).Replace('\u200b', ' '));
}
}
}
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>أوقات الإرسال</value></data>
<data name="CallCreated" xml:space="preserve"><value>إنشاء النداء</value></data>
<data name="CallAnswered" xml:space="preserve"><value>الرد على النداء</value></data>
<data name="CallArrival" xml:space="preserve"><value>أول وصول</value></data>
<data name="CallArrival" xml:space="preserve"><value>وصول المكالمة إلى مركز الإرسال</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>انتهاء الحادث</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>مركز الإرسال</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>رمز المحدد</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.de.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Dispositionszeiten</value></data>
<data name="CallCreated" xml:space="preserve"><value>Einsatz erstellt</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Anruf angenommen</value></data>
<data name="CallArrival" xml:space="preserve"><value>Erstes Eintreffen</value></data>
<data name="CallArrival" xml:space="preserve"><value>Eingang des Anrufs in der Leitstelle</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Einsatz beendet</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Leitstelle</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Determinantencode</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.el.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Χρόνοι αποστολής</value></data>
<data name="CallCreated" xml:space="preserve"><value>Δημιουργία κλήσης</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Απάντηση κλήσης</value></data>
<data name="CallArrival" xml:space="preserve"><value>Πρώτη άφιξη</value></data>
<data name="CallArrival" xml:space="preserve"><value>Λήψη κλήσης στο κέντρο επιχειρήσεων</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Λήξη συμβάντος</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Κέντρο αποστολής</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Κωδικός καθοριστή</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.en.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Dispatch times</value></data>
<data name="CallCreated" xml:space="preserve"><value>Call created</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Call answered</value></data>
<data name="CallArrival" xml:space="preserve"><value>First arrival</value></data>
<data name="CallArrival" xml:space="preserve"><value>Call received at dispatch</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Incident cleared</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Dispatch center</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Determinant code</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.es.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Tiempos de despacho</value></data>
<data name="CallCreated" xml:space="preserve"><value>Llamada creada</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Llamada contestada</value></data>
<data name="CallArrival" xml:space="preserve"><value>Primera llegada</value></data>
<data name="CallArrival" xml:space="preserve"><value>Recepción de la llamada en la central</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Incidente despejado</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Centro de despacho</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Código determinante</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Horaires de régulation</value></data>
<data name="CallCreated" xml:space="preserve"><value>Appel créé</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Appel décroché</value></data>
<data name="CallArrival" xml:space="preserve"><value>Première arrivée</value></data>
<data name="CallArrival" xml:space="preserve"><value>Réception de l'appel au centre de répartition</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Incident terminé</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Centre de régulation</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Code déterminant</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.it.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Tempi di dispatch</value></data>
<data name="CallCreated" xml:space="preserve"><value>Chiamata creata</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Chiamata risposta</value></data>
<data name="CallArrival" xml:space="preserve"><value>Primo arrivo</value></data>
<data name="CallArrival" xml:space="preserve"><value>Ricezione della chiamata in centrale</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Incidente concluso</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Centrale operativa</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Codice determinante</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Czasy dysponowania</value></data>
<data name="CallCreated" xml:space="preserve"><value>Utworzenie wezwania</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Odebranie zgłoszenia</value></data>
<data name="CallArrival" xml:space="preserve"><value>Pierwsze przybycie</value></data>
<data name="CallArrival" xml:space="preserve"><value>Odebranie zgłoszenia w dyspozytorni</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Zakończenie zdarzenia</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Centrum dyspozytorskie</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Kod determinanta</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Utlarmningstider</value></data>
<data name="CallCreated" xml:space="preserve"><value>Larm skapat</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Samtal besvarat</value></data>
<data name="CallArrival" xml:space="preserve"><value>Första ankomst</value></data>
<data name="CallArrival" xml:space="preserve"><value>Samtal inkom till larmcentral</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Insats avslutad</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Larmcentral</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Determinantkod</value></data>
Expand Down
4 changes: 2 additions & 2 deletions Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
Expand Down Expand Up @@ -258,7 +258,7 @@
<data name="DispatchTimes" xml:space="preserve"><value>Часи диспетчеризації</value></data>
<data name="CallCreated" xml:space="preserve"><value>Виклик створено</value></data>
<data name="CallAnswered" xml:space="preserve"><value>Виклик прийнято</value></data>
<data name="CallArrival" xml:space="preserve"><value>Перше прибуття</value></data>
<data name="CallArrival" xml:space="preserve"><value>Надходження виклику до диспетчерської</value></data>
<data name="IncidentCleared" xml:space="preserve"><value>Інцидент завершено</value></data>
<data name="DispatchCenter" xml:space="preserve"><value>Диспетчерський центр</value></data>
<data name="DeterminantCode" xml:space="preserve"><value>Код детермінанти</value></data>
Expand Down
2 changes: 2 additions & 0 deletions Core/Resgrid.Model/Providers/INerisProviders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Resgrid.Model.Providers
/// <summary>Department NERIS profile, credential, value sets and crosswalks (RMS plan section 5.5).</summary>
public interface INerisProfileService
{
/// <summary>Credential-free identity of the configured entity, environment and effective endpoint.</summary>
string GetDestinationIdentity(RmsNerisProfile profile);
Task<RmsNerisProfile> GetProfileAsync(int departmentId);

/// <summary>Saves the profile; a non-null credential replaces the stored one (encrypted per department), null keeps it.</summary>
Expand Down
4 changes: 3 additions & 1 deletion Core/Resgrid.Model/Providers/IPdfProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
public interface IPdfProvider
{
byte[] ConvertHtmlToPdf(string html);
/// <summary>Departmental Records require an explicit page size; older providers retain their existing default.</summary>
byte[] ConvertHtmlToPdf(string html, string pageSize) => ConvertHtmlToPdf(html);
}
}
}
8 changes: 7 additions & 1 deletion Core/Resgrid.Model/Records/IncidentReportContracts.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System;
using System.Collections.Generic;

namespace Resgrid.Model
{
/// <summary>The incident report aggregate as the Web and the mapper read it: header plus the working-draft child rows (or a revision's copies).</summary>
public class IncidentReportAggregate
{
public RecordUdfSection CustomFields { get; set; }
public List<RmsRecordAttachment> Attachments { get; set; } = new List<RmsRecordAttachment>();
public List<RmsEvidenceArtifact> Evidence { get; set; } = new List<RmsEvidenceArtifact>();
public RmsIncidentReport Report { get; set; }
public RmsLocation Location { get; set; }
public List<RmsIncidentType> Types { get; set; } = new List<RmsIncidentType>();
Expand Down Expand Up @@ -101,6 +104,7 @@ public class IncidentLocationInput
/// </summary>
public class IncidentReportDraftInput
{
public RecordUdfInput CustomFields { get; set; }
public string IncidentNumber { get; set; }
public DateTime? CallCreatedOn { get; set; }
public DateTime? CallAnsweredOn { get; set; }
Expand Down Expand Up @@ -161,6 +165,7 @@ public class IncidentResourceInput
/// </summary>
public class IncidentCasualtyRescueInput
{
public string CasualtyId { get; set; }
public RmsCasualtyRescueKind Kind { get; set; }
public string PersonType { get; set; }
public string PersonnelUserId { get; set; }
Expand Down Expand Up @@ -264,6 +269,7 @@ public class IncidentPropertyInput
/// <summary>VIN and plate are restricted; the service drops them from a caller without RecordRestricted_View.</summary>
public class IncidentVehicleInput
{
public string VehicleId { get; set; }
public string VehicleKind { get; set; }
public string Make { get; set; }
public string Model { get; set; }
Expand Down
7 changes: 7 additions & 0 deletions Core/Resgrid.Model/Records/NerisContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ namespace Resgrid.Model
/// </summary>
public class NerisIncidentSnapshot
{
public RecordUdfSection CustomFields { get; set; }
/// <summary>Departmental revision content. The national mapper never emits these collections.</summary>
public List<RmsRecordAttachment> Attachments { get; set; } = new List<RmsRecordAttachment>();
public List<RmsEvidenceArtifact> Evidence { get; set; } = new List<RmsEvidenceArtifact>();
public RmsIncidentReport Report { get; set; }
public RmsLocation Location { get; set; }
public List<RmsIncidentType> Types { get; set; } = new List<RmsIncidentType>();
Expand Down Expand Up @@ -117,6 +121,9 @@ public enum NerisOutcomeKind
/// <summary>One exchange with the destination, reduced to what the submission row and the workflow triggers may see.</summary>
public class NerisSubmissionOutcome
{
/// <summary>The queued payload was refused locally before credentials or HTTP; no destination response is implied.</summary>
public bool LocalValidationFailure { get; set; }
public bool DeliveryUncertain { get; set; }
public NerisOutcomeKind Kind { get; set; }
public int? StatusCode { get; set; }
public string ExternalId { get; set; }
Expand Down
9 changes: 9 additions & 0 deletions Core/Resgrid.Model/Records/RecordIdempotencyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Resgrid.Model
{
public sealed class RecordIdempotencyException : InvalidOperationException
{
public RecordIdempotencyException(string message) : base(message) { }
}
}
24 changes: 24 additions & 0 deletions Core/Resgrid.Model/Records/RecordUdfContracts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;

namespace Resgrid.Model
{
/// <summary>Values only: labels, types, visibility and classification come from the pinned published definition.</summary>
public class RecordUdfInput
{
public string DefinitionId { get; set; }
public Dictionary<string, string> Values { get; set; } = new Dictionary<string, string>();
}
public class RecordUdfSection
{
public string DefinitionId { get; set; }
public string RecordDefinitionKey { get; set; }
public int RecordDefinitionVersion { get; set; }
public int ExtensionVersion { get; set; }
public List<RecordUdfField> Fields { get; set; } = new List<RecordUdfField>();
}
public class RecordUdfField
{
public UdfField Field { get; set; }
public string Value { get; set; }
}
}
Loading
Loading