diff --git a/Core/Resgrid.Config/DataProtectionConfig.cs b/Core/Resgrid.Config/DataProtectionConfig.cs
index 64827e91..1b9ac9ba 100644
--- a/Core/Resgrid.Config/DataProtectionConfig.cs
+++ b/Core/Resgrid.Config/DataProtectionConfig.cs
@@ -52,6 +52,14 @@ public static class DataProtectionConfig
/// Maximum field items one broker request may carry; larger requests are refused.
public static int BrokerMaxItemsPerRequest = 200;
+ ///
+ /// Purposes the broker's workload decrypt lane (POST api/v1/broker/workload/decrypt?purpose=) accepts, comma
+ /// separated (RMS plan section 5.9.4). Each purpose is an egress the department acknowledged in the
+ /// application before the caller reaches the broker: neris-submission (worker 41) and records-export
+ /// (worker 45 / Workflow renders). Empty disables the lane; callers fail closed with workload_purpose_denied.
+ ///
+ public static string BrokerWorkloadPurposes = "neris-submission,records-export";
+
/// True on the broker host to run the ADP migration coordinator sweep there (the only
/// host with a real KMS adapter). Workers.Console keeps its sweep for liveness/offboarding
/// flips but never runs nights — its engine reports unavailable.
diff --git a/Core/Resgrid.Config/RecordsFieldConfig.cs b/Core/Resgrid.Config/RecordsFieldConfig.cs
new file mode 100644
index 00000000..5f145851
--- /dev/null
+++ b/Core/Resgrid.Config/RecordsFieldConfig.cs
@@ -0,0 +1,24 @@
+namespace Resgrid.Config
+{
+ ///
+ /// Field Records client change control (RMS plan RMS-1D): the minimum app version each operational app must
+ /// report before the server hands it a Field Records catalog, and the sync page bound. A blank minimum accepts
+ /// any version. Environment keys: RESGRID:RecordsFieldConfig:MinimumResponderVersion and so on.
+ ///
+ public static class RecordsFieldConfig
+ {
+ public static string MinimumResponderVersion = "";
+ public static string MinimumUnitVersion = "";
+ public static string MinimumIncidentCommandVersion = "";
+ public static string MinimumDispatchVersion = "";
+
+ /// Most rows one sync page carries; a field bundle is a working set, not an archive pull.
+ public static int SyncTakeMax = 200;
+
+ /// Most drafts and returned Records of the caller included in one bundle.
+ public static int SyncDraftsMax = 100;
+
+ /// Most open work assignments returned to one caller.
+ public static int AssignmentsMax = 200;
+ }
+}
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx
index 6534225e..2751a5d2 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx
@@ -1064,4 +1064,255 @@
تم الإقرار فييُطبَّق نظام حماية البيانات المتقدّمة: ستفشل عمليات الإرسال المحمية حتى يُسجَّل هذا الإقرار.معرّف السجل
+ إضافة صف
+ إزالة الصف
+ محجوب
+ الإحداثيات (خط العرض، خط الطول) أو مرجع المكان
+ أوقّع هذا السجل بصفتي المؤلف
+ نعم
+ لا
+ تعديل
+ مفعّل
+ معاينة
+ يتبع هذا السجل تعريفًا خاصًا بالقسم. الحقول المميزة بقفل مقيدة؛ وقد تُظهر القواعد حقولًا أو تطلبها أثناء الكتابة.
+ التعريفات
+ تصف تعريفات السجلات النماذج التي يعبّئها قسمك: الأقسام والحقول والقواعد والترقيم ومن يراجع أو يعتمد. النشر يجمّد الإصدار؛ وتثبّت السجلات الإصدار الذي كُتبت به.
+ تعريف جديد
+ إنشاء تعريف
+ البدء من قالب
+ استنساخ تعريف موجود
+ تعريف فارغ
+ استعراض القوالب
+ حزم القوالب هي نقاط انطلاق مُراجعة. تعرض كل حزمة مصادرها وتاريخ مراجعتها؛ وحزم المعاينة قابلة للاستخدام لكنها ما تزال قيد التحقق مع الأقسام.
+ تمت المراجعة
+ المصادر
+ الأقسام
+ الحقول
+ استخدام هذا القالب
+ ملف الاختصاص
+ الإعدادات الإقليمية
+ معاينة القالب
+ أحرف صغيرة وأرقام ونقاط وشرطات. لا يمكن تغييره بعد الإنشاء.
+ الاسم
+ الفئة
+ الوصف
+ المالك
+ المنتج (مقفل)
+ الإصدار المنشور
+ إصدار المسودة
+ إعداد دورة الحياة
+ قدرة العميل
+ متقاعد
+ إظهار التعريفات المتقاعدة
+ إخفاء التعريفات المتقاعدة
+ السجل
+ الإصدار
+ الحالة
+ نُشر في
+ ملاحظات التغيير
+ الإعدادات
+ الموضوعات المسموح بها
+ أدوار المراجع
+ أدوار المعتمد
+ مهلة المراجعة (ساعات)
+ مهلة الاعتماد (ساعات)
+ طلب إقرار المؤلف عند الإنهاء
+ الترقيم
+ بادئة الرقم
+ عرض التسلسل
+ تعيين الرقم
+ تسلسل لكل محطة
+ إعادة تعيين سنويًا
+ التصنيف
+ الاحتفاظ (سنوات)
+ واجهات العميل
+ السماح دون اتصال
+ السماح بالمرفقات
+ خريطة الترحيل (JSON)
+ المخطط (JSON)
+ تحتوي الأقسام على حقول؛ لكل حقل مفتاح وتسمية ونوع وقواعد اختيارية. تحقق قبل النشر؛ يعكس جدول الحقول أدناه آخر مسودة محفوظة.
+ الحقول
+ متكرر
+ القواعد
+ المفتاح
+ التسمية
+ نوع الحقل
+ مطلوب
+ التصنيف
+ العلامات
+ تحقق
+ حفظ المسودة
+ مراجعة الأثر والنشر
+ فتح مسودة جديدة
+ حذف المسودة
+ هل تريد حذف هذه المسودة؟ لن تتأثر الإصدارات المنشورة.
+ هذا الإصدار منشور وللقراءة فقط. افتح مسودة جديدة لإجراء التغييرات.
+ مشكلات التحقق
+ الاختلافات عن القالب الذي أُنشئ منه هذا التعريف
+ أثر النشر
+ النشر يجمّد هذا الإصدار. تحتفظ المسودات الحالية بإصدارها حتى ترحيلها؛ والسجلات المنتهية لا تتغير أبدًا.
+ المسودات المفتوحة على الإصدار الحالي
+ السجلات المنتهية على إصدارات سابقة
+ تغيير جذري
+ جاهزية العملاء
+ التغييرات
+ مقارنة من
+ إلى
+ مقارنة
+ لا توجد اختلافات بين هذين الإصدارين.
+ هل تريد نشر هذا الإصدار؟ سيصبح للقراءة فقط وستستخدمه السجلات الجديدة.
+ نشر
+ يتطلب النشر إذن إدارة تعريفات السجلات.
+ إيقاف
+ سبب إيقاف هذا التعريف
+ ترحيل المسودات المفتوحة
+ معاينة الترحيل
+ الترحيل الآن
+ هل تريد نقل جميع المسودات المفتوحة إلى الإصدار الأحدث؟ سيتم إسقاط القيم غير المعينة.
+ تم إنشاء التعريف. حرّر المخطط وتحقق منه ثم انشره.
+ تم حفظ المسودة.
+ التعريف صالح.
+ يحتوي التعريف على أخطاء. أصلحها قبل النشر.
+ تم حذف المسودة.
+ تم نشر الإصدار {0}.
+ تم إيقاف التعريف. تبقى السجلات الحالية قابلة للقراءة؛ ولا يمكن إنشاء سجلات جديدة.
+ التقارير المحفوظة
+ تستعلم التقارير المحفوظة عن سجلات تعريف واحد حسب الحقل وتجمعها وتلخصها وتصدّرها إلى CSV. تحترم النتائج رؤية السجلات والحقول المقيدة.
+ تقرير جديد
+ اسم التقرير
+ إعدادات التقرير
+ الأعمدة
+ أعمدة السجل
+ اختر تعريفًا لتحديد الأعمدة.
+ ترتيب حسب
+ تنازلي
+ تجميع حسب
+ التجميعات (JSON)
+ Count وSum وAverage وMin وMax على الحقول القابلة للتجميع
+ عوامل التصفية (JSON)
+ الحقول القابلة للتصفية
+ تعيينات الإصدارات (JSON)
+ الحد الأقصى للصفوف
+ تضمين المسودات
+ تضمين الحقول المقيدة
+ تحقق
+ تشغيل
+ آخر تشغيل
+ هل تريد حذف هذا التقرير المحفوظ؟
+ الصفوف
+ المجموعات
+ العدد
+ تم اقتطاع النتائج عند حد الصفوف؛ ضيّق عوامل التصفية أو صدّر إلى CSV.
+ تم تضمين السجلات على إصدارات تعريف غير معينة مع خلايا فارغة
+ تم حفظ التقرير.
+ تم حذف التقرير.
+ التقرير صالح.
+ يحتوي التقرير على أخطاء.
+ الانتشارات
+ طلبات موارد خارجية يلبيها قسمك. يبقى نظام الطلب هو المرجع: تُخزَّن المستندات كلقطات ثابتة ولا يُكتب شيء إليه.
+ انتشار جديد
+ الانتشارات ميزة معاينة: بُنيت النماذج من وثائق IROC وCIFFC وEMAC المنشورة وليس من تكاملات مباشرة. تحقق منها مقابل نظام الطلب لديك.
+ الطلب
+ رقم الطلب
+ الحادث
+ الدولة / المنطقة
+ المكاتب
+ مكتب الطلب
+ مكتب الإرسال
+ الجهات
+ الجهة الطالبة
+ الجهة المستلمة
+ الجهة المرسلة
+ دور القسم
+ يلبي الطلب
+ يطلب الموارد
+ يستضيف الحادث
+ التكلفة
+ رمز التكلفة
+ مرجع الاتفاقية
+ العملة / الوحدات / المنطقة الزمنية
+ مستند الطلب
+ ارفع مستند الطلب من النظام المصدر (PDF أو JSON أو CSV). يُخزَّن كلقطة ثابتة مع مجموع تحقق.
+ المصدر
+ النظام المصدر
+ إصدار المصدر
+ فتح في النظام المصدر
+ الحالة
+ إظهار الانتشارات المغلقة
+ إخفاء الانتشارات المغلقة
+ تمت التعبئة
+ تم الإفراج
+ تم الإغلاق
+ تسجيل لقطة جديدة
+ حفظ اللقطة
+ إغلاق الانتشار
+ ملاحظات الإغلاق
+ هل تريد إغلاق هذا الانتشار؟ لن يمكن تغيير التلبيات بعد ذلك.
+ يجب أن تكون كل تلبية مقبولة بحالة عادت قبل الإغلاق.
+ التلبيات
+ التلبية الأولى (اختياري)
+ أضف أول طلب/تلبية الآن أو من صفحة الانتشار.
+ إضافة تلبية
+ رقم الطلب الفرعي
+ الطلب الأصل
+ المورد
+ المنصب
+ متدرب
+ الشخص المعيّن
+ الوحدة الأصلية
+ الجهة المضيفة
+ مطلوب في
+ سجل الانتشار
+ إنشاء انتشار
+ سبب رفض هذا الطلب
+ تم إنشاء الانتشار.
+ تمت إضافة التلبية.
+ تم تحديث التلبية.
+ اختر مستند الطلب لحفظه كلقطة.
+ تم حفظ اللقطة. تُحفظ اللقطة السابقة وتُستبدل.
+ تم إغلاق الانتشار.
+ تخطيط الطباعة
+ رتّب الأقسام والحقول أو أعد تسميتها أو أخفها للطباعة وملفات PDF، وأضف فواصل الصفحات، وحدد موضع كتلة التوقيع وقائمة المرفقات. كل حفظ ينشئ إصدار تخطيط جديدًا يُختم في تذييل المصدر.
+ إصدار التخطيط
+ الافتراضي للقسم
+ الترتيب
+ عنوان بديل
+ مرئي
+ فاصل صفحة قبل
+ الحقول
+ ينطبق على الإصدار
+ جميع الإصدارات
+ كتلة التوقيع
+ في النهاية
+ ضمن قسمه
+ مخفي
+ قائمة المرفقات
+ جدول
+ قائمة بسيطة
+ مخفية
+ تجاوز هوية القسم لهذا التعريف
+ ترويسة
+ تم حفظ تخطيط الطباعة.
+ محدد
+ تحديد الكل في هذه الصفحة
+ المراجع…
+ السبب
+ تعيين للمراجعة
+ عنوان الحزمة
+ الغرض (مُدقّق)
+ إرسال الحزمة بالبريد إلى (اختياري)
+ ملف PDF مجمّع
+ حزمة zip
+ حتى {0} سجلًا لكل حزمة. لا إلغاء أو حذف جماعي.
+ حدد سجلًا واحدًا على الأقل.
+ تم تعيين {0} سجل(ات) للمراجعة؛ تم تخطي {1}.
+ تم تجميع الحزمة من {0} سجل(ات)؛ تم تخطي {1}. تبقى قابلة للتنزيل لمدة 30 يومًا.
+ تم الإرسال بالبريد.
+ إسقاط الوحدة
+ يُنشئ لقطة محدودة من الوحدة المالكة: المعرّفات والرموز والأعداد والحالات فقط مع مراجع المصدر. تبقى الوحدة هي نظام السجل.
+ الإسقاط
+ تسجيل حضور الأفراد
+ ملخص الموارد
+ المؤهلات
+ ملخص القيادة
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.de.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.de.resx
index b137f555..c64259a8 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.de.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.de.resx
@@ -1064,4 +1064,255 @@
Bestätigt amErweiterter Datenschutz ist erzwungen: Geschützte Übermittlungen schlagen fehl, bis diese Bestätigung erfasst ist.Datensatz-ID
+ Zeile hinzufügen
+ Zeile entfernen
+ Zurückgehalten
+ Koordinaten (Breite, Länge) oder Ortsreferenz
+ Ich unterzeichne diesen Datensatz als Autor
+ Ja
+ Nein
+ Bearbeiten
+ Aktiviert
+ Vorschau
+ Dieser Datensatz folgt einer Abteilungsdefinition. Mit einem Schloss markierte Felder sind eingeschränkt; Regeln können Felder während der Eingabe ein- oder ausblenden bzw. erfordern.
+ Definitionen
+ Datensatzdefinitionen beschreiben die Formulare Ihrer Abteilung: Abschnitte, Felder, Regeln, Nummerierung und wer prüft oder genehmigt. Veröffentlichen friert eine Version ein; Datensätze binden sich an die Version, mit der sie erstellt wurden.
+ Neue Definition
+ Definition erstellen
+ Aus Vorlage starten
+ Vorhandene Definition klonen
+ Leere Definition
+ Vorlagen durchsuchen
+ Vorlagenpakete sind geprüfte Ausgangspunkte. Jedes zeigt seine Quellen und das Prüfdatum; Vorschau-Pakete sind nutzbar, werden aber noch mit Abteilungen validiert.
+ Geprüft am
+ Quellen
+ Abschnitte
+ Felder
+ Diese Vorlage verwenden
+ Zuständigkeitsprofil
+ Sprachraum
+ Vorlagenvorschau
+ Kleinbuchstaben, Ziffern, Punkte und Bindestriche. Kann nach der Erstellung nicht geändert werden.
+ Name
+ Kategorie
+ Beschreibung
+ Eigentümer
+ Produkt (gesperrt)
+ Veröffentlichte Version
+ Entwurfsversion
+ Lebenszyklus-Voreinstellung
+ Client-Fähigkeit
+ Ausgemustert
+ Ausgemusterte Definitionen anzeigen
+ Ausgemusterte Definitionen ausblenden
+ Verlauf
+ Version
+ Status
+ Veröffentlicht am
+ Änderungshinweise
+ Einstellungen
+ Zulässige Betreffe
+ Prüferrollen
+ Genehmigerrollen
+ Prüffrist (Stunden)
+ Genehmigungsfrist (Stunden)
+ Autorbestätigung beim Abschließen verlangen
+ Nummerierung
+ Nummernpräfix
+ Sequenzbreite
+ Nummer vergeben
+ Sequenz pro Wache
+ Jährlich zurücksetzen
+ Klassifizierung
+ Aufbewahrung (Jahre)
+ Client-Oberflächen
+ Offline erlauben
+ Anhänge erlauben
+ Migrationszuordnung (JSON)
+ Schema (JSON)
+ Abschnitte enthalten Felder; jedes Feld hat Schlüssel, Bezeichnung, Typ und optionale Regeln. Vor dem Veröffentlichen validieren; die Feldtabelle unten zeigt den zuletzt gespeicherten Entwurf.
+ Felder
+ Wiederholend
+ Regeln
+ Schlüssel
+ Bezeichnung
+ Feldtyp
+ Erforderlich
+ Klassifizierung
+ Merkmale
+ Validieren
+ Entwurf speichern
+ Auswirkungen prüfen & veröffentlichen
+ Neuen Entwurf öffnen
+ Entwurf löschen
+ Diese Entwurfsversion löschen? Veröffentlichte Versionen bleiben unberührt.
+ Diese Version ist veröffentlicht und schreibgeschützt. Öffnen Sie einen neuen Entwurf für Änderungen.
+ Validierungsprobleme
+ Abweichungen von der Vorlage, aus der diese Definition erstellt wurde
+ Auswirkungen der Veröffentlichung
+ Veröffentlichen friert diese Version ein. Vorhandene Entwürfe behalten ihre Version bis zur Migration; abgeschlossene Datensätze ändern sich nie.
+ Offene Entwürfe der aktuellen Version
+ Abgeschlossene Datensätze auf früheren Versionen
+ Inkompatible Änderung
+ Client-Bereitschaft
+ Änderungen
+ Vergleichen von
+ bis
+ Vergleichen
+ Keine Unterschiede zwischen diesen Versionen.
+ Diese Version veröffentlichen? Sie wird schreibgeschützt und neue Datensätze verwenden sie.
+ Veröffentlichen
+ Veröffentlichen erfordert die Berechtigung zum Verwalten von Datensatzdefinitionen.
+ Ausmustern
+ Grund für das Ausmustern dieser Definition
+ Offene Entwürfe migrieren
+ Migration vorab anzeigen
+ Jetzt migrieren
+ Alle offenen Entwürfe auf die neuere Version verschieben? Nicht zugeordnete Werte gehen verloren.
+ Definition erstellt. Schema bearbeiten, validieren, dann veröffentlichen.
+ Entwurf gespeichert.
+ Die Definition ist gültig.
+ Die Definition enthält Fehler. Beheben Sie sie vor dem Veröffentlichen.
+ Entwurfsversion gelöscht.
+ Version {0} veröffentlicht.
+ Definition ausgemustert. Vorhandene Datensätze bleiben lesbar; neue können nicht erstellt werden.
+ Gespeicherte Berichte
+ Gespeicherte Berichte fragen die Datensätze einer Definition nach Feldern ab, gruppieren und aggregieren sie und exportieren nach CSV. Ergebnisse respektieren Sichtbarkeit und eingeschränkte Felder.
+ Neuer Bericht
+ Berichtsname
+ Berichtseinstellungen
+ Spalten
+ Datensatzspalten
+ Wählen Sie eine Definition, um Spalten auszuwählen.
+ Sortieren nach
+ Absteigend
+ Gruppieren nach
+ Aggregate (JSON)
+ Count, Sum, Average, Min, Max auf aggregierbaren Feldern
+ Filter (JSON)
+ Filterbare Felder
+ Versionszuordnungen (JSON)
+ Max. Zeilen
+ Entwürfe einbeziehen
+ Eingeschränkte Felder einbeziehen
+ Validieren
+ Ausführen
+ Letzte Ausführung
+ Diesen gespeicherten Bericht löschen?
+ Zeilen
+ Gruppen
+ Anzahl
+ Ergebnisse wurden am Zeilenlimit abgeschnitten; Filter einschränken oder als CSV exportieren.
+ Datensätze auf nicht zugeordneten Definitionsversionen wurden mit leeren Zellen einbezogen
+ Bericht gespeichert.
+ Bericht gelöscht.
+ Der Bericht ist gültig.
+ Der Bericht enthält Fehler.
+ Einsätze
+ Externe Ressourcenanforderungen, die Ihre Abteilung erfüllt. Das anfordernde System bleibt maßgeblich: Artefakte werden als unveränderliche Snapshots gespeichert, nichts wird zurückgeschrieben.
+ Neuer Einsatz
+ Einsätze sind eine Vorschaufunktion: Vorlagen wurden aus veröffentlichter IROC-, CIFFC- und EMAC-Dokumentation erstellt, nicht aus Live-Integrationen. Prüfen Sie gegen Ihr Anforderungssystem.
+ Anforderung
+ Anforderungsnummer
+ Ereignis
+ Land / Region
+ Stellen
+ Anfordernde Stelle
+ Leitstelle
+ Behörden
+ Anfordernde Behörde
+ Empfangende Behörde
+ Entsendende Behörde
+ Rolle der Abteilung
+ Erfüllt die Anforderung
+ Fordert Ressourcen an
+ Ausrichter des Ereignisses
+ Kosten
+ Kostenstelle
+ Vereinbarungsreferenz
+ Währung / Einheiten / Zeitzone
+ Anforderungsdokument
+ Laden Sie das Anforderungsdokument aus dem Quellsystem hoch (PDF, JSON oder CSV). Es wird als unveränderlicher Snapshot mit Prüfsumme gespeichert.
+ Quelle
+ Quellsystem
+ Quellversion
+ Im Quellsystem öffnen
+ Status
+ Abgeschlossene Einsätze anzeigen
+ Abgeschlossene Einsätze ausblenden
+ Mobilisiert
+ Freigegeben
+ Abgeschlossen
+ Neuen Snapshot erfassen
+ Snapshot speichern
+ Einsatz abschließen
+ Abschlussnotizen
+ Diesen Einsatz abschließen? Besetzungen können danach nicht mehr geändert werden.
+ Jede angenommene Besetzung muss vor dem Abschluss zurückgekehrt sein.
+ Besetzungen
+ Erste Besetzung (optional)
+ Fügen Sie die erste Anfrage/Besetzung jetzt hinzu oder später auf der Einsatzseite.
+ Besetzung hinzufügen
+ Anfragenummer
+ Übergeordnete Anfrage
+ Ressource
+ Position
+ Auszubildende/r
+ Zugewiesene Person
+ Heimateinheit
+ Gastgebende Behörde
+ Benötigt am
+ Einsatzdatensatz
+ Einsatz erstellen
+ Grund für die Ablehnung dieser Anfrage
+ Einsatz erstellt.
+ Besetzung hinzugefügt.
+ Besetzung aktualisiert.
+ Wählen Sie das Anforderungsdokument, das als Snapshot gespeichert werden soll.
+ Snapshot gespeichert. Der vorherige Snapshot bleibt erhalten und wird ersetzt.
+ Einsatz abgeschlossen.
+ Drucklayout
+ Abschnitte und Felder für Druck und PDF anordnen, umbenennen oder ausblenden, Seitenumbrüche hinzufügen und Unterschriftsblock sowie Anhangsliste platzieren. Jedes Speichern erzeugt eine neue Layoutversion, die in der Herkunftsfußzeile vermerkt wird.
+ Layoutversion
+ Abteilungsstandard
+ Reihenfolge
+ Überschrift überschreiben
+ Sichtbar
+ Seitenumbruch davor
+ Felder
+ Gilt für Version
+ Alle Versionen
+ Unterschriftsblock
+ Am Ende
+ Innerhalb des Abschnitts
+ Ausgeblendet
+ Anhangsliste
+ Tabelle
+ Einfache Liste
+ Ausgeblendet
+ Abteilungsbranding für diese Definition überschreiben
+ Briefkopf
+ Drucklayout gespeichert.
+ ausgewählt
+ Alle auf dieser Seite auswählen
+ Prüfer…
+ Grund
+ Zur Prüfung zuweisen
+ Pakettitel
+ Zweck (protokolliert)
+ Paket per E-Mail an (optional)
+ Zusammengefasstes PDF
+ Zip-Paket
+ Bis zu {0} Datensätze pro Paket. Kein Massen-Stornieren oder -Löschen.
+ Wählen Sie mindestens einen Datensatz aus.
+ {0} Datensatz/Datensätze zur Prüfung zugewiesen; {1} übersprungen.
+ Paket aus {0} Datensatz/Datensätzen erstellt; {1} übersprungen. 30 Tage lang herunterladbar.
+ Per E-Mail gesendet.
+ Modulprojektion
+ Erstellt einen begrenzten Schnappschuss aus dem zuständigen Modul: nur Kennungen, Codes, Zähler und Status mit Quellverweisen. Das Modul bleibt das führende System.
+ Projektion
+ Personal-Check-in
+ Ressourcenübersicht
+ Qualifikationen
+ Führungsübersicht
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.el.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.el.resx
index 5a4ba04c..ee0f9afc 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.el.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.el.resx
@@ -1064,4 +1064,255 @@
Επιβεβαιώθηκε στιςΗ Προηγμένη Προστασία Δεδομένων επιβάλλεται: οι προστατευμένες υποβολές θα αποτυγχάνουν μέχρι να καταγραφεί αυτή η επιβεβαίωση.Αναγνωριστικό εγγραφής
+ Προσθήκη γραμμής
+ Αφαίρεση γραμμής
+ Παρακρατήθηκε
+ Συντεταγμένες (γ. πλάτος, γ. μήκος) ή αναφορά τοποθεσίας
+ Υπογράφω αυτήν την εγγραφή ως συντάκτης
+ Ναι
+ Όχι
+ Επεξεργασία
+ Ενεργοποιημένο
+ Προεπισκόπηση
+ Αυτή η εγγραφή ακολουθεί έναν ορισμό του τμήματος. Τα πεδία με κλειδαριά είναι περιορισμένα· κανόνες μπορεί να εμφανίζουν ή να απαιτούν πεδία καθώς πληκτρολογείτε.
+ Ορισμοί
+ Οι ορισμοί εγγραφών περιγράφουν τις φόρμες του τμήματός σας: ενότητες, πεδία, κανόνες, αρίθμηση και ποιος ελέγχει ή εγκρίνει. Η δημοσίευση παγώνει μια έκδοση· οι εγγραφές δεσμεύονται στην έκδοση με την οποία γράφτηκαν.
+ Νέος ορισμός
+ Δημιουργία ορισμού
+ Έναρξη από πρότυπο
+ Κλωνοποίηση υπάρχοντος ορισμού
+ Κενός ορισμός
+ Περιήγηση προτύπων
+ Τα πακέτα προτύπων είναι ελεγμένα σημεία εκκίνησης. Καθένα δείχνει τις πηγές και την ημερομηνία ελέγχου του· τα πακέτα προεπισκόπησης είναι χρησιμοποιήσιμα αλλά ακόμη επικυρώνονται με τμήματα.
+ Ελέγχθηκε
+ Πηγές
+ Ενότητες
+ Πεδία
+ Χρήση αυτού του προτύπου
+ Προφίλ δικαιοδοσίας
+ Τοπικές ρυθμίσεις
+ Προεπισκόπηση προτύπου
+ Πεζά γράμματα, ψηφία, τελείες και παύλες. Δεν αλλάζει μετά τη δημιουργία.
+ Όνομα
+ Κατηγορία
+ Περιγραφή
+ Κάτοχος
+ Προϊόν (κλειδωμένο)
+ Δημοσιευμένη έκδοση
+ Πρόχειρη έκδοση
+ Προεπιλογή κύκλου ζωής
+ Δυνατότητα πελάτη
+ Αποσυρμένος
+ Εμφάνιση αποσυρμένων ορισμών
+ Απόκρυψη αποσυρμένων ορισμών
+ Ιστορικό
+ Έκδοση
+ Κατάσταση
+ Δημοσιεύθηκε
+ Σημειώσεις αλλαγών
+ Ρυθμίσεις
+ Επιτρεπόμενα θέματα
+ Ρόλοι ελεγκτή
+ Ρόλοι εγκρίνοντος
+ Προθεσμία ελέγχου (ώρες)
+ Προθεσμία έγκρισης (ώρες)
+ Απαίτηση βεβαίωσης συντάκτη κατά την οριστικοποίηση
+ Αρίθμηση
+ Πρόθεμα αριθμού
+ Πλάτος ακολουθίας
+ Ανάθεση αριθμού
+ Ακολουθία ανά σταθμό
+ Επαναφορά ετησίως
+ Ταξινόμηση
+ Διατήρηση (έτη)
+ Επιφάνειες πελάτη
+ Επιτρέπεται εκτός σύνδεσης
+ Επιτρέπονται συνημμένα
+ Χάρτης μετάβασης (JSON)
+ Σχήμα (JSON)
+ Οι ενότητες περιέχουν πεδία· κάθε πεδίο έχει κλειδί, ετικέτα, τύπο και προαιρετικούς κανόνες. Επικυρώστε πριν τη δημοσίευση· ο πίνακας πεδίων αντικατοπτρίζει το τελευταίο αποθηκευμένο προσχέδιο.
+ Πεδία
+ Επαναλαμβανόμενη
+ Κανόνες
+ Κλειδί
+ Ετικέτα
+ Τύπος πεδίου
+ Υποχρεωτικό
+ Ταξινόμηση
+ Σημάνσεις
+ Επικύρωση
+ Αποθήκευση προσχεδίου
+ Έλεγχος επίπτωσης & δημοσίευση
+ Άνοιγμα νέου προσχεδίου
+ Διαγραφή προσχεδίου
+ Διαγραφή αυτής της πρόχειρης έκδοσης; Οι δημοσιευμένες εκδόσεις δεν επηρεάζονται.
+ Αυτή η έκδοση είναι δημοσιευμένη και μόνο για ανάγνωση. Ανοίξτε νέο προσχέδιο για αλλαγές.
+ Ζητήματα επικύρωσης
+ Διαφορές από το πρότυπο από το οποίο δημιουργήθηκε αυτός ο ορισμός
+ Επίπτωση δημοσίευσης
+ Η δημοσίευση παγώνει αυτή την έκδοση. Τα υπάρχοντα προσχέδια κρατούν την έκδοσή τους μέχρι τη μετάβαση· οι οριστικοποιημένες εγγραφές δεν αλλάζουν ποτέ.
+ Ανοιχτά προσχέδια στην τρέχουσα έκδοση
+ Οριστικοποιημένες εγγραφές σε προηγούμενες εκδόσεις
+ Ασύμβατη αλλαγή
+ Ετοιμότητα πελατών
+ Αλλαγές
+ Σύγκριση από
+ έως
+ Σύγκριση
+ Δεν υπάρχουν διαφορές μεταξύ αυτών των εκδόσεων.
+ Δημοσίευση αυτής της έκδοσης; Γίνεται μόνο για ανάγνωση και οι νέες εγγραφές θα τη χρησιμοποιούν.
+ Δημοσίευση
+ Η δημοσίευση απαιτεί το δικαίωμα διαχείρισης ορισμών εγγραφών.
+ Απόσυρση
+ Λόγος απόσυρσης αυτού του ορισμού
+ Μετάβαση ανοιχτών προσχεδίων
+ Προεπισκόπηση μετάβασης
+ Μετάβαση τώρα
+ Μεταφορά όλων των ανοιχτών προσχεδίων στη νεότερη έκδοση; Οι μη αντιστοιχισμένες τιμές απορρίπτονται.
+ Ο ορισμός δημιουργήθηκε. Επεξεργαστείτε το σχήμα, επικυρώστε και έπειτα δημοσιεύστε.
+ Το προσχέδιο αποθηκεύτηκε.
+ Ο ορισμός είναι έγκυρος.
+ Ο ορισμός έχει σφάλματα. Διορθώστε τα πριν τη δημοσίευση.
+ Η πρόχειρη έκδοση διαγράφηκε.
+ Η έκδοση {0} δημοσιεύθηκε.
+ Ο ορισμός αποσύρθηκε. Οι υπάρχουσες εγγραφές παραμένουν αναγνώσιμες· δεν μπορούν να δημιουργηθούν νέες.
+ Αποθηκευμένες αναφορές
+ Οι αποθηκευμένες αναφορές ερωτούν τις εγγραφές ενός ορισμού ανά πεδίο, τις ομαδοποιούν και συγκεντρώνουν και εξάγουν σε CSV. Τα αποτελέσματα σέβονται την ορατότητα και τα περιορισμένα πεδία.
+ Νέα αναφορά
+ Όνομα αναφοράς
+ Ρυθμίσεις αναφοράς
+ Στήλες
+ Στήλες εγγραφής
+ Επιλέξτε ορισμό για να διαλέξετε στήλες.
+ Ταξινόμηση κατά
+ Φθίνουσα
+ Ομαδοποίηση κατά
+ Συγκεντρωτικά (JSON)
+ Count, Sum, Average, Min, Max σε συγκεντρώσιμα πεδία
+ Φίλτρα (JSON)
+ Φιλτραρίσιμα πεδία
+ Αντιστοιχίσεις εκδόσεων (JSON)
+ Μέγ. γραμμές
+ Συμπερίληψη προσχεδίων
+ Συμπερίληψη περιορισμένων πεδίων
+ Επικύρωση
+ Εκτέλεση
+ Τελευταία εκτέλεση
+ Διαγραφή αυτής της αποθηκευμένης αναφοράς;
+ Γραμμές
+ Ομάδες
+ Πλήθος
+ Τα αποτελέσματα περικόπηκαν στο όριο γραμμών· περιορίστε τα φίλτρα ή εξάγετε σε CSV.
+ Εγγραφές σε μη αντιστοιχισμένες εκδόσεις ορισμού συμπεριλήφθηκαν με κενά κελιά
+ Η αναφορά αποθηκεύτηκε.
+ Η αναφορά διαγράφηκε.
+ Η αναφορά είναι έγκυρη.
+ Η αναφορά έχει σφάλματα.
+ Αποστολές
+ Εξωτερικές παραγγελίες πόρων που καλύπτει το τμήμα σας. Το σύστημα παραγγελίας παραμένει έγκυρο: τα τεχνουργήματα αποθηκεύονται ως αμετάβλητα στιγμιότυπα και τίποτα δεν γράφεται πίσω.
+ Νέα αποστολή
+ Οι αποστολές είναι λειτουργία προεπισκόπησης: τα πρότυπα βασίστηκαν σε δημοσιευμένη τεκμηρίωση IROC, CIFFC και EMAC, όχι σε ζωντανές ενοποιήσεις. Επαληθεύστε με το σύστημα παραγγελίας σας.
+ Παραγγελία
+ Αριθμός παραγγελίας
+ Συμβάν
+ Χώρα / υποδιαίρεση
+ Γραφεία
+ Γραφείο παραγγελίας
+ Γραφείο αποστολής
+ Υπηρεσίες
+ Αιτούσα υπηρεσία
+ Παραλαμβάνουσα υπηρεσία
+ Αποστέλλουσα υπηρεσία
+ Ρόλος τμήματος
+ Καλύπτει την παραγγελία
+ Ζητά πόρους
+ Φιλοξενεί το συμβάν
+ Κόστος
+ Κωδικός κόστους
+ Αναφορά συμφωνίας
+ Νόμισμα / μονάδες / ζώνη ώρας
+ Έγγραφο παραγγελίας
+ Ανεβάστε το έγγραφο παραγγελίας από το σύστημα προέλευσης (PDF, JSON ή CSV). Αποθηκεύεται ως αμετάβλητο στιγμιότυπο με άθροισμα ελέγχου.
+ Προέλευση
+ Σύστημα προέλευσης
+ Έκδοση προέλευσης
+ Άνοιγμα στο σύστημα προέλευσης
+ Κατάσταση
+ Εμφάνιση κλειστών αποστολών
+ Απόκρυψη κλειστών αποστολών
+ Κινητοποιήθηκε
+ Απελευθερώθηκε
+ Έκλεισε
+ Καταγραφή νέου στιγμιοτύπου
+ Αποθήκευση στιγμιοτύπου
+ Κλείσιμο αποστολής
+ Σημειώσεις κλεισίματος
+ Κλείσιμο αυτής της αποστολής; Οι καλύψεις δεν θα μπορούν πλέον να αλλάξουν.
+ Κάθε αποδεκτή κάλυψη πρέπει να έχει Επιστρέψει πριν το κλείσιμο.
+ Καλύψεις
+ Πρώτη κάλυψη (προαιρετικό)
+ Προσθέστε το πρώτο αίτημα/κάλυψη τώρα ή από τη σελίδα αποστολής.
+ Προσθήκη κάλυψης
+ Αριθμός αιτήματος
+ Γονικό αίτημα
+ Πόρος
+ Θέση
+ Εκπαιδευόμενος
+ Ανατεθειμένο άτομο
+ Μονάδα προέλευσης
+ Υπηρεσία υποδοχής
+ Απαιτείται στις
+ Εγγραφή αποστολής
+ Δημιουργία αποστολής
+ Λόγος απόρριψης αυτού του αιτήματος
+ Η αποστολή δημιουργήθηκε.
+ Η κάλυψη προστέθηκε.
+ Η κάλυψη ενημερώθηκε.
+ Επιλέξτε το έγγραφο παραγγελίας για αποθήκευση ως στιγμιότυπο.
+ Το στιγμιότυπο αποθηκεύτηκε. Το προηγούμενο διατηρείται και αντικαθίσταται.
+ Η αποστολή έκλεισε.
+ Διάταξη εκτύπωσης
+ Ταξινομήστε, μετονομάστε ή αποκρύψτε ενότητες και πεδία για εκτύπωση και PDF, προσθέστε αλλαγές σελίδας και τοποθετήστε το μπλοκ υπογραφής και τη λίστα συνημμένων. Κάθε αποθήκευση δημιουργεί νέα έκδοση διάταξης που σφραγίζεται στο υποσέλιδο προέλευσης.
+ Έκδοση διάταξης
+ Προεπιλογή τμήματος
+ Σειρά
+ Εναλλακτική επικεφαλίδα
+ Ορατό
+ Αλλαγή σελίδας πριν
+ Πεδία
+ Ισχύει για την έκδοση
+ Όλες οι εκδόσεις
+ Μπλοκ υπογραφής
+ Στο τέλος
+ Μέσα στην ενότητά του
+ Κρυφό
+ Λίστα συνημμένων
+ Πίνακας
+ Απλή λίστα
+ Κρυφή
+ Παράκαμψη της επωνυμίας του τμήματος για αυτόν τον ορισμό
+ Επιστολόχαρτο
+ Η διάταξη εκτύπωσης αποθηκεύτηκε.
+ επιλεγμένα
+ Επιλογή όλων σε αυτήν τη σελίδα
+ Ελεγκτής…
+ Αιτία
+ Ανάθεση για έλεγχο
+ Τίτλος πακέτου
+ Σκοπός (ελέγχεται)
+ Αποστολή πακέτου με e-mail σε (προαιρετικό)
+ Συγκεντρωτικό PDF
+ Πακέτο zip
+ Έως {0} εγγραφές ανά πακέτο. Χωρίς μαζική ακύρωση ή διαγραφή.
+ Επιλέξτε τουλάχιστον μία εγγραφή.
+ Ανατέθηκαν {0} εγγραφές για έλεγχο· {1} παραλείφθηκαν.
+ Το πακέτο συντάχθηκε από {0} εγγραφές· {1} παραλείφθηκαν. Διαθέσιμο για λήψη για 30 ημέρες.
+ Στάλθηκε με e-mail.
+ Προβολή ενότητας
+ Συνθέτει ένα οριοθετημένο στιγμιότυπο από την ενότητα-κάτοχο: μόνο αναγνωριστικά, κωδικούς, πλήθη και καταστάσεις με αναφορές πηγής. Η ενότητα παραμένει το σύστημα αναφοράς.
+ Προβολή
+ Καταγραφή προσωπικού
+ Σύνοψη πόρων
+ Προσόντα
+ Σύνοψη διοίκησης
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.en.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.en.resx
index 591816a3..7958c84e 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.en.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.en.resx
@@ -1064,4 +1064,255 @@
Acknowledged onAdvanced Data Protection is enforced: protected submissions will fail until this acknowledgement is recorded.Record ID
+ Add row
+ Remove row
+ Withheld
+ Coordinates (lat, lon) or place reference
+ I sign this record as the author
+ Yes
+ No
+ Edit
+ Enabled
+ Preview
+ This record follows a department definition. Fields marked with a lock are restricted; rules may show or require fields as you type.
+ Definitions
+ Record definitions describe the forms your department fills out: sections, fields, rules, numbering and who reviews or approves. Publishing freezes a version; records pin the version they were written on.
+ New definition
+ Create definition
+ Start from template
+ Clone existing definition
+ Blank definition
+ Browse templates
+ Template packs are reviewed starting points. Each shows its sources and review date; Preview packs are usable but still being validated with departments.
+ Reviewed
+ Sources
+ Sections
+ Fields
+ Use this template
+ Jurisdiction profile
+ Locale
+ Template preview
+ Lower-case letters, digits, dots and dashes. Cannot change after creation.
+ Name
+ Category
+ Description
+ Owner
+ Product (locked)
+ Published version
+ Draft version
+ Lifecycle preset
+ Client capability
+ Retired
+ Show retired definitions
+ Hide retired definitions
+ History
+ Version
+ State
+ Published on
+ Change notes
+ Settings
+ Permitted subjects
+ Reviewer roles
+ Approver roles
+ Review due (hours)
+ Approval due (hours)
+ Require author attestation at finalize
+ Numbering
+ Number prefix
+ Sequence width
+ Assign number
+ Sequence per station
+ Reset yearly
+ Classification
+ Retention (years)
+ Client surfaces
+ Allow offline
+ Allow attachments
+ Migration map (JSON)
+ Schema (JSON)
+ Sections hold fields; each field has a key, label, type and optional rules. Validate before publishing; the field table below reflects the last saved draft.
+ Fields
+ Repeating
+ Rules
+ Key
+ Label
+ Field type
+ Required
+ Classification
+ Flags
+ Validate
+ Save draft
+ Review impact & publish
+ Open new draft
+ Delete draft
+ Delete this draft version? Published versions are not affected.
+ This version is published and read-only. Open a new draft to make changes.
+ Validation issues
+ Differences from the template this definition was created from
+ Publish impact
+ Publishing freezes this version. Existing drafts keep their version until migrated; finalized records never change.
+ Open drafts on current version
+ Finalized records on earlier versions
+ Breaking change
+ Client readiness
+ Changes
+ Compare from
+ to
+ Compare
+ No differences between these versions.
+ Publish this version? It becomes read-only and new records will use it.
+ Publish
+ Publishing requires the manage record definitions permission.
+ Retire
+ Reason for retiring this definition
+ Migrate open drafts
+ Preview migration
+ Migrate now
+ Move all open drafts to the newer version? Unmapped values are dropped.
+ Definition created. Edit the schema, validate, then publish.
+ Draft saved.
+ The definition is valid.
+ The definition has errors. Fix them before publishing.
+ Draft version deleted.
+ Version {0} published.
+ Definition retired. Existing records stay readable; no new records can be created.
+ Saved reports
+ Saved reports query one definition's records by field, group and aggregate them, and export to CSV. Results respect record visibility and restricted fields.
+ New report
+ Report name
+ Report settings
+ Columns
+ Record columns
+ Pick a definition to choose columns.
+ Sort by
+ Descending
+ Group by
+ Aggregates (JSON)
+ Count, Sum, Average, Min, Max on aggregatable fields
+ Filters (JSON)
+ Filterable fields
+ Version mappings (JSON)
+ Max rows
+ Include drafts
+ Include restricted fields
+ Validate
+ Run
+ Last run
+ Delete this saved report?
+ Rows
+ Groups
+ Count
+ Results were truncated at the row limit; narrow the filters or export to CSV.
+ Records on unmapped definition versions were included with blank cells
+ Report saved.
+ Report deleted.
+ The report is valid.
+ The report has errors.
+ Deployments
+ External resource orders your department is filling. The ordering system stays authoritative: artifacts are stored as immutable snapshots and nothing is written back.
+ New deployment
+ Deployments are a Preview feature: fixtures were built from published IROC, CIFFC and EMAC documentation, not from live integrations. Verify against your ordering system.
+ Order
+ Order number
+ Incident
+ Country / subdivision
+ Offices
+ Ordering office
+ Dispatch office
+ Agencies
+ Requesting agency
+ Receiving agency
+ Sending agency
+ Department role
+ Filling the order
+ Requesting resources
+ Hosting incident
+ Cost
+ Cost code
+ Agreement reference
+ Currency / units / time zone
+ Order artifact
+ Upload the order document from the source system (PDF, JSON or CSV). It is stored as an immutable, checksummed snapshot.
+ Source
+ Source system
+ Source version
+ Open in source system
+ Status
+ Show closed-out deployments
+ Hide closed-out deployments
+ Mobilized
+ Released
+ Closed out
+ Record a new snapshot
+ Store snapshot
+ Close out deployment
+ Closeout notes
+ Close out this deployment? Fills can no longer change.
+ Every accepted fill must be Returned before closeout.
+ Fills
+ First fill (optional)
+ Add the first request/fill now or add fills from the deployment page.
+ Add fill
+ Request number
+ Parent request
+ Resource
+ Position
+ Trainee
+ Assigned person
+ Home unit
+ Host agency
+ Needed on
+ Deployment record
+ Create deployment
+ Reason for declining this request
+ Deployment created.
+ Fill added.
+ Fill updated.
+ Choose the order document to store as a snapshot.
+ Snapshot stored. The previous snapshot is kept and superseded.
+ Deployment closed out.
+ Print layout
+ Order, rename or hide sections and fields for print and PDF, add page breaks, and place the signature block and attachment list. Every save is a new layout version stamped in the provenance footer.
+ Layout version
+ Department default
+ Order
+ Heading override
+ Visible
+ Page break before
+ Fields
+ Applies to version
+ All versions
+ Signature block
+ At the end
+ Inline with its section
+ Hidden
+ Attachment list
+ Table
+ Simple list
+ Hidden
+ Override the department branding for this definition
+ Letterhead
+ Print layout saved.
+ selected
+ Select all on this page
+ Reviewer…
+ Reason
+ Assign for review
+ Packet title
+ Purpose (audited)
+ Email packet to (optional)
+ Compiled PDF
+ Zip bundle
+ Up to {0} records per packet. No bulk void or delete.
+ Select at least one record.
+ Assigned {0} record(s) for review; {1} skipped.
+ Packet compiled from {0} record(s); {1} skipped. It stays downloadable for 30 days.
+ Emailed.
+ Module projection
+ Compose a bounded snapshot from the owning module: identifiers, codes, counts and statuses only, with source references. The module stays the system of record.
+ Projection
+ Personnel check-in
+ Resource summary
+ Qualifications
+ Command summary
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.es.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.es.resx
index 49aee750..c8f61428 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.es.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.es.resx
@@ -1064,4 +1064,255 @@
Reconocido elLa Protección Avanzada de Datos está aplicada: los envíos protegidos fallarán hasta que se registre este reconocimiento.ID de registro
+ Añadir fila
+ Quitar fila
+ Retenido
+ Coordenadas (lat, lon) o referencia de lugar
+ Firmo este registro como autor
+ Sí
+ No
+ Editar
+ Habilitado
+ Vista previa
+ Este registro sigue una definición del departamento. Los campos con candado están restringidos; las reglas pueden mostrar o exigir campos mientras escribe.
+ Definiciones
+ Las definiciones de registro describen los formularios de su departamento: secciones, campos, reglas, numeración y quién revisa o aprueba. Publicar congela una versión; los registros fijan la versión con la que se escribieron.
+ Nueva definición
+ Crear definición
+ Empezar desde plantilla
+ Clonar definición existente
+ Definición en blanco
+ Explorar plantillas
+ Los paquetes de plantillas son puntos de partida revisados. Cada uno muestra sus fuentes y fecha de revisión; los paquetes en vista previa son utilizables pero aún se validan con departamentos.
+ Revisado el
+ Fuentes
+ Secciones
+ Campos
+ Usar esta plantilla
+ Perfil de jurisdicción
+ Configuración regional
+ Vista previa de la plantilla
+ Letras minúsculas, dígitos, puntos y guiones. No se puede cambiar tras la creación.
+ Nombre
+ Categoría
+ Descripción
+ Propietario
+ Producto (bloqueado)
+ Versión publicada
+ Versión borrador
+ Preajuste de ciclo de vida
+ Capacidad del cliente
+ Retirada
+ Mostrar definiciones retiradas
+ Ocultar definiciones retiradas
+ Historial
+ Versión
+ Estado
+ Publicado el
+ Notas de cambio
+ Configuración
+ Sujetos permitidos
+ Roles de revisor
+ Roles de aprobador
+ Plazo de revisión (horas)
+ Plazo de aprobación (horas)
+ Exigir atestación del autor al finalizar
+ Numeración
+ Prefijo de número
+ Ancho de secuencia
+ Asignar número
+ Secuencia por estación
+ Reiniciar anualmente
+ Clasificación
+ Retención (años)
+ Superficies de cliente
+ Permitir sin conexión
+ Permitir adjuntos
+ Mapa de migración (JSON)
+ Esquema (JSON)
+ Las secciones contienen campos; cada campo tiene clave, etiqueta, tipo y reglas opcionales. Valide antes de publicar; la tabla de campos refleja el último borrador guardado.
+ Campos
+ Repetible
+ Reglas
+ Clave
+ Etiqueta
+ Tipo de campo
+ Obligatorio
+ Clasificación
+ Indicadores
+ Validar
+ Guardar borrador
+ Revisar impacto y publicar
+ Abrir nuevo borrador
+ Eliminar borrador
+ ¿Eliminar esta versión borrador? Las versiones publicadas no se ven afectadas.
+ Esta versión está publicada y es de solo lectura. Abra un nuevo borrador para hacer cambios.
+ Problemas de validación
+ Diferencias respecto a la plantilla de origen de esta definición
+ Impacto de la publicación
+ Publicar congela esta versión. Los borradores existentes conservan su versión hasta migrarse; los registros finalizados nunca cambian.
+ Borradores abiertos en la versión actual
+ Registros finalizados en versiones anteriores
+ Cambio incompatible
+ Preparación de clientes
+ Cambios
+ Comparar desde
+ hasta
+ Comparar
+ No hay diferencias entre estas versiones.
+ ¿Publicar esta versión? Pasará a solo lectura y los nuevos registros la usarán.
+ Publicar
+ Publicar requiere el permiso de gestionar definiciones de registro.
+ Retirar
+ Motivo para retirar esta definición
+ Migrar borradores abiertos
+ Previsualizar migración
+ Migrar ahora
+ ¿Mover todos los borradores abiertos a la versión más reciente? Los valores sin mapear se descartan.
+ Definición creada. Edite el esquema, valide y luego publique.
+ Borrador guardado.
+ La definición es válida.
+ La definición tiene errores. Corríjalos antes de publicar.
+ Versión borrador eliminada.
+ Versión {0} publicada.
+ Definición retirada. Los registros existentes siguen legibles; no se pueden crear nuevos.
+ Informes guardados
+ Los informes guardados consultan los registros de una definición por campo, los agrupan y agregan, y exportan a CSV. Los resultados respetan la visibilidad y los campos restringidos.
+ Nuevo informe
+ Nombre del informe
+ Configuración del informe
+ Columnas
+ Columnas del registro
+ Elija una definición para seleccionar columnas.
+ Ordenar por
+ Descendente
+ Agrupar por
+ Agregados (JSON)
+ Count, Sum, Average, Min, Max sobre campos agregables
+ Filtros (JSON)
+ Campos filtrables
+ Mapeos de versión (JSON)
+ Filas máx.
+ Incluir borradores
+ Incluir campos restringidos
+ Validar
+ Ejecutar
+ Última ejecución
+ ¿Eliminar este informe guardado?
+ Filas
+ Grupos
+ Cantidad
+ Los resultados se truncaron en el límite de filas; acote los filtros o exporte a CSV.
+ Se incluyeron registros de versiones de definición sin mapear con celdas vacías
+ Informe guardado.
+ Informe eliminado.
+ El informe es válido.
+ El informe tiene errores.
+ Despliegues
+ Órdenes de recursos externas que su departamento está cubriendo. El sistema emisor sigue siendo la autoridad: los artefactos se guardan como instantáneas inmutables y nada se escribe de vuelta.
+ Nuevo despliegue
+ Los despliegues son una función en vista previa: las plantillas se crearon a partir de documentación publicada de IROC, CIFFC y EMAC, no de integraciones en vivo. Verifique contra su sistema emisor.
+ Orden
+ Número de orden
+ Incidente
+ País / subdivisión
+ Oficinas
+ Oficina emisora
+ Oficina de despacho
+ Agencias
+ Agencia solicitante
+ Agencia receptora
+ Agencia emisora
+ Rol del departamento
+ Cubre la orden
+ Solicita recursos
+ Anfitrión del incidente
+ Costo
+ Código de costo
+ Referencia del acuerdo
+ Moneda / unidades / zona horaria
+ Documento de la orden
+ Suba el documento de la orden desde el sistema de origen (PDF, JSON o CSV). Se guarda como instantánea inmutable con suma de verificación.
+ Origen
+ Sistema de origen
+ Versión de origen
+ Abrir en el sistema de origen
+ Estado
+ Mostrar despliegues cerrados
+ Ocultar despliegues cerrados
+ Movilizado
+ Liberado
+ Cerrado
+ Registrar nueva instantánea
+ Guardar instantánea
+ Cerrar despliegue
+ Notas de cierre
+ ¿Cerrar este despliegue? Las asignaciones ya no podrán cambiar.
+ Cada asignación aceptada debe estar Devuelta antes del cierre.
+ Asignaciones
+ Primera asignación (opcional)
+ Añada la primera solicitud/asignación ahora o desde la página del despliegue.
+ Añadir asignación
+ Número de solicitud
+ Solicitud principal
+ Recurso
+ Posición
+ Aprendiz
+ Persona asignada
+ Unidad de origen
+ Agencia anfitriona
+ Necesario el
+ Registro del despliegue
+ Crear despliegue
+ Motivo para rechazar esta solicitud
+ Despliegue creado.
+ Asignación añadida.
+ Asignación actualizada.
+ Elija el documento de la orden para guardarlo como instantánea.
+ Instantánea guardada. La anterior se conserva y queda reemplazada.
+ Despliegue cerrado.
+ Diseño de impresión
+ Ordene, renombre u oculte secciones y campos para impresión y PDF, añada saltos de página y ubique el bloque de firma y la lista de adjuntos. Cada guardado crea una nueva versión de diseño registrada en el pie de procedencia.
+ Versión del diseño
+ Predeterminado del departamento
+ Orden
+ Encabezado alternativo
+ Visible
+ Salto de página antes
+ Campos
+ Se aplica a la versión
+ Todas las versiones
+ Bloque de firma
+ Al final
+ En su sección
+ Oculto
+ Lista de adjuntos
+ Tabla
+ Lista simple
+ Oculta
+ Anular la identidad del departamento para esta definición
+ Membrete
+ Diseño de impresión guardado.
+ seleccionados
+ Seleccionar todos en esta página
+ Revisor…
+ Motivo
+ Asignar para revisión
+ Título del paquete
+ Propósito (auditado)
+ Enviar paquete por correo a (opcional)
+ PDF compilado
+ Paquete zip
+ Hasta {0} registros por paquete. Sin anulación ni eliminación masiva.
+ Seleccione al menos un registro.
+ {0} registro(s) asignado(s) para revisión; {1} omitido(s).
+ Paquete compilado con {0} registro(s); {1} omitido(s). Descargable durante 30 días.
+ Enviado por correo.
+ Proyección de módulo
+ Compone una instantánea acotada del módulo propietario: solo identificadores, códigos, recuentos y estados, con referencias de origen. El módulo sigue siendo el sistema de registro.
+ Proyección
+ Registro de personal
+ Resumen de recursos
+ Cualificaciones
+ Resumen de mando
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx
index 9a21b2da..cbcc50cc 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx
@@ -1064,4 +1064,255 @@
Confirmé leLa protection avancée des données est appliquée : les envois protégés échoueront tant que cette confirmation n'est pas enregistrée.Identifiant d'enregistrement
+ Ajouter une ligne
+ Supprimer la ligne
+ Retenu
+ Coordonnées (lat, lon) ou référence de lieu
+ Je signe cet enregistrement en tant qu'auteur
+ Oui
+ Non
+ Modifier
+ Activé
+ Aperçu
+ Cet enregistrement suit une définition du service. Les champs marqués d'un cadenas sont restreints ; des règles peuvent afficher ou exiger des champs pendant la saisie.
+ Définitions
+ Les définitions d'enregistrement décrivent les formulaires de votre service : sections, champs, règles, numérotation et qui révise ou approuve. La publication fige une version ; les enregistrements conservent la version avec laquelle ils ont été rédigés.
+ Nouvelle définition
+ Créer une définition
+ Partir d'un modèle
+ Cloner une définition existante
+ Définition vierge
+ Parcourir les modèles
+ Les packs de modèles sont des points de départ révisés. Chacun indique ses sources et sa date de révision ; les packs en aperçu sont utilisables mais encore en validation avec des services.
+ Révisé le
+ Sources
+ Sections
+ Champs
+ Utiliser ce modèle
+ Profil de juridiction
+ Paramètres régionaux
+ Aperçu du modèle
+ Lettres minuscules, chiffres, points et tirets. Non modifiable après la création.
+ Nom
+ Catégorie
+ Description
+ Propriétaire
+ Produit (verrouillé)
+ Version publiée
+ Version brouillon
+ Préréglage de cycle de vie
+ Capacité du client
+ Retirée
+ Afficher les définitions retirées
+ Masquer les définitions retirées
+ Historique
+ Version
+ État
+ Publié le
+ Notes de modification
+ Paramètres
+ Sujets autorisés
+ Rôles de réviseur
+ Rôles d'approbateur
+ Délai de révision (heures)
+ Délai d'approbation (heures)
+ Exiger l'attestation de l'auteur à la finalisation
+ Numérotation
+ Préfixe de numéro
+ Largeur de séquence
+ Attribuer le numéro
+ Séquence par caserne
+ Réinitialiser chaque année
+ Classification
+ Conservation (années)
+ Surfaces client
+ Autoriser hors ligne
+ Autoriser les pièces jointes
+ Table de migration (JSON)
+ Schéma (JSON)
+ Les sections contiennent des champs ; chaque champ a une clé, un libellé, un type et des règles facultatives. Validez avant de publier ; le tableau des champs reflète le dernier brouillon enregistré.
+ Champs
+ Répétable
+ Règles
+ Clé
+ Libellé
+ Type de champ
+ Obligatoire
+ Classification
+ Indicateurs
+ Valider
+ Enregistrer le brouillon
+ Vérifier l'impact et publier
+ Ouvrir un nouveau brouillon
+ Supprimer le brouillon
+ Supprimer cette version brouillon ? Les versions publiées ne sont pas affectées.
+ Cette version est publiée et en lecture seule. Ouvrez un nouveau brouillon pour la modifier.
+ Problèmes de validation
+ Différences par rapport au modèle d'origine de cette définition
+ Impact de la publication
+ La publication fige cette version. Les brouillons existants conservent leur version jusqu'à migration ; les enregistrements finalisés ne changent jamais.
+ Brouillons ouverts sur la version actuelle
+ Enregistrements finalisés sur des versions antérieures
+ Changement incompatible
+ Préparation des clients
+ Modifications
+ Comparer depuis
+ à
+ Comparer
+ Aucune différence entre ces versions.
+ Publier cette version ? Elle devient en lecture seule et les nouveaux enregistrements l'utiliseront.
+ Publier
+ La publication nécessite l'autorisation de gérer les définitions d'enregistrement.
+ Retirer
+ Motif du retrait de cette définition
+ Migrer les brouillons ouverts
+ Aperçu de la migration
+ Migrer maintenant
+ Déplacer tous les brouillons ouverts vers la version plus récente ? Les valeurs non mappées sont perdues.
+ Définition créée. Modifiez le schéma, validez, puis publiez.
+ Brouillon enregistré.
+ La définition est valide.
+ La définition contient des erreurs. Corrigez-les avant de publier.
+ Version brouillon supprimée.
+ Version {0} publiée.
+ Définition retirée. Les enregistrements existants restent lisibles ; aucun nouveau ne peut être créé.
+ Rapports enregistrés
+ Les rapports enregistrés interrogent les enregistrements d'une définition par champ, les regroupent et les agrègent, puis exportent en CSV. Les résultats respectent la visibilité et les champs restreints.
+ Nouveau rapport
+ Nom du rapport
+ Paramètres du rapport
+ Colonnes
+ Colonnes de l'enregistrement
+ Choisissez une définition pour sélectionner les colonnes.
+ Trier par
+ Décroissant
+ Grouper par
+ Agrégats (JSON)
+ Count, Sum, Average, Min, Max sur les champs agrégeables
+ Filtres (JSON)
+ Champs filtrables
+ Correspondances de version (JSON)
+ Lignes max.
+ Inclure les brouillons
+ Inclure les champs restreints
+ Valider
+ Exécuter
+ Dernière exécution
+ Supprimer ce rapport enregistré ?
+ Lignes
+ Groupes
+ Nombre
+ Les résultats ont été tronqués à la limite de lignes ; affinez les filtres ou exportez en CSV.
+ Les enregistrements sur des versions de définition non mappées ont été inclus avec des cellules vides
+ Rapport enregistré.
+ Rapport supprimé.
+ Le rapport est valide.
+ Le rapport contient des erreurs.
+ Déploiements
+ Commandes de ressources externes que votre service pourvoit. Le système émetteur reste la référence : les artefacts sont stockés en instantanés immuables et rien n'est réécrit.
+ Nouveau déploiement
+ Les déploiements sont une fonctionnalité en aperçu : les gabarits proviennent de la documentation publiée IROC, CIFFC et EMAC, pas d'intégrations en direct. Vérifiez avec votre système de commande.
+ Commande
+ Numéro de commande
+ Incident
+ Pays / subdivision
+ Bureaux
+ Bureau émetteur
+ Bureau de répartition
+ Organismes
+ Organisme demandeur
+ Organisme destinataire
+ Organisme expéditeur
+ Rôle du service
+ Pourvoit la commande
+ Demande des ressources
+ Hôte de l'incident
+ Coût
+ Code de coût
+ Référence de l'accord
+ Devise / unités / fuseau horaire
+ Document de commande
+ Téléversez le document de commande du système source (PDF, JSON ou CSV). Il est stocké en instantané immuable avec somme de contrôle.
+ Source
+ Système source
+ Version source
+ Ouvrir dans le système source
+ Statut
+ Afficher les déploiements clôturés
+ Masquer les déploiements clôturés
+ Mobilisé
+ Libéré
+ Clôturé
+ Enregistrer un nouvel instantané
+ Stocker l'instantané
+ Clôturer le déploiement
+ Notes de clôture
+ Clôturer ce déploiement ? Les affectations ne pourront plus changer.
+ Chaque affectation acceptée doit être Retournée avant la clôture.
+ Affectations
+ Première affectation (facultatif)
+ Ajoutez la première demande/affectation maintenant ou depuis la page du déploiement.
+ Ajouter une affectation
+ Numéro de demande
+ Demande parente
+ Ressource
+ Poste
+ Stagiaire
+ Personne affectée
+ Unité d'origine
+ Organisme hôte
+ Requis le
+ Enregistrement du déploiement
+ Créer le déploiement
+ Motif du refus de cette demande
+ Déploiement créé.
+ Affectation ajoutée.
+ Affectation mise à jour.
+ Choisissez le document de commande à stocker en instantané.
+ Instantané stocké. Le précédent est conservé et remplacé.
+ Déploiement clôturé.
+ Mise en page d'impression
+ Ordonnez, renommez ou masquez des sections et des champs pour l'impression et le PDF, ajoutez des sauts de page et placez le bloc de signature et la liste des pièces jointes. Chaque enregistrement crée une nouvelle version de mise en page inscrite dans le pied de provenance.
+ Version de mise en page
+ Valeur par défaut du service
+ Ordre
+ Titre de remplacement
+ Visible
+ Saut de page avant
+ Champs
+ S'applique à la version
+ Toutes les versions
+ Bloc de signature
+ À la fin
+ Dans sa section
+ Masqué
+ Liste des pièces jointes
+ Tableau
+ Liste simple
+ Masquée
+ Remplacer l'identité visuelle du service pour cette définition
+ En-tête
+ Mise en page d'impression enregistrée.
+ sélectionnés
+ Tout sélectionner sur cette page
+ Réviseur…
+ Motif
+ Attribuer pour révision
+ Titre du dossier
+ Objet (audité)
+ Envoyer le dossier par e-mail à (facultatif)
+ PDF compilé
+ Archive zip
+ Jusqu'à {0} enregistrements par dossier. Pas d'annulation ni de suppression en masse.
+ Sélectionnez au moins un enregistrement.
+ {0} enregistrement(s) attribué(s) pour révision ; {1} ignoré(s).
+ Dossier compilé à partir de {0} enregistrement(s) ; {1} ignoré(s). Téléchargeable pendant 30 jours.
+ Envoyé par e-mail.
+ Projection de module
+ Compose un instantané borné à partir du module propriétaire : identifiants, codes, comptes et statuts uniquement, avec références sources. Le module reste le système de référence.
+ Projection
+ Pointage du personnel
+ Synthèse des ressources
+ Qualifications
+ Synthèse du commandement
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.it.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.it.resx
index b91c5517..0f30540d 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.it.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.it.resx
@@ -1064,4 +1064,255 @@
Confermato ilLa Protezione avanzata dei dati è applicata: gli invii protetti falliranno finché non viene registrata questa conferma.ID record
+ Aggiungi riga
+ Rimuovi riga
+ Trattenuto
+ Coordinate (lat, lon) o riferimento del luogo
+ Firmo questo record come autore
+ Sì
+ No
+ Modifica
+ Abilitato
+ Anteprima
+ Questo record segue una definizione del dipartimento. I campi con il lucchetto sono riservati; le regole possono mostrare o richiedere campi durante la digitazione.
+ Definizioni
+ Le definizioni dei record descrivono i moduli del dipartimento: sezioni, campi, regole, numerazione e chi revisiona o approva. La pubblicazione congela una versione; i record restano legati alla versione con cui sono stati scritti.
+ Nuova definizione
+ Crea definizione
+ Parti da un modello
+ Clona definizione esistente
+ Definizione vuota
+ Sfoglia modelli
+ I pacchetti di modelli sono punti di partenza revisionati. Ognuno mostra fonti e data di revisione; i pacchetti in anteprima sono utilizzabili ma ancora in validazione con i dipartimenti.
+ Revisionato il
+ Fonti
+ Sezioni
+ Campi
+ Usa questo modello
+ Profilo di giurisdizione
+ Impostazioni locali
+ Anteprima del modello
+ Lettere minuscole, cifre, punti e trattini. Non modificabile dopo la creazione.
+ Nome
+ Categoria
+ Descrizione
+ Proprietario
+ Prodotto (bloccato)
+ Versione pubblicata
+ Versione bozza
+ Preimpostazione del ciclo di vita
+ Capacità del client
+ Ritirata
+ Mostra definizioni ritirate
+ Nascondi definizioni ritirate
+ Cronologia
+ Versione
+ Stato
+ Pubblicato il
+ Note di modifica
+ Impostazioni
+ Soggetti consentiti
+ Ruoli revisore
+ Ruoli approvatore
+ Scadenza revisione (ore)
+ Scadenza approvazione (ore)
+ Richiedi attestazione dell'autore alla finalizzazione
+ Numerazione
+ Prefisso numero
+ Larghezza sequenza
+ Assegna numero
+ Sequenza per stazione
+ Azzera ogni anno
+ Classificazione
+ Conservazione (anni)
+ Superfici client
+ Consenti offline
+ Consenti allegati
+ Mappa di migrazione (JSON)
+ Schema (JSON)
+ Le sezioni contengono campi; ogni campo ha chiave, etichetta, tipo e regole facoltative. Convalida prima di pubblicare; la tabella dei campi riflette l'ultima bozza salvata.
+ Campi
+ Ripetibile
+ Regole
+ Chiave
+ Etichetta
+ Tipo di campo
+ Obbligatorio
+ Classificazione
+ Contrassegni
+ Convalida
+ Salva bozza
+ Verifica impatto e pubblica
+ Apri nuova bozza
+ Elimina bozza
+ Eliminare questa versione bozza? Le versioni pubblicate non vengono toccate.
+ Questa versione è pubblicata ed è di sola lettura. Apri una nuova bozza per modificarla.
+ Problemi di convalida
+ Differenze rispetto al modello da cui è stata creata questa definizione
+ Impatto della pubblicazione
+ La pubblicazione congela questa versione. Le bozze esistenti mantengono la loro versione finché non vengono migrate; i record finalizzati non cambiano mai.
+ Bozze aperte sulla versione corrente
+ Record finalizzati su versioni precedenti
+ Modifica non compatibile
+ Prontezza dei client
+ Modifiche
+ Confronta da
+ a
+ Confronta
+ Nessuna differenza tra queste versioni.
+ Pubblicare questa versione? Diventa di sola lettura e i nuovi record la useranno.
+ Pubblica
+ La pubblicazione richiede il permesso di gestire le definizioni dei record.
+ Ritira
+ Motivo del ritiro di questa definizione
+ Migra bozze aperte
+ Anteprima migrazione
+ Migra ora
+ Spostare tutte le bozze aperte alla versione più recente? I valori non mappati vengono scartati.
+ Definizione creata. Modifica lo schema, convalida e poi pubblica.
+ Bozza salvata.
+ La definizione è valida.
+ La definizione contiene errori. Correggili prima di pubblicare.
+ Versione bozza eliminata.
+ Versione {0} pubblicata.
+ Definizione ritirata. I record esistenti restano leggibili; non se ne possono creare di nuovi.
+ Report salvati
+ I report salvati interrogano i record di una definizione per campo, li raggruppano e aggregano ed esportano in CSV. I risultati rispettano la visibilità e i campi riservati.
+ Nuovo report
+ Nome del report
+ Impostazioni del report
+ Colonne
+ Colonne del record
+ Scegli una definizione per selezionare le colonne.
+ Ordina per
+ Decrescente
+ Raggruppa per
+ Aggregati (JSON)
+ Count, Sum, Average, Min, Max sui campi aggregabili
+ Filtri (JSON)
+ Campi filtrabili
+ Mappature di versione (JSON)
+ Righe max
+ Includi bozze
+ Includi campi riservati
+ Convalida
+ Esegui
+ Ultima esecuzione
+ Eliminare questo report salvato?
+ Righe
+ Gruppi
+ Conteggio
+ I risultati sono stati troncati al limite di righe; restringi i filtri o esporta in CSV.
+ I record su versioni di definizione non mappate sono stati inclusi con celle vuote
+ Report salvato.
+ Report eliminato.
+ Il report è valido.
+ Il report contiene errori.
+ Dispiegamenti
+ Ordini di risorse esterni che il dipartimento sta evadendo. Il sistema ordinante resta autoritativo: gli artefatti sono salvati come snapshot immutabili e nulla viene riscritto.
+ Nuovo dispiegamento
+ I dispiegamenti sono una funzione in anteprima: i modelli derivano dalla documentazione pubblicata IROC, CIFFC ed EMAC, non da integrazioni live. Verifica con il tuo sistema ordinante.
+ Ordine
+ Numero ordine
+ Incidente
+ Paese / suddivisione
+ Uffici
+ Ufficio ordinante
+ Ufficio di dispaccio
+ Agenzie
+ Agenzia richiedente
+ Agenzia ricevente
+ Agenzia inviante
+ Ruolo del dipartimento
+ Evade l'ordine
+ Richiede risorse
+ Ospita l'incidente
+ Costo
+ Codice costo
+ Riferimento accordo
+ Valuta / unità / fuso orario
+ Documento dell'ordine
+ Carica il documento dell'ordine dal sistema di origine (PDF, JSON o CSV). Viene salvato come snapshot immutabile con checksum.
+ Origine
+ Sistema di origine
+ Versione di origine
+ Apri nel sistema di origine
+ Stato
+ Mostra dispiegamenti chiusi
+ Nascondi dispiegamenti chiusi
+ Mobilitato
+ Rilasciato
+ Chiuso
+ Registra un nuovo snapshot
+ Salva snapshot
+ Chiudi dispiegamento
+ Note di chiusura
+ Chiudere questo dispiegamento? Le assegnazioni non potranno più cambiare.
+ Ogni assegnazione accettata deve essere Rientrata prima della chiusura.
+ Assegnazioni
+ Prima assegnazione (facoltativa)
+ Aggiungi ora la prima richiesta/assegnazione oppure dalla pagina del dispiegamento.
+ Aggiungi assegnazione
+ Numero richiesta
+ Richiesta principale
+ Risorsa
+ Posizione
+ Tirocinante
+ Persona assegnata
+ Unità di appartenenza
+ Agenzia ospitante
+ Necessario il
+ Record del dispiegamento
+ Crea dispiegamento
+ Motivo del rifiuto di questa richiesta
+ Dispiegamento creato.
+ Assegnazione aggiunta.
+ Assegnazione aggiornata.
+ Scegli il documento dell'ordine da salvare come snapshot.
+ Snapshot salvato. Il precedente resta conservato e viene sostituito.
+ Dispiegamento chiuso.
+ Layout di stampa
+ Ordina, rinomina o nascondi sezioni e campi per stampa e PDF, aggiungi interruzioni di pagina e posiziona il blocco firma e l'elenco allegati. Ogni salvataggio crea una nuova versione del layout riportata nel piè di pagina di provenienza.
+ Versione del layout
+ Predefinito del dipartimento
+ Ordine
+ Intestazione alternativa
+ Visibile
+ Interruzione di pagina prima
+ Campi
+ Si applica alla versione
+ Tutte le versioni
+ Blocco firma
+ Alla fine
+ Nella sua sezione
+ Nascosto
+ Elenco allegati
+ Tabella
+ Elenco semplice
+ Nascosto
+ Sostituisci il branding del dipartimento per questa definizione
+ Intestazione
+ Layout di stampa salvato.
+ selezionati
+ Seleziona tutto in questa pagina
+ Revisore…
+ Motivo
+ Assegna per revisione
+ Titolo del pacchetto
+ Scopo (verificato)
+ Invia il pacchetto via e-mail a (facoltativo)
+ PDF compilato
+ Pacchetto zip
+ Fino a {0} record per pacchetto. Nessun annullamento o eliminazione in blocco.
+ Seleziona almeno un record.
+ {0} record assegnati per la revisione; {1} ignorati.
+ Pacchetto compilato da {0} record; {1} ignorati. Scaricabile per 30 giorni.
+ Inviato via e-mail.
+ Proiezione del modulo
+ Compone uno snapshot delimitato dal modulo proprietario: solo identificativi, codici, conteggi e stati, con riferimenti alle fonti. Il modulo resta il sistema di riferimento.
+ Proiezione
+ Check-in del personale
+ Riepilogo risorse
+ Qualifiche
+ Riepilogo del comando
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx
index e67a5aa2..a20f0bba 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx
@@ -1064,4 +1064,255 @@
Potwierdzono dniaZaawansowana ochrona danych jest wymuszona: chronione wysyłki będą kończyć się błędem do czasu zapisania tego potwierdzenia.Identyfikator rekordu
+ Dodaj wiersz
+ Usuń wiersz
+ Wstrzymane
+ Współrzędne (szer., dł.) lub odniesienie do miejsca
+ Podpisuję ten rekord jako autor
+ Tak
+ Nie
+ Edytuj
+ Włączone
+ Podgląd
+ Ten rekord jest oparty na definicji jednostki. Pola oznaczone kłódką są zastrzeżone; reguły mogą pokazywać lub wymagać pól podczas wpisywania.
+ Definicje
+ Definicje rekordów opisują formularze jednostki: sekcje, pola, reguły, numerację oraz kto weryfikuje lub zatwierdza. Publikacja zamraża wersję; rekordy pozostają przypięte do wersji, w której powstały.
+ Nowa definicja
+ Utwórz definicję
+ Zacznij od szablonu
+ Sklonuj istniejącą definicję
+ Pusta definicja
+ Przeglądaj szablony
+ Pakiety szablonów to zweryfikowane punkty wyjścia. Każdy pokazuje źródła i datę przeglądu; pakiety w podglądzie są użyteczne, ale wciąż walidowane z jednostkami.
+ Zweryfikowano
+ Źródła
+ Sekcje
+ Pola
+ Użyj tego szablonu
+ Profil jurysdykcji
+ Ustawienia regionalne
+ Podgląd szablonu
+ Małe litery, cyfry, kropki i myślniki. Nie można zmienić po utworzeniu.
+ Nazwa
+ Kategoria
+ Opis
+ Właściciel
+ Produkt (zablokowany)
+ Opublikowana wersja
+ Wersja robocza
+ Ustawienie cyklu życia
+ Zdolność klienta
+ Wycofana
+ Pokaż wycofane definicje
+ Ukryj wycofane definicje
+ Historia
+ Wersja
+ Stan
+ Opublikowano
+ Uwagi o zmianach
+ Ustawienia
+ Dozwolone podmioty
+ Role weryfikatora
+ Role zatwierdzającego
+ Termin weryfikacji (godz.)
+ Termin zatwierdzenia (godz.)
+ Wymagaj poświadczenia autora przy finalizacji
+ Numeracja
+ Prefiks numeru
+ Szerokość sekwencji
+ Przydziel numer
+ Sekwencja na stację
+ Resetuj co rok
+ Klasyfikacja
+ Przechowywanie (lata)
+ Powierzchnie klienta
+ Zezwól offline
+ Zezwól na załączniki
+ Mapa migracji (JSON)
+ Schemat (JSON)
+ Sekcje zawierają pola; każde pole ma klucz, etykietę, typ i opcjonalne reguły. Zweryfikuj przed publikacją; tabela pól odzwierciedla ostatnio zapisany szkic.
+ Pola
+ Powtarzalna
+ Reguły
+ Klucz
+ Etykieta
+ Typ pola
+ Wymagane
+ Klasyfikacja
+ Flagi
+ Zweryfikuj
+ Zapisz szkic
+ Sprawdź wpływ i opublikuj
+ Otwórz nowy szkic
+ Usuń szkic
+ Usunąć tę wersję roboczą? Opublikowane wersje pozostają bez zmian.
+ Ta wersja jest opublikowana i tylko do odczytu. Otwórz nowy szkic, aby wprowadzić zmiany.
+ Problemy walidacji
+ Różnice względem szablonu, z którego utworzono tę definicję
+ Wpływ publikacji
+ Publikacja zamraża tę wersję. Istniejące szkice zachowują swoją wersję do czasu migracji; sfinalizowane rekordy nigdy się nie zmieniają.
+ Otwarte szkice w bieżącej wersji
+ Sfinalizowane rekordy we wcześniejszych wersjach
+ Zmiana niezgodna
+ Gotowość klientów
+ Zmiany
+ Porównaj od
+ do
+ Porównaj
+ Brak różnic między tymi wersjami.
+ Opublikować tę wersję? Stanie się tylko do odczytu i będą jej używać nowe rekordy.
+ Opublikuj
+ Publikacja wymaga uprawnienia do zarządzania definicjami rekordów.
+ Wycofaj
+ Powód wycofania tej definicji
+ Migruj otwarte szkice
+ Podgląd migracji
+ Migruj teraz
+ Przenieść wszystkie otwarte szkice do nowszej wersji? Niezmapowane wartości zostaną odrzucone.
+ Utworzono definicję. Edytuj schemat, zweryfikuj, a następnie opublikuj.
+ Szkic zapisany.
+ Definicja jest poprawna.
+ Definicja zawiera błędy. Popraw je przed publikacją.
+ Wersja robocza usunięta.
+ Opublikowano wersję {0}.
+ Definicja wycofana. Istniejące rekordy pozostają czytelne; nie można tworzyć nowych.
+ Zapisane raporty
+ Zapisane raporty odpytują rekordy jednej definicji według pól, grupują je i agregują oraz eksportują do CSV. Wyniki respektują widoczność i pola zastrzeżone.
+ Nowy raport
+ Nazwa raportu
+ Ustawienia raportu
+ Kolumny
+ Kolumny rekordu
+ Wybierz definicję, aby wybrać kolumny.
+ Sortuj według
+ Malejąco
+ Grupuj według
+ Agregaty (JSON)
+ Count, Sum, Average, Min, Max na polach agregowalnych
+ Filtry (JSON)
+ Pola filtrowalne
+ Mapowania wersji (JSON)
+ Maks. wierszy
+ Uwzględnij szkice
+ Uwzględnij pola zastrzeżone
+ Zweryfikuj
+ Uruchom
+ Ostatnie uruchomienie
+ Usunąć ten zapisany raport?
+ Wiersze
+ Grupy
+ Liczba
+ Wyniki zostały obcięte do limitu wierszy; zawęź filtry lub wyeksportuj do CSV.
+ Rekordy w niezmapowanych wersjach definicji uwzględniono z pustymi komórkami
+ Raport zapisany.
+ Raport usunięty.
+ Raport jest poprawny.
+ Raport zawiera błędy.
+ Oddelegowania
+ Zewnętrzne zamówienia zasobów realizowane przez jednostkę. System zamawiający pozostaje wiążący: artefakty są przechowywane jako niezmienne migawki i nic nie jest zapisywane zwrotnie.
+ Nowe oddelegowanie
+ Oddelegowania to funkcja w podglądzie: wzorce zbudowano z opublikowanej dokumentacji IROC, CIFFC i EMAC, nie z integracji na żywo. Zweryfikuj z systemem zamawiającym.
+ Zamówienie
+ Numer zamówienia
+ Zdarzenie
+ Kraj / region
+ Biura
+ Biuro zamawiające
+ Biuro dyspozytorskie
+ Agencje
+ Agencja wnioskująca
+ Agencja przyjmująca
+ Agencja wysyłająca
+ Rola jednostki
+ Realizuje zamówienie
+ Wnioskuje o zasoby
+ Gospodarz zdarzenia
+ Koszt
+ Kod kosztu
+ Odniesienie do umowy
+ Waluta / jednostki / strefa czasowa
+ Dokument zamówienia
+ Prześlij dokument zamówienia z systemu źródłowego (PDF, JSON lub CSV). Jest zapisywany jako niezmienna migawka z sumą kontrolną.
+ Źródło
+ System źródłowy
+ Wersja źródłowa
+ Otwórz w systemie źródłowym
+ Status
+ Pokaż zamknięte oddelegowania
+ Ukryj zamknięte oddelegowania
+ Zmobilizowano
+ Zwolniono
+ Zamknięto
+ Zapisz nową migawkę
+ Zapisz migawkę
+ Zamknij oddelegowanie
+ Uwagi zamknięcia
+ Zamknąć to oddelegowanie? Obsady nie będą mogły się już zmieniać.
+ Każda zaakceptowana obsada musi mieć status Powrót przed zamknięciem.
+ Obsady
+ Pierwsza obsada (opcjonalnie)
+ Dodaj pierwszą prośbę/obsadę teraz lub ze strony oddelegowania.
+ Dodaj obsadę
+ Numer prośby
+ Prośba nadrzędna
+ Zasób
+ Stanowisko
+ Stażysta
+ Przypisana osoba
+ Jednostka macierzysta
+ Agencja goszcząca
+ Potrzebne na
+ Rekord oddelegowania
+ Utwórz oddelegowanie
+ Powód odrzucenia tej prośby
+ Utworzono oddelegowanie.
+ Dodano obsadę.
+ Zaktualizowano obsadę.
+ Wybierz dokument zamówienia do zapisania jako migawka.
+ Zapisano migawkę. Poprzednia jest zachowana i zastąpiona.
+ Oddelegowanie zamknięte.
+ Układ wydruku
+ Porządkuj, zmieniaj nazwy lub ukrywaj sekcje i pola na wydruku i w PDF, dodawaj podziały stron oraz umieszczaj blok podpisu i listę załączników. Każdy zapis tworzy nową wersję układu zapisywaną w stopce pochodzenia.
+ Wersja układu
+ Domyślny jednostki
+ Kolejność
+ Nagłówek zastępczy
+ Widoczne
+ Podział strony przed
+ Pola
+ Dotyczy wersji
+ Wszystkie wersje
+ Blok podpisu
+ Na końcu
+ W swojej sekcji
+ Ukryty
+ Lista załączników
+ Tabela
+ Prosta lista
+ Ukryta
+ Zastąp branding jednostki dla tej definicji
+ Nagłówek firmowy
+ Zapisano układ wydruku.
+ zaznaczono
+ Zaznacz wszystko na tej stronie
+ Weryfikator…
+ Powód
+ Przypisz do weryfikacji
+ Tytuł pakietu
+ Cel (audytowany)
+ Wyślij pakiet e-mailem do (opcjonalnie)
+ Scalony PDF
+ Paczka zip
+ Do {0} rekordów na pakiet. Brak masowego unieważniania lub usuwania.
+ Zaznacz co najmniej jeden rekord.
+ Przypisano {0} rekord(ów) do weryfikacji; pominięto {1}.
+ Pakiet utworzono z {0} rekord(ów); pominięto {1}. Dostępny do pobrania przez 30 dni.
+ Wysłano e-mailem.
+ Projekcja modułu
+ Tworzy ograniczoną migawkę z modułu źródłowego: tylko identyfikatory, kody, liczby i statusy z odniesieniami do źródła. Moduł pozostaje systemem źródłowym.
+ Projekcja
+ Odprawa personelu
+ Podsumowanie zasobów
+ Kwalifikacje
+ Podsumowanie dowodzenia
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx
index c56a12c6..50a1ab19 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx
@@ -1064,4 +1064,255 @@
Godkänt denAvancerat dataskydd tillämpas: skyddade sändningar misslyckas tills detta godkännande registreras.Post-ID
+ Lägg till rad
+ Ta bort rad
+ Undanhållet
+ Koordinater (lat, lon) eller platsreferens
+ Jag signerar denna post som författare
+ Ja
+ Nej
+ Redigera
+ Aktiverad
+ Förhandsvisning
+ Denna post följer en avdelningsdefinition. Fält med hänglås är begränsade; regler kan visa eller kräva fält medan du skriver.
+ Definitioner
+ Postdefinitioner beskriver avdelningens formulär: sektioner, fält, regler, numrering och vem som granskar eller godkänner. Publicering fryser en version; poster låses till den version de skrevs med.
+ Ny definition
+ Skapa definition
+ Utgå från mall
+ Klona befintlig definition
+ Tom definition
+ Bläddra bland mallar
+ Mallpaket är granskade utgångspunkter. Varje paket visar sina källor och granskningsdatum; förhandsvisningspaket går att använda men valideras fortfarande med avdelningar.
+ Granskad
+ Källor
+ Sektioner
+ Fält
+ Använd denna mall
+ Jurisdiktionsprofil
+ Språkinställning
+ Förhandsvisning av mall
+ Små bokstäver, siffror, punkter och bindestreck. Kan inte ändras efter skapandet.
+ Namn
+ Kategori
+ Beskrivning
+ Ägare
+ Produkt (låst)
+ Publicerad version
+ Utkastversion
+ Livscykelmall
+ Klientkapacitet
+ Avvecklad
+ Visa avvecklade definitioner
+ Dölj avvecklade definitioner
+ Historik
+ Version
+ Tillstånd
+ Publicerad
+ Ändringsanteckningar
+ Inställningar
+ Tillåtna ämnen
+ Granskarroller
+ Godkännarroller
+ Granskning senast (timmar)
+ Godkännande senast (timmar)
+ Kräv författarens intyg vid slutförande
+ Numrering
+ Nummerprefix
+ Sekvensbredd
+ Tilldela nummer
+ Sekvens per station
+ Återställ årligen
+ Klassificering
+ Bevarande (år)
+ Klientytor
+ Tillåt offline
+ Tillåt bilagor
+ Migreringskarta (JSON)
+ Schema (JSON)
+ Sektioner innehåller fält; varje fält har nyckel, etikett, typ och valfria regler. Validera innan publicering; fälttabellen nedan speglar det senast sparade utkastet.
+ Fält
+ Upprepande
+ Regler
+ Nyckel
+ Etikett
+ Fälttyp
+ Obligatoriskt
+ Klassificering
+ Flaggor
+ Validera
+ Spara utkast
+ Granska påverkan & publicera
+ Öppna nytt utkast
+ Ta bort utkast
+ Ta bort denna utkastversion? Publicerade versioner påverkas inte.
+ Denna version är publicerad och skrivskyddad. Öppna ett nytt utkast för att göra ändringar.
+ Valideringsproblem
+ Skillnader mot mallen som denna definition skapades från
+ Publiceringspåverkan
+ Publicering fryser denna version. Befintliga utkast behåller sin version tills de migreras; slutförda poster ändras aldrig.
+ Öppna utkast på nuvarande version
+ Slutförda poster på tidigare versioner
+ Brytande ändring
+ Klientberedskap
+ Ändringar
+ Jämför från
+ till
+ Jämför
+ Inga skillnader mellan dessa versioner.
+ Publicera denna version? Den blir skrivskyddad och nya poster använder den.
+ Publicera
+ Publicering kräver behörigheten att hantera postdefinitioner.
+ Avveckla
+ Orsak till att avveckla denna definition
+ Migrera öppna utkast
+ Förhandsgranska migrering
+ Migrera nu
+ Flytta alla öppna utkast till den nyare versionen? Omappade värden förloras.
+ Definition skapad. Redigera schemat, validera och publicera sedan.
+ Utkast sparat.
+ Definitionen är giltig.
+ Definitionen har fel. Åtgärda dem innan publicering.
+ Utkastversion borttagen.
+ Version {0} publicerad.
+ Definition avvecklad. Befintliga poster kan fortfarande läsas; inga nya kan skapas.
+ Sparade rapporter
+ Sparade rapporter frågar en definitions poster per fält, grupperar och aggregerar dem och exporterar till CSV. Resultaten respekterar synlighet och begränsade fält.
+ Ny rapport
+ Rapportnamn
+ Rapportinställningar
+ Kolumner
+ Postkolumner
+ Välj en definition för att välja kolumner.
+ Sortera efter
+ Fallande
+ Gruppera efter
+ Aggregat (JSON)
+ Count, Sum, Average, Min, Max på aggregerbara fält
+ Filter (JSON)
+ Filtrerbara fält
+ Versionsmappningar (JSON)
+ Max rader
+ Inkludera utkast
+ Inkludera begränsade fält
+ Validera
+ Kör
+ Senaste körning
+ Ta bort denna sparade rapport?
+ Rader
+ Grupper
+ Antal
+ Resultaten kapades vid radgränsen; begränsa filtren eller exportera till CSV.
+ Poster på omappade definitionsversioner inkluderades med tomma celler
+ Rapport sparad.
+ Rapport borttagen.
+ Rapporten är giltig.
+ Rapporten har fel.
+ Insatser
+ Externa resursbeställningar som din avdelning fyller. Beställningssystemet är fortsatt auktoritativt: artefakter lagras som oföränderliga ögonblicksbilder och inget skrivs tillbaka.
+ Ny insats
+ Insatser är en förhandsvisningsfunktion: mallarna byggdes från publicerad IROC-, CIFFC- och EMAC-dokumentation, inte från liveintegrationer. Verifiera mot ditt beställningssystem.
+ Beställning
+ Beställningsnummer
+ Händelse
+ Land / region
+ Kontor
+ Beställande kontor
+ Larmcentral
+ Myndigheter
+ Begärande myndighet
+ Mottagande myndighet
+ Sändande myndighet
+ Avdelningens roll
+ Fyller beställningen
+ Begär resurser
+ Värd för händelsen
+ Kostnad
+ Kostnadskod
+ Avtalsreferens
+ Valuta / enheter / tidszon
+ Beställningsdokument
+ Ladda upp beställningsdokumentet från källsystemet (PDF, JSON eller CSV). Det lagras som en oföränderlig ögonblicksbild med kontrollsumma.
+ Källa
+ Källsystem
+ Källversion
+ Öppna i källsystemet
+ Status
+ Visa avslutade insatser
+ Dölj avslutade insatser
+ Mobiliserad
+ Frisläppt
+ Avslutad
+ Registrera ny ögonblicksbild
+ Spara ögonblicksbild
+ Avsluta insats
+ Avslutningsanteckningar
+ Avsluta denna insats? Tillsättningar kan inte längre ändras.
+ Varje accepterad tillsättning måste vara Återvänd före avslut.
+ Tillsättningar
+ Första tillsättning (valfritt)
+ Lägg till första begäran/tillsättning nu eller från insatssidan.
+ Lägg till tillsättning
+ Begärannummer
+ Överordnad begäran
+ Resurs
+ Befattning
+ Praktikant
+ Tilldelad person
+ Hemmaenhet
+ Värdmyndighet
+ Behövs den
+ Insatspost
+ Skapa insats
+ Orsak till att avböja denna begäran
+ Insats skapad.
+ Tillsättning tillagd.
+ Tillsättning uppdaterad.
+ Välj beställningsdokumentet som ska sparas som ögonblicksbild.
+ Ögonblicksbild sparad. Den tidigare behålls och ersätts.
+ Insats avslutad.
+ Utskriftslayout
+ Ordna, byt namn på eller dölj sektioner och fält för utskrift och PDF, lägg till sidbrytningar och placera signaturblock och bilagelista. Varje sparning skapar en ny layoutversion som stämplas i proveniensfoten.
+ Layoutversion
+ Avdelningsstandard
+ Ordning
+ Alternativ rubrik
+ Synlig
+ Sidbrytning före
+ Fält
+ Gäller version
+ Alla versioner
+ Signaturblock
+ I slutet
+ I sin sektion
+ Dold
+ Bilagelista
+ Tabell
+ Enkel lista
+ Dold
+ Åsidosätt avdelningens profil för denna definition
+ Brevhuvud
+ Utskriftslayout sparad.
+ valda
+ Markera alla på denna sida
+ Granskare…
+ Orsak
+ Tilldela för granskning
+ Pakettitel
+ Syfte (granskas)
+ E-posta paketet till (valfritt)
+ Sammanställd PDF
+ Zip-paket
+ Upp till {0} poster per paket. Ingen massannullering eller -radering.
+ Välj minst en post.
+ {0} post(er) tilldelade för granskning; {1} hoppades över.
+ Paket sammanställt från {0} post(er); {1} hoppades över. Nedladdningsbart i 30 dagar.
+ Skickat via e-post.
+ Modulprojektion
+ Sätter samman en avgränsad ögonblicksbild från ägarmodulen: endast identifierare, koder, antal och statusar med källreferenser. Modulen förblir systemet som gäller.
+ Projektion
+ Personalincheckning
+ Resurssammanfattning
+ Kvalifikationer
+ Ledningssammanfattning
diff --git a/Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx b/Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx
index 86e67e5e..27b59712 100644
--- a/Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx
+++ b/Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx
@@ -1064,4 +1064,255 @@
ПідтвердженоЗастосовано Розширений захист даних: захищені надсилання не працюватимуть, доки не зафіксовано це підтвердження.Ідентифікатор запису
+ Додати рядок
+ Видалити рядок
+ Приховано
+ Координати (шир., довг.) або посилання на місце
+ Я підписую цей запис як автор
+ Так
+ Ні
+ Редагувати
+ Увімкнено
+ Попередній перегляд
+ Цей запис відповідає визначенню підрозділу. Поля з замком обмежені; правила можуть показувати або вимагати поля під час введення.
+ Визначення
+ Визначення записів описують форми вашого підрозділу: розділи, поля, правила, нумерацію та хто перевіряє чи затверджує. Публікація заморожує версію; записи закріплюються за версією, в якій їх створено.
+ Нове визначення
+ Створити визначення
+ Почати з шаблону
+ Клонувати наявне визначення
+ Порожнє визначення
+ Переглянути шаблони
+ Пакети шаблонів є перевіреними відправними точками. Кожен показує джерела та дату перегляду; пакети попереднього перегляду можна використовувати, але вони ще перевіряються з підрозділами.
+ Переглянуто
+ Джерела
+ Розділи
+ Поля
+ Використати цей шаблон
+ Профіль юрисдикції
+ Локаль
+ Попередній перегляд шаблону
+ Малі літери, цифри, крапки та дефіси. Не можна змінити після створення.
+ Назва
+ Категорія
+ Опис
+ Власник
+ Продукт (заблоковано)
+ Опублікована версія
+ Чернетка версії
+ Шаблон життєвого циклу
+ Можливість клієнта
+ Виведено з ужитку
+ Показати виведені визначення
+ Приховати виведені визначення
+ Історія
+ Версія
+ Стан
+ Опубліковано
+ Примітки до змін
+ Налаштування
+ Дозволені суб'єкти
+ Ролі рецензентів
+ Ролі затверджувачів
+ Термін перевірки (год.)
+ Термін затвердження (год.)
+ Вимагати підтвердження автора під час завершення
+ Нумерація
+ Префікс номера
+ Ширина послідовності
+ Призначити номер
+ Послідовність на станцію
+ Скидати щороку
+ Класифікація
+ Зберігання (роки)
+ Клієнтські поверхні
+ Дозволити офлайн
+ Дозволити вкладення
+ Карта міграції (JSON)
+ Схема (JSON)
+ Розділи містять поля; кожне поле має ключ, підпис, тип і необов'язкові правила. Перевірте перед публікацією; таблиця полів нижче відображає останню збережену чернетку.
+ Поля
+ Повторювана
+ Правила
+ Ключ
+ Підпис
+ Тип поля
+ Обов'язкове
+ Класифікація
+ Прапорці
+ Перевірити
+ Зберегти чернетку
+ Переглянути вплив і опублікувати
+ Відкрити нову чернетку
+ Видалити чернетку
+ Видалити цю чернетку версії? Опубліковані версії не зачіпаються.
+ Ця версія опублікована й доступна лише для читання. Відкрийте нову чернетку для змін.
+ Проблеми перевірки
+ Відмінності від шаблону, з якого створено це визначення
+ Вплив публікації
+ Публікація заморожує цю версію. Наявні чернетки зберігають свою версію до міграції; завершені записи ніколи не змінюються.
+ Відкриті чернетки поточної версії
+ Завершені записи в попередніх версіях
+ Несумісна зміна
+ Готовність клієнтів
+ Зміни
+ Порівняти від
+ до
+ Порівняти
+ Між цими версіями немає відмінностей.
+ Опублікувати цю версію? Вона стане лише для читання, і нові записи використовуватимуть її.
+ Опублікувати
+ Публікація потребує дозволу на керування визначеннями записів.
+ Вивести з ужитку
+ Причина виведення цього визначення
+ Мігрувати відкриті чернетки
+ Попередній перегляд міграції
+ Мігрувати зараз
+ Перенести всі відкриті чернетки на новішу версію? Незіставлені значення буде відкинуто.
+ Визначення створено. Відредагуйте схему, перевірте, потім опублікуйте.
+ Чернетку збережено.
+ Визначення дійсне.
+ Визначення містить помилки. Виправте їх перед публікацією.
+ Чернетку версії видалено.
+ Версію {0} опубліковано.
+ Визначення виведено з ужитку. Наявні записи залишаються доступними; нові створити не можна.
+ Збережені звіти
+ Збережені звіти запитують записи одного визначення за полями, групують і агрегують їх та експортують у CSV. Результати враховують видимість і обмежені поля.
+ Новий звіт
+ Назва звіту
+ Налаштування звіту
+ Стовпці
+ Стовпці запису
+ Виберіть визначення, щоб обрати стовпці.
+ Сортувати за
+ За спаданням
+ Групувати за
+ Агрегати (JSON)
+ Count, Sum, Average, Min, Max для агрегованих полів
+ Фільтри (JSON)
+ Поля для фільтрації
+ Зіставлення версій (JSON)
+ Макс. рядків
+ Включити чернетки
+ Включити обмежені поля
+ Перевірити
+ Запустити
+ Останній запуск
+ Видалити цей збережений звіт?
+ Рядки
+ Групи
+ Кількість
+ Результати обрізано за лімітом рядків; звузьте фільтри або експортуйте в CSV.
+ Записи на незіставлених версіях визначення включено з порожніми комірками
+ Звіт збережено.
+ Звіт видалено.
+ Звіт дійсний.
+ Звіт містить помилки.
+ Розгортання
+ Зовнішні замовлення ресурсів, які виконує ваш підрозділ. Система замовлення залишається авторитетною: артефакти зберігаються як незмінні знімки, і нічого не записується назад.
+ Нове розгортання
+ Розгортання є функцією попереднього перегляду: шаблони побудовано з опублікованої документації IROC, CIFFC та EMAC, а не з живих інтеграцій. Перевірте у своїй системі замовлення.
+ Замовлення
+ Номер замовлення
+ Інцидент
+ Країна / регіон
+ Офіси
+ Офіс замовлення
+ Диспетчерський офіс
+ Агенції
+ Агенція-замовник
+ Агенція-отримувач
+ Агенція-відправник
+ Роль підрозділу
+ Виконує замовлення
+ Запитує ресурси
+ Приймає інцидент
+ Вартість
+ Код витрат
+ Посилання на угоду
+ Валюта / одиниці / часовий пояс
+ Документ замовлення
+ Завантажте документ замовлення з вихідної системи (PDF, JSON або CSV). Він зберігається як незмінний знімок із контрольною сумою.
+ Джерело
+ Вихідна система
+ Версія джерела
+ Відкрити у вихідній системі
+ Статус
+ Показати закриті розгортання
+ Приховати закриті розгортання
+ Мобілізовано
+ Звільнено
+ Закрито
+ Записати новий знімок
+ Зберегти знімок
+ Закрити розгортання
+ Примітки закриття
+ Закрити це розгортання? Заповнення більше не можна змінювати.
+ Кожне прийняте заповнення має бути повернуте перед закриттям.
+ Заповнення
+ Перше заповнення (необов'язково)
+ Додайте перший запит/заповнення зараз або зі сторінки розгортання.
+ Додати заповнення
+ Номер запиту
+ Батьківський запит
+ Ресурс
+ Посада
+ Стажер
+ Призначена особа
+ Домашній підрозділ
+ Приймаюча агенція
+ Потрібно на
+ Запис розгортання
+ Створити розгортання
+ Причина відхилення цього запиту
+ Розгортання створено.
+ Заповнення додано.
+ Заповнення оновлено.
+ Виберіть документ замовлення для збереження як знімок.
+ Знімок збережено. Попередній збережено та замінено.
+ Розгортання закрито.
+ Макет друку
+ Упорядковуйте, перейменовуйте або приховуйте розділи та поля для друку і PDF, додавайте розриви сторінок і розміщуйте блок підпису та список вкладень. Кожне збереження створює нову версію макета, зазначену в нижньому колонтитулі походження.
+ Версія макета
+ Типовий для підрозділу
+ Порядок
+ Заміна заголовка
+ Видимий
+ Розрив сторінки перед
+ Поля
+ Застосовується до версії
+ Усі версії
+ Блок підпису
+ Наприкінці
+ У своєму розділі
+ Приховано
+ Список вкладень
+ Таблиця
+ Простий список
+ Приховано
+ Перевизначити брендинг підрозділу для цього визначення
+ Бланк
+ Макет друку збережено.
+ вибрано
+ Вибрати всі на цій сторінці
+ Рецензент…
+ Причина
+ Призначити на перевірку
+ Назва пакета
+ Мета (аудит)
+ Надіслати пакет на e-mail (необов'язково)
+ Зведений PDF
+ Zip-пакет
+ До {0} записів у пакеті. Без масового анулювання чи видалення.
+ Виберіть принаймні один запис.
+ Призначено {0} запис(ів) на перевірку; пропущено {1}.
+ Пакет зібрано з {0} запис(ів); пропущено {1}. Доступний для завантаження 30 днів.
+ Надіслано на e-mail.
+ Проєкція модуля
+ Складає обмежений знімок з модуля-власника: лише ідентифікатори, коди, кількості та статуси з посиланнями на джерело. Модуль залишається системою обліку.
+ Проєкція
+ Реєстрація особового складу
+ Зведення ресурсів
+ Кваліфікації
+ Зведення командування
diff --git a/Core/Resgrid.Model/AdpTableBinding.cs b/Core/Resgrid.Model/AdpTableBinding.cs
index 5b8d213b..4d929be8 100644
--- a/Core/Resgrid.Model/AdpTableBinding.cs
+++ b/Core/Resgrid.Model/AdpTableBinding.cs
@@ -60,6 +60,18 @@ public static AdpTableBinding ViaParent(string tableName, string pkColumn, bool
/// Row-level protection marker column ("IsProtected"), when the table has one (companion pattern).
public string ProtectedMarkerColumn { get; init; }
+
+ ///
+ /// Non-cataloged sibling columns a PackedJson column packs and clears (ADP catalog v11). They are read and
+ /// written with the row but never counted as residue on their own.
+ ///
+ public IReadOnlyList CarrierColumns { get; init; } = Array.Empty();
+
+ ///
+ /// Optional boolean column that scopes the sweep to rows needing protection (RmsRecordValues.ProtectionRequired);
+ /// null sweeps every row of the table.
+ ///
+ public string RowFilterColumn { get; init; }
}
/// One cataloged column inside a binding.
diff --git a/Core/Resgrid.Model/ProtectedFieldStorageKind.cs b/Core/Resgrid.Model/ProtectedFieldStorageKind.cs
index 20914233..782b03fb 100644
--- a/Core/Resgrid.Model/ProtectedFieldStorageKind.cs
+++ b/Core/Resgrid.Model/ProtectedFieldStorageKind.cs
@@ -16,6 +16,14 @@ public enum ProtectedFieldStorageKind
/// while protected and a Protected{Name}Envelope companion column carries the value
/// (Appendix B pattern).
///
- CompanionColumn = 3
+ CompanionColumn = 3,
+
+ ///
+ /// A JSON pack of a row's sibling typed columns carried as a text envelope in a dedicated column
+ /// (RmsRecordValues.ProtectedEnvelope, ADP catalog v11). While sealed the siblings are null, so the row still
+ /// populates exactly one column group; the binding names the siblings as carrier columns and a boolean row
+ /// filter scopes the sweep to rows whose field is Protected-classified.
+ ///
+ PackedJson = 4
}
}
diff --git a/Core/Resgrid.Model/Records/FieldRecordsContracts.cs b/Core/Resgrid.Model/Records/FieldRecordsContracts.cs
new file mode 100644
index 00000000..4bed5ab2
--- /dev/null
+++ b/Core/Resgrid.Model/Records/FieldRecordsContracts.cs
@@ -0,0 +1,328 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Resgrid.Model
+{
+ ///
+ /// The versioned Field Records client contract (RMS plan RMS-1D "FieldRecordCatalogV1"): what a field app may
+ /// assume the server speaks. Everything a client renders, validates, queues or syncs is named here so the shared
+ /// golden fixtures can pin it; a client that reports an unknown control or rule is refused for authoring and may
+ /// still read. Constants only; the catalog itself is computed per request by .
+ ///
+ public static class FieldRecordCatalogV1
+ {
+ public const string ContractVersion = "field-catalog.v1";
+ public const string SyncContractVersion = "field-sync.v1";
+ public const string PrefillContractVersion = "field-prefill.v1";
+
+ /// Launch contexts a definition surface may name; "none" is the app's Field Records home.
+ public static class LaunchContexts
+ {
+ public const string None = "none";
+ public const string Call = "call";
+ public const string Unit = "unit";
+ public const string Contact = "contact";
+ public const string Checklist = "checklist";
+ public const string WorkOrder = "workorder";
+ public const string Command = "command";
+ public static readonly IReadOnlyList All = new[] { None, Call, Unit, Contact, Checklist, WorkOrder, Command };
+ }
+
+ /// Why a definition is withheld from a catalog. Codes only: never a reason a forged client could act on.
+ public static class ExclusionReasons
+ {
+ public const string OriginNotField = "origin_not_field";
+ public const string ModuleDisabled = "module_disabled";
+ public const string RecordsNotUsable = "records_not_usable";
+ public const string AppDisabled = "app_disabled";
+ public const string AppVersionTooOld = "app_version_too_old";
+ public const string NotMember = "not_member";
+ public const string SurfaceNotEnabled = "surface_not_enabled";
+ public const string ContextNotAllowed = "context_not_allowed";
+ public const string ContextNotVerified = "context_not_verified";
+ public const string CapabilityUnsupported = "capability_unsupported";
+ public const string Retired = "retired";
+ public const string NotPublished = "not_published";
+ public const string ProtectedDataUnavailable = "protected_data_unavailable";
+ public static readonly IReadOnlyList All = new[]
+ {
+ OriginNotField, ModuleDisabled, RecordsNotUsable, AppDisabled, AppVersionTooOld, NotMember, SurfaceNotEnabled, ContextNotAllowed, ContextNotVerified,
+ CapabilityUnsupported, Retired, NotPublished, ProtectedDataUnavailable
+ };
+ }
+
+ /// Controls the contract expects every adapter to render (the RMS-1B/1C field catalog).
+ public static readonly IReadOnlyList SupportedControls = Enum.GetNames(typeof(RmsFieldType));
+
+ /// Rule effects and operators an adapter must evaluate client-side exactly as the server does.
+ public static readonly IReadOnlyList SupportedRuleEffects = Enum.GetNames(typeof(RmsRuleEffect));
+ public static readonly IReadOnlyList SupportedRuleOperators = Enum.GetNames(typeof(RmsRuleOperator));
+
+ /// Lifecycle actions a field client may issue. Amendment, void and approval stay Web-first (plan RMS-1D).
+ public static readonly IReadOnlyList LifecycleActions = new[] { "create", "save", "submit-for-review", "finalize", "cancel", "acknowledge-assignment", "complete-assignment" };
+
+ /// Sync states a client reports and the server can answer with.
+ public static readonly IReadOnlyList SyncStates = new[] { "fresh", "delta", "reset-required", "conflict" };
+
+ /// Conflict kinds a client must present explicitly rather than replay silently.
+ public static readonly IReadOnlyList ConflictKinds = new[] { "etag", "permission", "scope", "definition-retired", "protected-data", "app-version" };
+
+ /// Prefill sources a definition surface's prefill map may name (server-calculated, provenance-stamped).
+ public static readonly IReadOnlyList PrefillSources = new[]
+ {
+ "call.id", "call.number", "call.name", "call.nature", "call.address", "call.geolocation", "call.logged_on",
+ "unit.id", "unit.name", "group.id", "group.name", "user.id", "command.name", "command.commander", "now"
+ };
+
+ ///
+ /// Locked system definitions a field app may start without a department surface (the "Field-ready starter
+ /// allowlist"): Responder authors self/assignment records, Unit authors apparatus activity, IC and Dispatch
+ /// author Call-bound run records. A department definition reaches an app only through its client surface.
+ ///
+ public static IReadOnlyList LockedStarterAllowlist(RmsOriginClient origin)
+ {
+ switch (origin)
+ {
+ case RmsOriginClient.Responder: return new[] { RmsDefinitionKeys.Training, RmsDefinitionKeys.Meeting, RmsDefinitionKeys.Work };
+ case RmsOriginClient.Unit: return new[] { RmsDefinitionKeys.UnitActivity, RmsDefinitionKeys.Run };
+ case RmsOriginClient.IncidentCommand: return new[] { RmsDefinitionKeys.Run };
+ case RmsOriginClient.Dispatch: return new[] { RmsDefinitionKeys.Callback, RmsDefinitionKeys.Run };
+ default: return Array.Empty();
+ }
+ }
+
+ /// The launch contexts a locked starter definition accepts.
+ public static IReadOnlyList LockedLaunchContexts(string definitionKey)
+ {
+ switch (definitionKey)
+ {
+ case RmsDefinitionKeys.Run: return new[] { LaunchContexts.Call, LaunchContexts.Command };
+ case RmsDefinitionKeys.UnitActivity: return new[] { LaunchContexts.Unit, LaunchContexts.Call, LaunchContexts.None };
+ case RmsDefinitionKeys.Callback: return new[] { LaunchContexts.Call, LaunchContexts.None };
+ default: return new[] { LaunchContexts.None };
+ }
+ }
+
+ public static bool IsFieldOrigin(RmsOriginClient origin)
+ => origin == RmsOriginClient.Responder || origin == RmsOriginClient.Unit || origin == RmsOriginClient.IncidentCommand || origin == RmsOriginClient.Dispatch;
+
+ /// Numeric dotted-version compare ("1.2.10" > "1.2.9"); a null or blank side sorts lowest; non-numeric segments compare ordinally.
+ public static int CompareVersions(string left, string right)
+ {
+ var a = Segments(left);
+ var b = Segments(right);
+ for (var i = 0; i < Math.Max(a.Count, b.Count); i++)
+ {
+ var x = i < a.Count ? a[i] : "0";
+ var y = i < b.Count ? b[i] : "0";
+ if (int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var xi) && int.TryParse(y, NumberStyles.Integer, CultureInfo.InvariantCulture, out var yi))
+ {
+ if (xi != yi) return xi.CompareTo(yi);
+ continue;
+ }
+ var ordinal = string.CompareOrdinal(x, y);
+ if (ordinal != 0) return ordinal;
+ }
+ return 0;
+ }
+
+ /// True when the app version meets the minimum; an unset minimum passes, an unset app version never does.
+ public static bool MeetsMinimum(string appVersion, string minimum)
+ {
+ if (string.IsNullOrWhiteSpace(minimum)) return true;
+ if (string.IsNullOrWhiteSpace(appVersion)) return false;
+ return CompareVersions(appVersion, minimum) >= 0;
+ }
+
+ private static List Segments(string version)
+ {
+ var text = (version ?? string.Empty).Trim();
+ if (text.StartsWith("v", StringComparison.OrdinalIgnoreCase)) text = text.Substring(1);
+ var plus = text.IndexOf('+');
+ if (plus >= 0) text = text.Substring(0, plus);
+ var dash = text.IndexOf('-');
+ if (dash >= 0) text = text.Substring(0, dash);
+ return text.Length == 0 ? new List() : text.Split('.').Select(s => s.Trim()).ToList();
+ }
+ }
+
+ /// The verified field context a request runs in: identifiers only, every one checked server-side.
+ public class FieldRecordContext
+ {
+ public int? CallId { get; set; }
+ public int? UnitId { get; set; }
+ public int? GroupId { get; set; }
+ /// IC app: the command role the user claims on the Call; verified against the active command.
+ public string CommandRole { get; set; }
+ public int? ContactId { get; set; }
+
+ public bool IsEmpty => !CallId.HasValue && !UnitId.HasValue && !GroupId.HasValue && string.IsNullOrWhiteSpace(CommandRole) && !ContactId.HasValue;
+
+ /// The launch context kind this context represents for catalog filtering.
+ public string Kind
+ {
+ get
+ {
+ if (!string.IsNullOrWhiteSpace(CommandRole)) return FieldRecordCatalogV1.LaunchContexts.Command;
+ if (UnitId.HasValue) return FieldRecordCatalogV1.LaunchContexts.Unit;
+ if (CallId.HasValue) return FieldRecordCatalogV1.LaunchContexts.Call;
+ if (ContactId.HasValue) return FieldRecordCatalogV1.LaunchContexts.Contact;
+ return FieldRecordCatalogV1.LaunchContexts.None;
+ }
+ }
+ }
+
+ /// Outcome of server-side context authorization.
+ public class FieldRecordContextVerification
+ {
+ public bool Ok { get; set; }
+ public List Reasons { get; set; } = new List();
+ public string CallNumber { get; set; }
+ public string UnitName { get; set; }
+ public string GroupName { get; set; }
+ public string CommandName { get; set; }
+ public bool StaffedOnUnit { get; set; }
+ public bool HoldsCommandRole { get; set; }
+ public static FieldRecordContextVerification Allowed() => new FieldRecordContextVerification { Ok = true };
+ public static FieldRecordContextVerification Denied(string reason) => new FieldRecordContextVerification { Ok = false, Reasons = { reason } };
+ }
+
+ public class FieldRecordCatalogRequest
+ {
+ public RmsOriginClient Origin { get; set; }
+ public string AppVersion { get; set; }
+ /// The renderer capability the client reports (records.v1 / v1b / v1c); unknown is treated as records.v1.
+ public string ClientCapability { get; set; }
+ public FieldRecordContext Context { get; set; } = new FieldRecordContext();
+ }
+
+ /// Minimum-version / flag preflight a field app runs before showing Records at all.
+ public class FieldRecordPreflight
+ {
+ public string ContractVersion { get; set; } = FieldRecordCatalogV1.ContractVersion;
+ public string SyncContractVersion { get; set; } = FieldRecordCatalogV1.SyncContractVersion;
+ public RmsOriginClient Origin { get; set; }
+ public bool Ok { get; set; }
+ public List Reasons { get; set; } = new List();
+ public bool ModuleEnabled { get; set; }
+ public bool RecordsUsable { get; set; }
+ public bool AppEnabled { get; set; }
+ public string MinimumAppVersion { get; set; }
+ public string AppVersion { get; set; }
+ public string ClientCapability { get; set; }
+ public string ProtectionState { get; set; }
+ public long ServerTimestampMs { get; set; }
+ }
+
+ /// One definition a field app may start, with what it needs to decide offline/attachment/protected behavior.
+ public class FieldRecordCatalogEntry
+ {
+ public string DefinitionKey { get; set; }
+ public int Version { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public bool Locked { get; set; }
+ public int? RecordType { get; set; }
+ public string LifecyclePreset { get; set; }
+ public List LaunchContexts { get; set; } = new List();
+ public bool AllowOffline { get; set; }
+ public bool AllowAttachments { get; set; }
+ public string MinimumAppVersion { get; set; }
+ public string MinimumClientCapability { get; set; }
+ public bool Restricted { get; set; }
+ /// The definition carries Protected fields: authoring needs a live grant and never caches plaintext offline.
+ public bool RequiresProtectedGrant { get; set; }
+ public string SchemaChecksum { get; set; }
+ public int PrefillVersion { get; set; }
+ public bool SupportsPrefill { get; set; }
+ }
+
+ public class FieldRecordCatalogExclusion
+ {
+ public string DefinitionKey { get; set; }
+ public string Reason { get; set; }
+ }
+
+ /// The manifest, filtered server-side; a client cannot widen it by changing a query value.
+ public class FieldRecordCatalog
+ {
+ public string ContractVersion { get; set; } = FieldRecordCatalogV1.ContractVersion;
+ public RmsOriginClient Origin { get; set; }
+ public bool Ok { get; set; }
+ public List Reasons { get; set; } = new List();
+ public string ContextKind { get; set; }
+ public bool ContextVerified { get; set; }
+ public string ProtectionState { get; set; }
+ public string ScopeStamp { get; set; }
+ public List Definitions { get; set; } = new List();
+ public List Exclusions { get; set; } = new List();
+ public long ServerTimestampMs { get; set; }
+
+ public bool Includes(string definitionKey, int version) => Definitions.Any(d => string.Equals(d.DefinitionKey, definitionKey, StringComparison.OrdinalIgnoreCase) && d.Version == version);
+ }
+
+ /// Where one prefilled value came from.
+ public class FieldRecordPrefillProvenance
+ {
+ public string FieldKey { get; set; }
+ public string Source { get; set; }
+ public string SourceId { get; set; }
+ public DateTime CapturedOn { get; set; }
+ }
+
+ /// Server-calculated prefill for a definition version in a verified context.
+ public class FieldRecordPrefill
+ {
+ public string ContractVersion { get; set; } = FieldRecordCatalogV1.PrefillContractVersion;
+ public string DefinitionKey { get; set; }
+ public int Version { get; set; }
+ public int PrefillVersion { get; set; }
+ public int? CallId { get; set; }
+ public int? UnitId { get; set; }
+ public int? StationGroupId { get; set; }
+ public List Values { get; set; } = new List();
+ public List Provenance { get; set; } = new List();
+ public List SuggestedParticipantUserIds { get; set; } = new List();
+ public List SuggestedUnitIds { get; set; } = new List();
+ public DateTime CalculatedOn { get; set; }
+ }
+
+ public class FieldRecordSyncRequest
+ {
+ public RmsOriginClient Origin { get; set; }
+ public string AppVersion { get; set; }
+ public string ClientCapability { get; set; }
+ public FieldRecordContext Context { get; set; } = new FieldRecordContext();
+ public long Since { get; set; }
+ public string SinceId { get; set; }
+ public string ScopeStamp { get; set; }
+ public int Take { get; set; } = 200;
+ public bool IncludeCatalog { get; set; } = true;
+ }
+
+ ///
+ /// A bounded Field Records sync bundle: catalog, authorized change delta with tombstones, the caller's own
+ /// drafts and returned Records, and the caller's open work assignments. Never a bulk RMS export: every row
+ /// is re-authorized at read time and a scope change resets the client.
+ ///
+ public class FieldRecordSyncBundle
+ {
+ public string ContractVersion { get; set; } = FieldRecordCatalogV1.SyncContractVersion;
+ public bool Ok { get; set; }
+ public List Reasons { get; set; } = new List();
+ public string ScopeStamp { get; set; }
+ public bool ResetRequired { get; set; }
+ public long Since { get; set; }
+ public long ServerTimestampMs { get; set; }
+ public string ServerCursorId { get; set; }
+ public bool HasMore { get; set; }
+ public FieldRecordCatalog Catalog { get; set; }
+ public List Changes { get; set; } = new List();
+ /// Record ids in the caller may no longer read; the client evicts them.
+ public List Tombstones { get; set; } = new List();
+ public List Drafts { get; set; } = new List();
+ public List Assignments { get; set; } = new List();
+ }
+}
diff --git a/Core/Resgrid.Model/Records/IncidentReportContracts.cs b/Core/Resgrid.Model/Records/IncidentReportContracts.cs
index e733c9a0..ed0e4358 100644
--- a/Core/Resgrid.Model/Records/IncidentReportContracts.cs
+++ b/Core/Resgrid.Model/Records/IncidentReportContracts.cs
@@ -146,6 +146,12 @@ public class IncidentReportDraftInput
/// One conditional section instance being saved; is the contract-shaped body.
public class IncidentModuleInput
{
+ ///
+ /// The stored row this input replaces, or null for a new section. Identity has to travel with the row:
+ /// matching by list position would hand one section's id, ProtectionId and sealed envelopes to another
+ /// section's content as soon as a client reorders or removes an entry.
+ ///
+ public string ModuleId { get; set; }
public RmsIncidentModuleKind Kind { get; set; }
public string PrimaryCode { get; set; }
public string SecondaryCode { get; set; }
@@ -157,6 +163,8 @@ public class IncidentModuleInput
public class IncidentResourceInput
{
+ /// The stored row this input replaces, or null for a new resource. See .
+ public string ResourceId { get; set; }
public string ResourceCode { get; set; }
public int? Quantity { get; set; }
public string Detail { get; set; }
@@ -200,6 +208,8 @@ public class IncidentCasualtyRescueInput
public class IncidentExposureInput
{
+ /// The stored row this input replaces, or null for a new exposure. See .
+ public string ExposureId { get; set; }
public string LocationKind { get; set; }
public string ItemType { get; set; }
public string DamageType { get; set; }
diff --git a/Core/Resgrid.Model/Records/RecordsBulkContracts.cs b/Core/Resgrid.Model/Records/RecordsBulkContracts.cs
new file mode 100644
index 00000000..3f5a810c
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RecordsBulkContracts.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+
+namespace Resgrid.Model
+{
+ /// How a bulk packet is compiled (RMS plan section 4.7, "Bulk operations and packets").
+ public enum RecordsBulkPacketMode
+ {
+ /// One compiled PDF: cover page with manifest, then every record in selection order.
+ CompiledPdf = 1,
+ /// A zip bundle of per-record PDFs plus a manifest.json.
+ Bundle = 2
+ }
+
+ /// A bounded bulk print/export over an authorized selection; never bulk void or bulk delete.
+ public class RecordsBulkPacketRequest
+ {
+ /// Most records one packet may compile; a packet is a deliverable, not an export of the archive.
+ public const int MaxRecords = 200;
+
+ public List RecordIds { get; set; } = new List();
+ public RecordsBulkPacketMode Mode { get; set; } = RecordsBulkPacketMode.CompiledPdf;
+ public string Title { get; set; }
+ /// Why the packet is produced (accreditation, insurance, discovery, board packet); audited per record.
+ public string Purpose { get; set; }
+ /// Optional delivery through the scheduled-report email path; the stored run stays downloadable either way.
+ public string DeliverToEmail { get; set; }
+ public RmsOriginClient OriginClient { get; set; } = RmsOriginClient.Web;
+ }
+
+ public class RecordsBulkAssignRequest
+ {
+ public List RecordIds { get; set; } = new List();
+ public string ReviewerUserId { get; set; }
+ public string Reason { get; set; }
+ }
+
+ public class RecordsBulkResult
+ {
+ public int Processed { get; set; }
+ public int Skipped { get; set; }
+ public List Skips { get; set; } = new List();
+ /// The stored run for a packet (download through the export runs surface); null for an assignment.
+ public RmsExportRun Run { get; set; }
+ public bool Delivered { get; set; }
+ }
+
+ public class RecordsBulkSkip
+ {
+ public string RecordId { get; set; }
+ public string Reason { get; set; }
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RecordsContracts.cs b/Core/Resgrid.Model/Records/RecordsContracts.cs
index 8d487330..ee7910e0 100644
--- a/Core/Resgrid.Model/Records/RecordsContracts.cs
+++ b/Core/Resgrid.Model/Records/RecordsContracts.cs
@@ -158,6 +158,8 @@ public class RecordNewCallInput
public class RecordDraftInput
{
public RecordUdfInput CustomFields { get; set; }
+ /// Typed values for a department definition (RMS-1B, plan section 5.3); ignored for locked system definitions.
+ public List Values { get; set; } = new List();
/// One of ; required on create.
public string DefinitionKey { get; set; }
public int? CallId { get; set; }
@@ -185,6 +187,11 @@ public class RecordAggregate
public ProtectedReadResult Protection { get; set; }
public RecordUdfSection CustomFields { get; set; }
+ /// Typed values rendered against the pinned definition version (department definitions only).
+ public RecordValueSet Values { get; set; }
+ /// The pinned definition version for a department definition; null for locked system definitions.
+ [Newtonsoft.Json.JsonIgnore]
+ public RmsRecordDefinitionVersion DefinitionVersionRow { get; set; }
public RmsOperationalRecord Record { get; set; }
public RmsOperationalRecordDetail Details { get; set; }
public List Participants { get; set; } = new List();
@@ -201,6 +208,8 @@ public class RecordAggregate
public class RecordSnapshot
{
public RecordUdfSection CustomFields { get; set; }
+ /// Department-definition typed values pinned to the version's labels: section label -> field label -> display, or a row array for repeating sections.
+ public Dictionary Values { get; set; }
public int SnapshotVersion { get; set; } = 1;
public List Evidence { get; set; } = new List();
public string RecordId { get; set; }
diff --git a/Core/Resgrid.Model/Records/RmsEvidenceArtifact.cs b/Core/Resgrid.Model/Records/RmsEvidenceArtifact.cs
index 9f981f4b..f9caaad9 100644
--- a/Core/Resgrid.Model/Records/RmsEvidenceArtifact.cs
+++ b/Core/Resgrid.Model/Records/RmsEvidenceArtifact.cs
@@ -30,7 +30,10 @@ public enum RmsEvidenceKind
InventoryUsage = 5,
/// Participant certification/qualification validity at the incident time.
- CertificationSnapshot = 6
+ CertificationSnapshot = 6,
+
+ /// An operational pack's module projection (RMS-1C): personnel check-in, resource summary, qualifications or command summary composed from the owning module with source identifiers.
+ ModuleProjection = 7
}
///
diff --git a/Core/Resgrid.Model/Records/RmsExportTemplate.cs b/Core/Resgrid.Model/Records/RmsExportTemplate.cs
index ebca6dc2..587666de 100644
--- a/Core/Resgrid.Model/Records/RmsExportTemplate.cs
+++ b/Core/Resgrid.Model/Records/RmsExportTemplate.cs
@@ -226,6 +226,8 @@ public enum RmsExportTrigger
{
Scheduled = 1,
Record = 2,
- Manual = 3
+ Manual = 3,
+ /// A bulk packet compiled over an authorized selection (RMS plan section 4.7).
+ Bulk = 4
}
}
diff --git a/Core/Resgrid.Model/Records/RmsExternalOrders.cs b/Core/Resgrid.Model/Records/RmsExternalOrders.cs
new file mode 100644
index 00000000..7efaaf9f
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsExternalOrders.cs
@@ -0,0 +1,277 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// Mutual-aid ordering profiles (RMS plan section 4.1 "external-order fill contract").
+ public static class RmsDeploymentProfiles
+ {
+ public const string Generic = "generic";
+ public const string UsWildland = "us-wildland";
+ public const string CaWildland = "ca-wildland";
+ public const string CrossBorder = "us-ca-crossborder";
+ public const string Compact = "emac-compact";
+ public const string LocalMutualAid = "local-mutual-aid";
+
+ public static readonly IReadOnlyList All = new[] { Generic, UsWildland, CaWildland, CrossBorder, Compact, LocalMutualAid };
+ public static bool IsKnown(string key) => key != null && All.Contains(key, StringComparer.OrdinalIgnoreCase);
+ }
+
+ /// The fill lifecycle a supplied resource walks (RMS plan section 4.1 steps 3-5). Return is decided by the department, never inferred from the external system.
+ public enum RmsDeploymentFillStatus
+ {
+ Requested = 1,
+ Accepted = 2,
+ Declined = 3,
+ Mobilized = 4,
+ CheckedIn = 5,
+ Assigned = 6,
+ Released = 7,
+ Demobilized = 8,
+ Returned = 9
+ }
+
+ public enum RmsExternalOrderStatus
+ {
+ Open = 1,
+ Mobilized = 2,
+ Released = 3,
+ ClosedOut = 4
+ }
+
+ ///
+ /// One external resource order/request the department is filling (registry M0163). The source system stays
+ /// authoritative: the order artifact is stored as an immutable, checksummed snapshot, later imports arrive as new
+ /// snapshots, and nothing here writes back to IROC, CIFFC or any member agency.
+ ///
+ public class RmsExternalOrder : IEntity
+ {
+ public string RmsExternalOrderId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ /// The deployment Record (pack.mutual-aid.deployment definition) this order rides on.
+ public string RecordId { get; set; }
+ public string ProfileKey { get; set; }
+ public int ProfileVersion { get; set; }
+ /// Cross-border deployments retain both sides (RMS plan section 4.1).
+ public string HomeProfileKey { get; set; }
+ public string HostProfileKey { get; set; }
+ /// Opaque identifier scheme of the ordering system (iroc, ciffc, agency:, local).
+ public string SourceScheme { get; set; }
+ public string SourceSystem { get; set; }
+ public string OrderNumber { get; set; }
+ public string IncidentName { get; set; }
+ public string IncidentNumber { get; set; }
+ public string IncidentCountry { get; set; }
+ public string IncidentSubdivision { get; set; }
+ public string OrderingOffice { get; set; }
+ public string DispatchOffice { get; set; }
+ public string RequestingAgency { get; set; }
+ public string ReceivingAgency { get; set; }
+ public string SendingAgency { get; set; }
+ /// filling | sending | both
+ public string DepartmentRole { get; set; }
+ public string CostCode { get; set; }
+ public string AgreementReference { get; set; }
+ public string CurrencyCode { get; set; }
+ public string MeasurementSystem { get; set; }
+ public string TimeZoneId { get; set; }
+ public int? CapturedOffsetMinutes { get; set; }
+ public DateTime? SourceCapturedOn { get; set; }
+ public string SourceVersion { get; set; }
+ public string ArtifactFileName { get; set; }
+ public string ArtifactContentType { get; set; }
+ public string ArtifactChecksum { get; set; }
+ public byte[] ArtifactData { get; set; }
+ public string ArtifactSafeUrl { get; set; }
+ public int Status { get; set; }
+ public DateTime? MobilizedOn { get; set; }
+ public DateTime? ReleasedOn { get; set; }
+ public DateTime? ClosedOutOn { get; set; }
+ public string ClosedOutByUserId { get; set; }
+ public string CloseoutNotes { get; set; }
+ public bool IsProtected { get; set; }
+ public int ProtectedCatalogVersion { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string CreatedByUserId { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string ModifiedByUserId { get; set; }
+ public long RowVersion { get; set; }
+ public DateTime? DeletedOn { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsExternalOrderId; }
+ set { RmsExternalOrderId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsExternalOrders";
+ [NotMapped] public string IdName => "RmsExternalOrderId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ /// One request/fill assignment on an external order: the supplied resource linked to its exact request number.
+ public class RmsExternalOrderFill : IEntity
+ {
+ public string RmsExternalOrderFillId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RmsExternalOrderId { get; set; }
+ public string RecordId { get; set; }
+ public string RequestNumber { get; set; }
+ public string ParentRequestNumber { get; set; }
+ /// overhead | crew | equipment | aircraft-support | supply | other
+ public string RequestCategory { get; set; }
+ public string FillNumber { get; set; }
+ public string ResourceKind { get; set; }
+ public string ResourceType { get; set; }
+ public string ResourceTypeScheme { get; set; }
+ public string Position { get; set; }
+ public string PositionScheme { get; set; }
+ public bool IsTrainee { get; set; }
+ public string HomeUnit { get; set; }
+ public string HostAgency { get; set; }
+ public string AgencyUnitId { get; set; }
+ public string PointOfHire { get; set; }
+ public string CostCode { get; set; }
+ public string AgreementReference { get; set; }
+ public string AssignedUserId { get; set; }
+ public int? AssignedUnitId { get; set; }
+ /// Qualifications asserted by the Certifications module at capture; a snapshot, never a declaration of equivalence.
+ public string QualificationsJson { get; set; }
+ public string RosterJson { get; set; }
+ public string TravelJson { get; set; }
+ public int Status { get; set; }
+ public string DeclineReason { get; set; }
+ public DateTime? RequestedOn { get; set; }
+ public DateTime? NeededOn { get; set; }
+ public DateTime? FilledOn { get; set; }
+ public DateTime? MobilizedOn { get; set; }
+ public DateTime? CheckedInOn { get; set; }
+ public DateTime? AssignedOn { get; set; }
+ public DateTime? ReleasedOn { get; set; }
+ public DateTime? DemobilizedOn { get; set; }
+ public DateTime? ReturnedOn { get; set; }
+ public int? CapturedOffsetMinutes { get; set; }
+ public string Notes { get; set; }
+ public bool IsProtected { get; set; }
+ public int ProtectedCatalogVersion { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string CreatedByUserId { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string ModifiedByUserId { get; set; }
+ public long RowVersion { get; set; }
+ public DateTime? DeletedOn { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsExternalOrderFillId; }
+ set { RmsExternalOrderFillId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsExternalOrderFills";
+ [NotMapped] public string IdName => "RmsExternalOrderFillId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ // ------------------------------------------------------------------------------------------------------
+ // Service contracts
+ // ------------------------------------------------------------------------------------------------------
+
+ public class RecordDeploymentCreateInput
+ {
+ public string ProfileKey { get; set; }
+ public string HomeProfileKey { get; set; }
+ public string HostProfileKey { get; set; }
+ public string SourceScheme { get; set; }
+ public string SourceSystem { get; set; }
+ public string OrderNumber { get; set; }
+ public string IncidentName { get; set; }
+ public string IncidentNumber { get; set; }
+ public string IncidentCountry { get; set; }
+ public string IncidentSubdivision { get; set; }
+ public string OrderingOffice { get; set; }
+ public string DispatchOffice { get; set; }
+ public string RequestingAgency { get; set; }
+ public string ReceivingAgency { get; set; }
+ public string SendingAgency { get; set; }
+ public string DepartmentRole { get; set; } = "filling";
+ public string CostCode { get; set; }
+ public string AgreementReference { get; set; }
+ public string CurrencyCode { get; set; }
+ public string MeasurementSystem { get; set; }
+ public string TimeZoneId { get; set; }
+ public int? CapturedOffsetMinutes { get; set; }
+ public DateTime? SourceCapturedOn { get; set; }
+ public string SourceVersion { get; set; }
+ public string ArtifactFileName { get; set; }
+ public string ArtifactContentType { get; set; }
+ public byte[] ArtifactData { get; set; }
+ public string ArtifactSafeUrl { get; set; }
+ public int? StationGroupId { get; set; }
+ public string IdempotencyKey { get; set; }
+ public RmsOriginClient OriginClient { get; set; } = RmsOriginClient.Web;
+ public List Fills { get; set; } = new List();
+ }
+
+ public class RecordDeploymentFillInput
+ {
+ public string RequestNumber { get; set; }
+ public string ParentRequestNumber { get; set; }
+ public string RequestCategory { get; set; }
+ public string FillNumber { get; set; }
+ public string ResourceKind { get; set; }
+ public string ResourceType { get; set; }
+ public string ResourceTypeScheme { get; set; }
+ public string Position { get; set; }
+ public string PositionScheme { get; set; }
+ public bool IsTrainee { get; set; }
+ public string HomeUnit { get; set; }
+ public string HostAgency { get; set; }
+ public string AgencyUnitId { get; set; }
+ public string PointOfHire { get; set; }
+ public string CostCode { get; set; }
+ public string AgreementReference { get; set; }
+ public string AssignedUserId { get; set; }
+ public int? AssignedUnitId { get; set; }
+ public DateTime? RequestedOn { get; set; }
+ public DateTime? NeededOn { get; set; }
+ public DateTime? FilledOn { get; set; }
+ public int? CapturedOffsetMinutes { get; set; }
+ public string Notes { get; set; }
+ }
+
+ /// A lifecycle step on one fill (accept/decline/mobilize/check-in/assign/release/demobilize/return).
+ public class RecordDeploymentFillTransitionInput
+ {
+ public RmsDeploymentFillStatus Status { get; set; }
+ public DateTime? OccurredOn { get; set; }
+ public int? CapturedOffsetMinutes { get; set; }
+ public string Reason { get; set; }
+ public string Notes { get; set; }
+ public string RosterJson { get; set; }
+ public string TravelJson { get; set; }
+ }
+
+ public class RecordDeploymentAggregate
+ {
+ public RmsExternalOrder Order { get; set; }
+ public List Fills { get; set; } = new List();
+ public RecordAggregate Record { get; set; }
+ public RmsJurisdictionProfileVersion Profile { get; set; }
+ public RmsJurisdictionProfileVersion HomeProfile { get; set; }
+ public RmsJurisdictionProfileVersion HostProfile { get; set; }
+ public bool IsPreview => true;
+ /// A deployment is returned only when every accepted fill reached Returned; the external release flag alone never closes it.
+ public bool AllReturned => Fills.Where(f => f.Status != (int)RmsDeploymentFillStatus.Declined).All(f => f.Status == (int)RmsDeploymentFillStatus.Returned) && Fills.Any();
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RmsIncidentReport.cs b/Core/Resgrid.Model/Records/RmsIncidentReport.cs
index 172aed8c..2752175d 100644
--- a/Core/Resgrid.Model/Records/RmsIncidentReport.cs
+++ b/Core/Resgrid.Model/Records/RmsIncidentReport.cs
@@ -432,6 +432,10 @@ public class RmsLocation : IEntity
public string ProtectedLongitudeEnvelope { get; set; }
public string Jurisdiction { get; set; }
public int SourceKind { get; set; }
+ /// ADP marker (catalog v10, M0176). RmsLocations is bound with ProtectedMarkerColumn "IsProtected",
+ /// so the catalog-upgrade sweep reads these two columns to tell a sealed row from an unenrolled one.
+ public bool IsProtected { get; set; }
+ public int ProtectedCatalogVersion { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public long RowVersion { get; set; }
diff --git a/Core/Resgrid.Model/Records/RmsProtectedFields.cs b/Core/Resgrid.Model/Records/RmsProtectedFields.cs
index 6f52a701..59eeb0f8 100644
--- a/Core/Resgrid.Model/Records/RmsProtectedFields.cs
+++ b/Core/Resgrid.Model/Records/RmsProtectedFields.cs
@@ -168,6 +168,23 @@ private static string TableOf() where T : IEntity
public static readonly string AttachmentDataFieldId = FieldId("RmsRecordAttachments", "Data");
+ ///
+ /// Typed values of department definitions (catalog v11): one virtual field per row. The accessor packs the
+ /// typed sibling columns to seal and unpacks them to reveal, so the seam's generic text path needs nothing new.
+ /// A REDACTED sentinel leaves a sealed row exactly as stored (its siblings are already null), which is what
+ /// RecordTypedValuesService.Shape renders as the withheld cell.
+ ///
+ public static readonly IReadOnlyDictionary Get, Action Set)> Values = Map(
+ ("ProtectedEnvelope",
+ v => !string.IsNullOrEmpty(v.ProtectedEnvelope) ? v.ProtectedEnvelope : (v.ProtectionRequired ? RmsRecordValuePack.Pack(v) : null),
+ (v, text) =>
+ {
+ if (ProtectedDataEnvelope.HasEnvelopePrefix(text)) { v.ProtectedEnvelope = text; RmsRecordValuePack.Clear(v); }
+ else if (text != null && text != ProtectedDataEnvelope.RedactionValue) { RmsRecordValuePack.Unpack(v, text); v.ProtectedEnvelope = null; }
+ }));
+
+ public static readonly string ValueFieldId = FieldId("RmsRecordValues", "ProtectedEnvelope");
+
/// The rendered export artifact (RmsExportRuns.Data) is a generated copy of record content.
public static readonly string ExportRunDataFieldId = FieldId("RmsExportRuns", "Data");
@@ -196,6 +213,7 @@ public static IEnumerable AllFieldIds()
foreach (var k in Attachments.Keys) yield return k;
yield return AttachmentDataFieldId;
yield return ExportRunDataFieldId;
+ foreach (var k in Values.Keys) yield return k;
}
}
}
diff --git a/Core/Resgrid.Model/Records/RmsRecordDefinitions.cs b/Core/Resgrid.Model/Records/RmsRecordDefinitions.cs
new file mode 100644
index 00000000..eab6a534
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsRecordDefinitions.cs
@@ -0,0 +1,659 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// Who owns a Record definition (RMS plan section 4.1): locked product content or a department clone/draft.
+ public enum RmsDefinitionOwner
+ {
+ System = 1,
+ Department = 2
+ }
+
+ /// Definition version lifecycle: Draft -> Published -> Retired. Only an unused draft can be deleted.
+ public enum RmsDefinitionVersionState
+ {
+ Draft = 1,
+ Published = 2,
+ Retired = 3
+ }
+
+ ///
+ /// The controlled field catalog (RMS plan section 4.1). 1-18 ship in RMS-1B (the launch templates need them);
+ /// 19-24 are the RMS-1C additions the operational packs need. Values are stable and pinned by the client contract.
+ ///
+ public enum RmsFieldType
+ {
+ ShortText = 1,
+ LongText = 2,
+ Integer = 3,
+ Decimal = 4,
+ Boolean = 5,
+ Date = 6,
+ DateTime = 7,
+ Duration = 8,
+ SingleSelect = 9,
+ MultiSelect = 10,
+ Address = 11,
+ Person = 12,
+ Unit = 13,
+ Group = 14,
+ Contact = 15,
+ Attachment = 16,
+ Signature = 17,
+ ExternalReference = 18,
+ // RMS-1C
+ Currency = 19,
+ Quantity = 20,
+ CountrySubdivision = 21,
+ CallReference = 22,
+ InventoryReference = 23,
+ ChecklistWorkOrderReference = 24
+ }
+
+ /// Field classification (RMS plan section 5.9.2). Restricted gates on RecordRestricted_View; Protected is sealed under ADP once cataloged.
+ public enum RmsFieldClassification
+ {
+ Standard = 0,
+ Restricted = 1,
+ Protected = 2
+ }
+
+ /// Bounded rule operators (RMS plan section 4.1). Nothing here produces a value; rules only control visibility and requiredness.
+ public enum RmsRuleOperator
+ {
+ Equals = 1,
+ NotEquals = 2,
+ InSet = 3,
+ NotInSet = 4,
+ IsEmpty = 5,
+ IsNotEmpty = 6,
+ InRange = 7,
+ And = 20,
+ Or = 21
+ }
+
+ public enum RmsRuleEffect
+ {
+ /// The section/field is shown only while the condition holds.
+ Show = 1,
+ /// The field is required (at finalize) while the condition holds.
+ Require = 2
+ }
+
+ /// Stable definition identity (RMS plan section 5.2, registry M0158). One row per department definition key.
+ public class RmsRecordDefinition : IEntity
+ {
+ public string RmsRecordDefinitionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ /// Stable key; department keys never use the reserved "system." prefix.
+ public string DefinitionKey { get; set; }
+ public int Owner { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ /// Product template this definition was cloned from, when any (template lineage never mutates the clone).
+ public string TemplateKey { get; set; }
+ public int? TemplatePackVersion { get; set; }
+ /// Locked jurisdiction overlay applied at clone time (RMS-1C); null for the generic base.
+ public string JurisdictionProfileKey { get; set; }
+ /// Comma-separated subject/reference types a Record on this definition may link (call, unit, contact, person, checklist, workorder).
+ public string PermittedSubjectTypes { get; set; }
+ /// The version new Records start on; null until first publish.
+ public int? CurrentPublishedVersion { get; set; }
+ public int LatestVersion { get; set; }
+ public bool IsRetired { get; set; }
+ public DateTime? RetiredOn { get; set; }
+ public string RetiredByUserId { get; set; }
+ public string RetiredReason { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string CreatedByUserId { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string ModifiedByUserId { get; set; }
+ public long RowVersion { get; set; }
+ public DateTime? DeletedOn { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordDefinitionId; }
+ set { RmsRecordDefinitionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordDefinitions";
+ [NotMapped] public string IdName => "RmsRecordDefinitionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ ///
+ /// One immutable-once-published schema version (RMS plan section 5.2). The authored document lives in SchemaJson;
+ /// publishing freezes it, computes its checksum and capability floor, and materializes the section/field rows.
+ ///
+ public class RmsRecordDefinitionVersion : IEntity
+ {
+ public string RmsRecordDefinitionVersionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RmsRecordDefinitionId { get; set; }
+ public string DefinitionKey { get; set; }
+ public int Version { get; set; }
+ public int State { get; set; }
+ public int LifecyclePreset { get; set; }
+ /// Comma-separated PersonnelRole ids that narrow Record_Review for this definition; empty = anyone holding it.
+ public string ReviewerRoleIds { get; set; }
+ /// Comma-separated PersonnelRole ids that narrow Record_Approve; empty = anyone holding it.
+ public string ApproverRoleIds { get; set; }
+ public int? ReviewDueHours { get; set; }
+ public int? ApproveDueHours { get; set; }
+ public bool RequireAuthorAttestation { get; set; }
+ /// Serialized .
+ public string NumberingJson { get; set; }
+ /// Retention years for Records on this version; null = class default; 0 = permanent.
+ public int? RetentionYears { get; set; }
+ /// Whole-definition classification floor (RmsFieldClassification); a field can be stricter, never looser.
+ public int Classification { get; set; }
+ /// Serialized .
+ public string SchemaJson { get; set; }
+ public string SchemaChecksum { get; set; }
+ /// Derived at publish from the field and rule types actually used (RMS plan section 5.4).
+ public string MinimumClientCapability { get; set; }
+ /// Serialized : which field apps may author on this version.
+ public string ClientSurfaceJson { get; set; }
+ /// Explicit field mapping from the previous published version (RecordDefinitionFieldMapping list), for draft migration.
+ public string MigrationMapJson { get; set; }
+ public string ChangeNotes { get; set; }
+ public DateTime? PublishedOn { get; set; }
+ public string PublishedByUserId { get; set; }
+ public DateTime? RetiredOn { get; set; }
+ public string RetiredByUserId { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string CreatedByUserId { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string ModifiedByUserId { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordDefinitionVersionId; }
+ set { RmsRecordDefinitionVersionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordDefinitionVersions";
+ [NotMapped] public string IdName => "RmsRecordDefinitionVersionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName", "Schema", "Numbering", "ClientSurface" };
+
+ private RecordDefinitionSchema _schema;
+ [NotMapped]
+ [JsonIgnore]
+ public RecordDefinitionSchema Schema
+ {
+ get { return _schema ??= RecordDefinitionSchema.Parse(SchemaJson); }
+ set { _schema = value; SchemaJson = value == null ? null : RecordDefinitionSchema.Serialize(value); }
+ }
+
+ [NotMapped]
+ [JsonIgnore]
+ public RecordDefinitionNumbering Numbering
+ {
+ get { return RecordDefinitionNumbering.Parse(NumberingJson); }
+ set { NumberingJson = value == null ? null : JsonConvert.SerializeObject(value); }
+ }
+
+ [NotMapped]
+ [JsonIgnore]
+ public RecordDefinitionClientSurface ClientSurface
+ {
+ get { return RecordDefinitionClientSurface.Parse(ClientSurfaceJson); }
+ set { ClientSurfaceJson = value == null ? null : JsonConvert.SerializeObject(value); }
+ }
+
+ public bool IsPublished => State == (int)RmsDefinitionVersionState.Published;
+ public bool IsDraft => State == (int)RmsDefinitionVersionState.Draft;
+ }
+
+ /// Materialized at publish (RMS plan section 5.2): ordered section identity for one published version.
+ public class RmsRecordSectionDefinition : IEntity
+ {
+ public string RmsRecordSectionDefinitionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RmsRecordDefinitionVersionId { get; set; }
+ public string DefinitionKey { get; set; }
+ public int DefinitionVersion { get; set; }
+ public string SectionKey { get; set; }
+ public string Label { get; set; }
+ public string Help { get; set; }
+ public int Ordinal { get; set; }
+ public bool IsRepeating { get; set; }
+ public int? MinRows { get; set; }
+ public int? MaxRows { get; set; }
+ public string RulesJson { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordSectionDefinitionId; }
+ set { RmsRecordSectionDefinitionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordSectionDefinitions";
+ [NotMapped] public string IdName => "RmsRecordSectionDefinitionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ /// Materialized at publish: stable field identity, type, classification and capability flags for one published version.
+ public class RmsRecordFieldDefinition : IEntity
+ {
+ public string RmsRecordFieldDefinitionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RmsRecordDefinitionVersionId { get; set; }
+ public string DefinitionKey { get; set; }
+ public int DefinitionVersion { get; set; }
+ public string SectionKey { get; set; }
+ public string FieldKey { get; set; }
+ public string Label { get; set; }
+ public int DataType { get; set; }
+ public int Ordinal { get; set; }
+ public bool Required { get; set; }
+ public bool RequiredToFinalize { get; set; }
+ public int Classification { get; set; }
+ public string ReferenceType { get; set; }
+ public bool Searchable { get; set; }
+ public bool Filterable { get; set; }
+ public bool Sortable { get; set; }
+ public bool Groupable { get; set; }
+ public bool Aggregatable { get; set; }
+ public bool WorkflowExposed { get; set; }
+ public bool Exportable { get; set; }
+ public string ConstraintsJson { get; set; }
+ public string RulesJson { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordFieldDefinitionId; }
+ set { RmsRecordFieldDefinitionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordFieldDefinitions";
+ [NotMapped] public string IdName => "RmsRecordFieldDefinitionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ // ------------------------------------------------------------------------------------------------------
+ // Authored schema document (what the designer edits and what a published version freezes)
+ // ------------------------------------------------------------------------------------------------------
+
+ public class RecordDefinitionSchema
+ {
+ public const int CurrentSchemaVersion = 1;
+ public const int MaxSections = 40;
+ public const int MaxFieldsPerSection = 60;
+ public const int MaxOptions = 200;
+ public const int MaxRuleDepth = 6;
+
+ public int SchemaVersion { get; set; } = CurrentSchemaVersion;
+ public List Sections { get; set; } = new List();
+
+ private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None };
+
+ public static RecordDefinitionSchema Parse(string json) => string.IsNullOrWhiteSpace(json) ? new RecordDefinitionSchema() : JsonConvert.DeserializeObject(json, Settings) ?? new RecordDefinitionSchema();
+ public static string Serialize(RecordDefinitionSchema schema) => JsonConvert.SerializeObject(schema ?? new RecordDefinitionSchema(), Settings);
+
+ /// Canonical form: sections and fields ordered, keys trimmed and lower-cased. Checksums are taken over this.
+ public string Canonical()
+ {
+ var clone = JsonConvert.DeserializeObject(Serialize(this), Settings);
+ foreach (var section in clone.Sections)
+ {
+ section.Key = RecordDefinitionKeys.NormalizeKey(section.Key);
+ foreach (var field in section.Fields)
+ field.Key = RecordDefinitionKeys.NormalizeKey(field.Key);
+ }
+ return JsonConvert.SerializeObject(clone, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None, ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver { NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy() } });
+ }
+
+ public IEnumerable AllFields() => Sections.SelectMany(s => s.Fields);
+
+ public RecordFieldSchema FindField(string fieldKey) => AllFields().FirstOrDefault(f => string.Equals(f.Key, fieldKey, StringComparison.OrdinalIgnoreCase));
+
+ public RecordSectionSchema FindSection(string sectionKey) => Sections.FirstOrDefault(s => string.Equals(s.Key, sectionKey, StringComparison.OrdinalIgnoreCase));
+
+ public RecordSectionSchema SectionOf(string fieldKey) => Sections.FirstOrDefault(s => s.Fields.Any(f => string.Equals(f.Key, fieldKey, StringComparison.OrdinalIgnoreCase)));
+ }
+
+ public class RecordSectionSchema
+ {
+ public string Key { get; set; }
+ public string Label { get; set; }
+ public string Help { get; set; }
+ public bool Repeating { get; set; }
+ public int? MinRows { get; set; }
+ public int? MaxRows { get; set; }
+ /// Visibility rules (Show) over fields of the same version.
+ public List Rules { get; set; } = new List();
+ public List Fields { get; set; } = new List();
+ }
+
+ public class RecordFieldSchema
+ {
+ public string Key { get; set; }
+ public string Label { get; set; }
+ public string Help { get; set; }
+ public RmsFieldType Type { get; set; }
+ /// Required whenever visible (checked at finalize; drafts may be incomplete).
+ public bool Required { get; set; }
+ public bool RequiredToFinalize { get; set; }
+ public RmsFieldClassification Classification { get; set; }
+ public List Options { get; set; } = new List();
+ public decimal? Min { get; set; }
+ public decimal? Max { get; set; }
+ public int? MaxLength { get; set; }
+ /// Reference/attachment target (user, unit, group, contact, attachment, call, inventory-item, checklist, workorder) or the external scheme.
+ public string ReferenceType { get; set; }
+ /// Quantity fields: the unit family (length, volume, area, temperature, mass) that bounds the accepted units.
+ public string UnitFamily { get; set; }
+ /// Quantity fields: the unit the value is canonicalized to for comparison; Decimal fields: a display-only unit label.
+ public string DefaultUnit { get; set; }
+ public string FixedUnitLabel { get; set; }
+ public string DefaultCurrency { get; set; }
+ public bool Searchable { get; set; }
+ public bool Filterable { get; set; }
+ public bool Sortable { get; set; }
+ public bool Groupable { get; set; }
+ public bool Aggregatable { get; set; }
+ public bool WorkflowExposed { get; set; }
+ public bool Exportable { get; set; } = true;
+ public List Rules { get; set; } = new List();
+ }
+
+ public class RecordOptionSchema
+ {
+ public string Key { get; set; }
+ public string Label { get; set; }
+ /// Locale overrides supplied by a jurisdiction overlay (RMS-1C): "fr-CA" -> label.
+ public Dictionary Labels { get; set; }
+ }
+
+ public class RecordRuleSchema
+ {
+ public RmsRuleEffect Effect { get; set; }
+ public RecordConditionSchema Condition { get; set; }
+ }
+
+ public class RecordConditionSchema
+ {
+ public RmsRuleOperator Operator { get; set; }
+ public string FieldKey { get; set; }
+ public string Value { get; set; }
+ public List Values { get; set; }
+ public decimal? Min { get; set; }
+ public decimal? Max { get; set; }
+ public DateTime? MinDate { get; set; }
+ public DateTime? MaxDate { get; set; }
+ public List Conditions { get; set; }
+
+ public IEnumerable ReferencedFieldKeys()
+ {
+ if (!string.IsNullOrWhiteSpace(FieldKey))
+ yield return FieldKey;
+ foreach (var child in Conditions ?? new List())
+ foreach (var key in child.ReferencedFieldKeys())
+ yield return key;
+ }
+ }
+
+ /// Definition-specific numbering (RMS plan section 4.1 "Numbering"). Server-enforced; clients never invent a number.
+ public class RecordDefinitionNumbering
+ {
+ public string Prefix { get; set; }
+ public RmsNumberAssignment Assignment { get; set; } = RmsNumberAssignment.OnFinalize;
+ public bool PerGroupSequence { get; set; }
+ public bool ResetYearly { get; set; } = true;
+ public int SequenceWidth { get; set; } = 4;
+
+ public static RecordDefinitionNumbering Parse(string json) => string.IsNullOrWhiteSpace(json) ? new RecordDefinitionNumbering() : JsonConvert.DeserializeObject(json) ?? new RecordDefinitionNumbering();
+ }
+
+ /// Per-version eligibility for the field apps (RMS plan section 5.2 RmsDefinitionClientSurface), kept as a document on the version.
+ public class RecordDefinitionClientSurface
+ {
+ public bool Responder { get; set; }
+ public bool Unit { get; set; }
+ public bool IncidentCommand { get; set; }
+ public bool Dispatch { get; set; }
+ /// Launch contexts: call, unit, contact, checklist, workorder, none.
+ public List LaunchContexts { get; set; } = new List();
+ public bool AllowOffline { get; set; }
+ public bool AllowAttachments { get; set; } = true;
+ public string MinimumAppVersion { get; set; }
+
+ public static RecordDefinitionClientSurface Parse(string json) => string.IsNullOrWhiteSpace(json) ? new RecordDefinitionClientSurface() : JsonConvert.DeserializeObject(json) ?? new RecordDefinitionClientSurface();
+ }
+
+ public class RecordDefinitionFieldMapping
+ {
+ public string FromFieldKey { get; set; }
+ public string ToFieldKey { get; set; }
+ }
+
+ /// Key rules shared by the designer, the packs and the value seam.
+ public static class RecordDefinitionKeys
+ {
+ public const int MaxKeyLength = 64;
+ public const string TemplatePrefix = "template.";
+ public const string PackPrefix = "pack.";
+
+ public static string NormalizeKey(string key) => (key ?? string.Empty).Trim().ToLowerInvariant();
+
+ /// Lower-case letters, digits, dots and dashes; must start with a letter; no reserved prefix.
+ public static bool IsValidDefinitionKey(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key) || key.Length > MaxKeyLength) return false;
+ if (RmsDefinitionKeys.IsSystemKey(key)) return false;
+ if (!char.IsLetter(key[0])) return false;
+ return key.All(c => char.IsLetterOrDigit(c) && !char.IsUpper(c) || c == '.' || c == '-' || c == '_');
+ }
+
+ /// Section/field keys: letters, digits, underscore and dash; start with a letter.
+ public static bool IsValidMemberKey(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key) || key.Length > MaxKeyLength) return false;
+ if (!char.IsLetter(key[0])) return false;
+ return key.All(c => char.IsLetterOrDigit(c) && !char.IsUpper(c) || c == '_' || c == '-');
+ }
+
+ public static bool IsTemplateKey(string key) => key != null && (key.StartsWith(TemplatePrefix, StringComparison.Ordinal) || key.StartsWith(PackPrefix, StringComparison.Ordinal));
+ }
+
+ /// Client capability floors (RMS plan section 5.4). Derived from the types a version uses, never hand-entered.
+ public static class RecordsClientCapabilities
+ {
+ /// Locked Logs-parity definitions only.
+ public const string Locked = "records.v1";
+ /// RMS-1B controlled catalog: the 18 launch field types, repeating groups and the bounded rules.
+ public const string Configurable = "records.v1b";
+ /// RMS-1C additions: currency, measured quantity, country/subdivision, Call/Inventory/Checklist references.
+ public const string Packs = "records.v1c";
+
+ private static readonly string[] Order = { Locked, Configurable, Packs };
+
+ public static int Rank(string capability)
+ {
+ var index = Array.IndexOf(Order, capability ?? string.Empty);
+ return index < 0 ? -1 : index;
+ }
+
+ /// True when a client reporting can author on a version needing .
+ public static bool Satisfies(string clientCapability, string required) => Rank(clientCapability) >= Rank(required) && Rank(required) >= 0;
+
+ public static bool IsPackType(RmsFieldType type) => (int)type >= (int)RmsFieldType.Currency;
+
+ public static string Derive(RecordDefinitionSchema schema)
+ {
+ if (schema == null) return Configurable;
+ return schema.AllFields().Any(f => IsPackType(f.Type)) ? Packs : Configurable;
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------------------
+ // Service contracts
+ // ------------------------------------------------------------------------------------------------------
+
+ public class RecordDefinitionIssue
+ {
+ public string Severity { get; set; } = "error";
+ public string Path { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+
+ public static RecordDefinitionIssue Error(string path, string code, string message) => new RecordDefinitionIssue { Severity = "error", Path = path, Code = code, Message = message };
+ public static RecordDefinitionIssue Warning(string path, string code, string message) => new RecordDefinitionIssue { Severity = "warning", Path = path, Code = code, Message = message };
+ }
+
+ public class RecordDefinitionValidation
+ {
+ public List Issues { get; set; } = new List();
+ public string MinimumClientCapability { get; set; }
+ public bool IsValid => Issues.All(i => i.Severity != "error");
+ }
+
+ public class RecordDefinitionDraftInput
+ {
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ public string PermittedSubjectTypes { get; set; }
+ public RmsLifecyclePreset LifecyclePreset { get; set; } = RmsLifecyclePreset.QuickEntry;
+ public List ReviewerRoleIds { get; set; } = new List();
+ public List ApproverRoleIds { get; set; } = new List();
+ public int? ReviewDueHours { get; set; }
+ public int? ApproveDueHours { get; set; }
+ public bool RequireAuthorAttestation { get; set; }
+ public RecordDefinitionNumbering Numbering { get; set; } = new RecordDefinitionNumbering();
+ public int? RetentionYears { get; set; }
+ public RmsFieldClassification Classification { get; set; }
+ public RecordDefinitionSchema Schema { get; set; } = new RecordDefinitionSchema();
+ public RecordDefinitionClientSurface ClientSurface { get; set; } = new RecordDefinitionClientSurface();
+ public List MigrationMap { get; set; } = new List();
+ public string ChangeNotes { get; set; }
+ }
+
+ public class RecordDefinitionCreateInput
+ {
+ public string DefinitionKey { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ /// Product template/pack definition key to clone (template.* or pack.*); exclusive with CloneFromDefinitionKey.
+ public string TemplateKey { get; set; }
+ /// Existing department definition to clone as a new definition.
+ public string CloneFromDefinitionKey { get; set; }
+ /// Jurisdiction overlay to apply when cloning a pack (RMS-1C): generic, us, ca.
+ public string JurisdictionProfileKey { get; set; }
+ /// Locale for overlay labels (en-US, en-CA, fr-CA).
+ public string Locale { get; set; }
+ }
+
+ /// One department definition with its versions, for lists and the designer.
+ public class RecordDefinitionAggregate
+ {
+ public RmsRecordDefinition Definition { get; set; }
+ public List Versions { get; set; } = new List();
+ public RmsRecordDefinitionVersion Published => Versions.FirstOrDefault(v => v.IsPublished && Definition?.CurrentPublishedVersion == v.Version) ?? Versions.Where(v => v.IsPublished).OrderByDescending(v => v.Version).FirstOrDefault();
+ public RmsRecordDefinitionVersion Draft => Versions.Where(v => v.IsDraft).OrderByDescending(v => v.Version).FirstOrDefault();
+ public RmsRecordDefinitionVersion Latest => Versions.OrderByDescending(v => v.Version).FirstOrDefault();
+ }
+
+ public class RecordDefinitionImpactPreview
+ {
+ public string DefinitionKey { get; set; }
+ public int Version { get; set; }
+ public string MinimumClientCapability { get; set; }
+ public List FieldTypesUsed { get; set; } = new List();
+ public bool UsesRepeatingGroups { get; set; }
+ public int? CurrentPublishedVersion { get; set; }
+ /// Draft Records on the currently published version that a mapping could migrate.
+ public int OpenDraftsOnCurrentVersion { get; set; }
+ /// Finalized Records on earlier versions; they never migrate and keep rendering against their version.
+ public int FinalizedRecordsOnEarlierVersions { get; set; }
+ /// Field apps enabled for this department that cannot render this version (from the client surface and capability floor).
+ public List Clients { get; set; } = new List();
+ public List Issues { get; set; } = new List();
+ public bool BreakingChange { get; set; }
+ }
+
+ public class RecordDefinitionClientImpact
+ {
+ public string App { get; set; }
+ public bool Enabled { get; set; }
+ public bool EligibleOnSurface { get; set; }
+ public string RequiredCapability { get; set; }
+ /// Active clients of this app below the floor; null when the platform has no telemetry for it.
+ public int? ClientsBelowFloor { get; set; }
+ public string Message { get; set; }
+ }
+
+ public class RecordDefinitionDiff
+ {
+ public string DefinitionKey { get; set; }
+ public int FromVersion { get; set; }
+ public int ToVersion { get; set; }
+ public List Entries { get; set; } = new List();
+ public bool Breaking => Entries.Any(e => e.Breaking);
+ }
+
+ public class RecordDefinitionDiffEntry
+ {
+ public string Kind { get; set; } // section | field | policy
+ public string Change { get; set; } // added | removed | changed
+ public string Key { get; set; }
+ public string Detail { get; set; }
+ public bool Breaking { get; set; }
+ }
+
+ public class RecordDefinitionMigrationResult
+ {
+ public int Migrated { get; set; }
+ public int Skipped { get; set; }
+ public List SkippedRecordIds { get; set; } = new List();
+ public List UnmappedFieldKeys { get; set; } = new List();
+ }
+
+ /// What the client contract advertises for one definition (locked or department), RMS plan section 5.4.
+ public class RecordDefinitionSummary
+ {
+ public string Key { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Owner { get; set; }
+ public bool Locked { get; set; }
+ public int? PublishedVersion { get; set; }
+ public int? DraftVersion { get; set; }
+ public bool Retired { get; set; }
+ public string LifecyclePreset { get; set; }
+ public string MinimumClientCapability { get; set; }
+ public string TemplateKey { get; set; }
+ public string JurisdictionProfileKey { get; set; }
+ public string ArtifactStatus { get; set; }
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RmsRecordPrintLayout.cs b/Core/Resgrid.Model/Records/RmsRecordPrintLayout.cs
index 9237d40d..23a6004a 100644
--- a/Core/Resgrid.Model/Records/RmsRecordPrintLayout.cs
+++ b/Core/Resgrid.Model/Records/RmsRecordPrintLayout.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -45,6 +46,64 @@ public static string NormalizePageSize(string value)
/// Versioned print layout row (migration M0160). Only the DepartmentDefault scope is written in RMS-1.
[Table("RmsRecordPrintLayouts")]
+ ///
+ /// The Definition-scope print layout (RMS plan section 4.10.1): presentation over a department definition's approved
+ /// sections and fields — order, visibility, headings, page breaks, signature-block placement, attachment-list style
+ /// and an optional branding-block override. Presentation only: a hidden-by-layout field is still exported by data
+ /// exports per its flags, and no layout bypasses restricted/protected/group rules or the provenance footer.
+ ///
+ public class RecordsDefinitionLayoutConfig
+ {
+ public const string SignatureAtEnd = "end";
+ public const string SignatureInline = "inline";
+ public const string SignatureNone = "none";
+ public static readonly string[] SignaturePlacements = { SignatureAtEnd, SignatureInline, SignatureNone };
+
+ public const string AttachmentsTable = "table";
+ public const string AttachmentsList = "list";
+ public const string AttachmentsNone = "none";
+ public static readonly string[] AttachmentStyles = { AttachmentsTable, AttachmentsList, AttachmentsNone };
+
+ /// Null applies to every version of the definition; otherwise only Records pinned to this version use it.
+ public int? AppliesToVersion { get; set; }
+ public List SectionOrder { get; set; } = new List();
+ public List HiddenSectionKeys { get; set; } = new List();
+ public List HiddenFieldKeys { get; set; } = new List();
+ public Dictionary SectionHeadings { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ public List PageBreakBeforeSectionKeys { get; set; } = new List();
+ public string SignatureBlockPlacement { get; set; } = SignatureAtEnd;
+ public string AttachmentListStyle { get; set; } = AttachmentsTable;
+ /// Null keeps the department default branding block; otherwise these values replace it for this definition.
+ public RecordsPrintLayoutConfig BrandingOverrides { get; set; }
+
+ public static RecordsDefinitionLayoutConfig Default() => new RecordsDefinitionLayoutConfig();
+
+ public bool AppliesTo(int definitionVersion) => !AppliesToVersion.HasValue || AppliesToVersion.Value == definitionVersion;
+ public bool IsSectionVisible(string sectionKey) => !HiddenSectionKeys.Contains(sectionKey ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ public bool IsFieldVisible(string fieldKey) => !HiddenFieldKeys.Contains(fieldKey ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ public bool PageBreakBefore(string sectionKey) => PageBreakBeforeSectionKeys.Contains(sectionKey ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ public string HeadingFor(string sectionKey, string fallback) => SectionHeadings.TryGetValue(sectionKey ?? string.Empty, out var heading) && !string.IsNullOrWhiteSpace(heading) ? heading : fallback;
+
+ /// Section keys in layout order: listed keys first in their order, then any the layout does not mention, hidden ones dropped.
+ public List OrderedSectionKeys(IEnumerable schemaSectionKeys)
+ {
+ var all = (schemaSectionKeys ?? Enumerable.Empty()).ToList();
+ var ordered = SectionOrder.Where(k => all.Contains(k, StringComparer.OrdinalIgnoreCase)).ToList();
+ ordered.AddRange(all.Where(k => !ordered.Contains(k, StringComparer.OrdinalIgnoreCase)));
+ return ordered.Where(IsSectionVisible).ToList();
+ }
+ }
+
+ /// What print resolves for one Record: the branding block, the definition layout (if any) and the composite layout version stamped on the footer.
+ public class RecordsResolvedPrintLayout
+ {
+ public RecordsPrintLayoutConfig Branding { get; set; } = RecordsPrintLayoutConfig.Default();
+ public string BrandingLayoutVersion { get; set; } = RmsRecordPrintLayout.GeneratedLayoutVersion;
+ public RecordsDefinitionLayoutConfig Definition { get; set; }
+ public string DefinitionLayoutVersion { get; set; }
+ public string LayoutVersion => Definition == null ? BrandingLayoutVersion : DefinitionLayoutVersion + "+" + BrandingLayoutVersion;
+ }
+
public class RmsRecordPrintLayout : IEntity
{
public const string GeneratedLayoutVersion = "system-default/1";
@@ -85,6 +144,10 @@ public class RmsRecordPrintLayout : IEntity
[NotMapped]
public RecordsPrintLayoutConfig Config { get; set; }
+ /// Parsed Definition-scope config; null on a DepartmentDefault row.
+ [NotMapped]
+ public RecordsDefinitionLayoutConfig DefinitionConfig { get; set; }
+
[NotMapped]
public object IdValue
{
@@ -102,6 +165,6 @@ public object IdValue
public int IdType => 1;
[NotMapped]
- public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "LayoutVersion", "Config" };
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "LayoutVersion", "Config", "DefinitionConfig" };
}
}
diff --git a/Core/Resgrid.Model/Records/RmsRecordValues.cs b/Core/Resgrid.Model/Records/RmsRecordValues.cs
new file mode 100644
index 00000000..ce421b1f
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsRecordValues.cs
@@ -0,0 +1,499 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Globalization;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// Stable row identity for one repeating-section row of a draft or revision (RMS plan section 5.2, registry M0159).
+ public class RmsRecordValueGroup : IEntity
+ {
+ public string RmsRecordValueGroupId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RecordId { get; set; }
+ public int RecordKind { get; set; }
+ /// Null for the working draft; the revision id once copied into an immutable revision.
+ public string RevisionId { get; set; }
+ public string RmsRecordDefinitionVersionId { get; set; }
+ public string SectionKey { get; set; }
+ public int Ordinal { get; set; }
+ /// Client-supplied row key kept so autosave round-trips keep row identity across saves.
+ public string ClientRowKey { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordValueGroupId; }
+ set { RmsRecordValueGroupId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordValueGroups";
+ [NotMapped] public string IdName => "RmsRecordValueGroupId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ ///
+ /// One typed value (RMS plan section 5.3): one row per scalar, per repeating-group cell, per selected multi-select
+ /// option. Exactly one scalar/reference column group is populated per row; the service guard and the per-dialect
+ /// check constraint both enforce it. Locked system definitions never use this table.
+ ///
+ public class RmsRecordValue : IEntity
+ {
+ public string RmsRecordValueId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string RecordId { get; set; }
+ public int RecordKind { get; set; }
+ public string RevisionId { get; set; }
+ public string RmsRecordDefinitionVersionId { get; set; }
+ public string FieldKey { get; set; }
+ public string RmsRecordValueGroupId { get; set; }
+ public int Ordinal { get; set; }
+ /// Mirrors RmsRecordFieldDefinition.DataType; a mismatch against the pinned version is rejected.
+ public int ValueType { get; set; }
+ public string TextValue { get; set; }
+ public string LongTextValue { get; set; }
+ public decimal? NumberValue { get; set; }
+ public bool? BoolValue { get; set; }
+ public DateTime? DateTimeValue { get; set; }
+ public int? DateTimeOffsetMinutes { get; set; }
+ public long? DurationSeconds { get; set; }
+ public string UnitCode { get; set; }
+ public decimal? CanonicalNumberValue { get; set; }
+ public string CanonicalUnitCode { get; set; }
+ public string CurrencyCode { get; set; }
+ public string ReferenceType { get; set; }
+ public string ReferenceId { get; set; }
+ /// Bounded, server-authored display snapshot of a reference; never client-authored, never a second source of truth.
+ public string ReferenceSnapshotJson { get; set; }
+ public string OptionKey { get; set; }
+ public bool IsProtected { get; set; }
+ public string ProtectedEnvelope { get; set; }
+ public int ProtectedCatalogVersion { get; set; }
+ /// True when the field is Protected-classified in its definition version: the ADP seam seals this row (catalog v11) and the sweep targets it.
+ public bool ProtectionRequired { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsRecordValueId; }
+ set { RmsRecordValueId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsRecordValues";
+ [NotMapped] public string IdName => "RmsRecordValueId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+
+ /// Sealed under ADP (catalog v11): the envelope holds the packed typed columns and the siblings are null.
+ [NotMapped]
+ [JsonIgnore]
+ public bool IsSealed => ProtectedDataEnvelope.HasEnvelopePrefix(ProtectedEnvelope);
+
+ /// The service-layer half of the "exactly one column group" rule.
+ public int PopulatedColumnGroups()
+ {
+ var count = 0;
+ if (TextValue != null) count++;
+ if (LongTextValue != null) count++;
+ if (NumberValue.HasValue) count++;
+ if (BoolValue.HasValue) count++;
+ if (DateTimeValue.HasValue) count++;
+ if (DurationSeconds.HasValue) count++;
+ if (ReferenceId != null) count++;
+ if (OptionKey != null) count++;
+ return count;
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------------------
+ // Input and hydrated forms
+ // ------------------------------------------------------------------------------------------------------
+
+ /// One posted value. Everything is a string on the wire; the server parses against the pinned field type.
+ ///
+ /// The packed representation of a typed value row under ADP (catalog v11): the sibling typed columns as one JSON
+ /// document, sealed into RmsRecordValues.ProtectedEnvelope while the siblings are null. Shared by the write/read seam
+ /// (RmsProtectedFields.Values) and the migration engine's PackedJson column kind so both agree byte for byte.
+ ///
+ public static class RmsRecordValuePack
+ {
+ public static readonly IReadOnlyList CarrierColumns = new[]
+ {
+ "TextValue", "LongTextValue", "NumberValue", "BoolValue", "DateTimeValue", "DateTimeOffsetMinutes", "DurationSeconds",
+ "UnitCode", "CanonicalNumberValue", "CanonicalUnitCode", "CurrencyCode", "ReferenceType", "ReferenceId", "ReferenceSnapshotJson", "OptionKey"
+ };
+
+ public static IReadOnlyDictionary Columns(RmsRecordValue row) => new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ ["TextValue"] = row.TextValue, ["LongTextValue"] = row.LongTextValue, ["NumberValue"] = row.NumberValue, ["BoolValue"] = row.BoolValue,
+ ["DateTimeValue"] = row.DateTimeValue, ["DateTimeOffsetMinutes"] = row.DateTimeOffsetMinutes, ["DurationSeconds"] = row.DurationSeconds,
+ ["UnitCode"] = row.UnitCode, ["CanonicalNumberValue"] = row.CanonicalNumberValue, ["CanonicalUnitCode"] = row.CanonicalUnitCode,
+ ["CurrencyCode"] = row.CurrencyCode, ["ReferenceType"] = row.ReferenceType, ["ReferenceId"] = row.ReferenceId,
+ ["ReferenceSnapshotJson"] = row.ReferenceSnapshotJson, ["OptionKey"] = row.OptionKey
+ };
+
+ /// The row's typed columns as JSON (null when nothing is populated). Numbers and dates are strings in invariant form so scale and kind survive.
+ public static string Pack(RmsRecordValue row) => row == null ? null : PackColumns(Columns(row));
+
+ public static string PackColumns(IReadOnlyDictionary columns)
+ {
+ if (columns == null) return null;
+ var packed = new Dictionary(StringComparer.Ordinal);
+ foreach (var column in CarrierColumns)
+ {
+ var key = columns.Keys.FirstOrDefault(k => string.Equals(k, column, StringComparison.OrdinalIgnoreCase));
+ var value = key == null ? null : columns[key];
+ if (value == null || value is DBNull) continue;
+ packed[column] = value switch
+ {
+ decimal d => d.ToString(CultureInfo.InvariantCulture),
+ double d => ((decimal)d).ToString(CultureInfo.InvariantCulture),
+ float f => ((decimal)f).ToString(CultureInfo.InvariantCulture),
+ bool b => b ? "true" : "false",
+ DateTime dt => DateTime.SpecifyKind(dt, DateTimeKind.Utc).ToString("O", CultureInfo.InvariantCulture),
+ DateTimeOffset dto => dto.UtcDateTime.ToString("O", CultureInfo.InvariantCulture),
+ int i => i.ToString(CultureInfo.InvariantCulture),
+ long l => l.ToString(CultureInfo.InvariantCulture),
+ short sh => sh.ToString(CultureInfo.InvariantCulture),
+ _ => value.ToString()
+ };
+ }
+ return packed.Count == 0 ? null : JsonConvert.SerializeObject(packed);
+ }
+
+ /// Typed CLR values per carrier column (null for anything the pack does not carry), ready for an entity or a Dapper update.
+ public static Dictionary UnpackColumns(string json)
+ {
+ var packed = string.IsNullOrWhiteSpace(json) ? new Dictionary() : JsonConvert.DeserializeObject>(json) ?? new Dictionary();
+ var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var column in CarrierColumns)
+ {
+ packed.TryGetValue(column, out var text);
+ result[column] = text == null ? null : column switch
+ {
+ "NumberValue" or "CanonicalNumberValue" => decimal.Parse(text, NumberStyles.Number, CultureInfo.InvariantCulture),
+ "BoolValue" => (object)(text == "true"),
+ "DateTimeValue" => DateTime.SpecifyKind(DateTime.Parse(text, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal), DateTimeKind.Utc),
+ "DateTimeOffsetMinutes" => int.Parse(text, CultureInfo.InvariantCulture),
+ "DurationSeconds" => long.Parse(text, CultureInfo.InvariantCulture),
+ _ => text
+ };
+ }
+ return result;
+ }
+
+ public static void Unpack(RmsRecordValue row, string json)
+ {
+ if (row == null) return;
+ var columns = UnpackColumns(json);
+ row.TextValue = (string)columns["TextValue"]; row.LongTextValue = (string)columns["LongTextValue"];
+ row.NumberValue = (decimal?)columns["NumberValue"]; row.BoolValue = (bool?)columns["BoolValue"];
+ row.DateTimeValue = (DateTime?)columns["DateTimeValue"]; row.DateTimeOffsetMinutes = (int?)columns["DateTimeOffsetMinutes"];
+ row.DurationSeconds = (long?)columns["DurationSeconds"]; row.UnitCode = (string)columns["UnitCode"];
+ row.CanonicalNumberValue = (decimal?)columns["CanonicalNumberValue"]; row.CanonicalUnitCode = (string)columns["CanonicalUnitCode"];
+ row.CurrencyCode = (string)columns["CurrencyCode"]; row.ReferenceType = (string)columns["ReferenceType"]; row.ReferenceId = (string)columns["ReferenceId"];
+ row.ReferenceSnapshotJson = (string)columns["ReferenceSnapshotJson"]; row.OptionKey = (string)columns["OptionKey"];
+ }
+
+ public static void Clear(RmsRecordValue row)
+ {
+ if (row == null) return;
+ row.TextValue = null; row.LongTextValue = null; row.NumberValue = null; row.BoolValue = null; row.DateTimeValue = null; row.DateTimeOffsetMinutes = null;
+ row.DurationSeconds = null; row.UnitCode = null; row.CanonicalNumberValue = null; row.CanonicalUnitCode = null; row.CurrencyCode = null;
+ row.ReferenceType = null; row.ReferenceId = null; row.ReferenceSnapshotJson = null; row.OptionKey = null;
+ }
+ }
+
+ public class RecordValueInput
+ {
+ public string SectionKey { get; set; }
+ public string FieldKey { get; set; }
+ /// Repeating sections: the client's row key (stable across autosaves) and the row's ordinal.
+ public string RowKey { get; set; }
+ public int Ordinal { get; set; }
+ public string Value { get; set; }
+ /// Multi-select option keys.
+ public List Values { get; set; }
+ public string ReferenceType { get; set; }
+ public string ReferenceId { get; set; }
+ public string UnitCode { get; set; }
+ public string CurrencyCode { get; set; }
+ public int? OffsetMinutes { get; set; }
+ }
+
+ /// A hydrated cell: the stored row(s) for one field in one row, plus the display text rendered against the pinned schema.
+ public class RecordValueCell
+ {
+ public string SectionKey { get; set; }
+ public string FieldKey { get; set; }
+ public string Label { get; set; }
+ public RmsFieldType Type { get; set; }
+ public RmsFieldClassification Classification { get; set; }
+ public string GroupId { get; set; }
+ public string RowKey { get; set; }
+ public int Ordinal { get; set; }
+ /// Display text (never markup). Null when nothing is stored, "REDACTED" when withheld.
+ public string Display { get; set; }
+ /// Wire form the client can post back unchanged (RecordValueInput.Value).
+ public string Value { get; set; }
+ public List Values { get; set; }
+ public string ReferenceType { get; set; }
+ public string ReferenceId { get; set; }
+ public string UnitCode { get; set; }
+ public string CurrencyCode { get; set; }
+ public int? OffsetMinutes { get; set; }
+ public decimal? Number { get; set; }
+ public decimal? CanonicalNumber { get; set; }
+ public string CanonicalUnitCode { get; set; }
+ public bool Withheld { get; set; }
+ }
+
+ public class RecordValueRow
+ {
+ public string GroupId { get; set; }
+ public string RowKey { get; set; }
+ public int Ordinal { get; set; }
+ public List Cells { get; set; } = new List();
+ public RecordValueCell Cell(string fieldKey) => Cells.FirstOrDefault(c => string.Equals(c.FieldKey, fieldKey, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public class RecordValueSectionSet
+ {
+ public string SectionKey { get; set; }
+ public string Label { get; set; }
+ public bool Repeating { get; set; }
+ public List Rows { get; set; } = new List();
+ }
+
+ /// Everything stored for one Record (draft or revision), shaped by the pinned definition version.
+ public class RecordValueSet
+ {
+ public string DefinitionKey { get; set; }
+ public int DefinitionVersion { get; set; }
+ public string DefinitionVersionId { get; set; }
+ public List Sections { get; set; } = new List();
+ public List WithheldFieldKeys { get; set; } = new List();
+
+ public RecordValueCell Scalar(string fieldKey) => Sections.Where(s => !s.Repeating).SelectMany(s => s.Rows).SelectMany(r => r.Cells).FirstOrDefault(c => string.Equals(c.FieldKey, fieldKey, StringComparison.OrdinalIgnoreCase));
+ public RecordValueSectionSet Section(string sectionKey) => Sections.FirstOrDefault(s => string.Equals(s.SectionKey, sectionKey, StringComparison.OrdinalIgnoreCase));
+ public IEnumerable AllCells() => Sections.SelectMany(s => s.Rows).SelectMany(r => r.Cells);
+ public bool IsEmpty => !AllCells().Any(c => c.Display != null);
+
+ /// Round-trips the set as inputs (what a client posts back on the next save).
+ public List ToInputs()
+ {
+ var inputs = new List();
+ foreach (var section in Sections)
+ foreach (var row in section.Rows)
+ foreach (var cell in row.Cells.Where(c => !c.Withheld && (c.Value != null || c.Values != null || c.ReferenceId != null)))
+ inputs.Add(new RecordValueInput
+ {
+ SectionKey = section.SectionKey, FieldKey = cell.FieldKey, RowKey = section.Repeating ? row.RowKey ?? row.GroupId : null, Ordinal = row.Ordinal,
+ Value = cell.Value, Values = cell.Values, ReferenceType = cell.ReferenceType, ReferenceId = cell.ReferenceId, UnitCode = cell.UnitCode, CurrencyCode = cell.CurrencyCode, OffsetMinutes = cell.OffsetMinutes
+ });
+ return inputs;
+ }
+ }
+
+ public class RecordValueIssue
+ {
+ public string Severity { get; set; } = "error";
+ public string SectionKey { get; set; }
+ public string FieldKey { get; set; }
+ public string RowKey { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+ }
+
+ public class RecordValueValidation
+ {
+ public List Issues { get; set; } = new List();
+ public bool IsValid => Issues.All(i => i.Severity != "error");
+ }
+
+ /// The outcome of evaluating a version's rules over a value set: what is visible, what is required.
+ public class RecordRuleEvaluation
+ {
+ public HashSet HiddenSectionKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ /// Fields hidden regardless of row: scalar fields, and repeating-section fields whose rules only look at scalars.
+ public HashSet HiddenFieldKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ public HashSet RequiredFieldKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ /// Per-row outcomes for repeating sections (a rule may look at its own row); keyed "section|rowKey".
+ public Dictionary Rows { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public static string RowKey(string sectionKey, string rowKey) => (sectionKey ?? string.Empty) + "|" + (rowKey ?? string.Empty);
+
+ public RecordRowRuleEvaluation Row(string sectionKey, string rowKey)
+ {
+ var key = RowKey(sectionKey, rowKey);
+ if (!Rows.TryGetValue(key, out var row)) Rows[key] = row = new RecordRowRuleEvaluation { SectionKey = sectionKey, RowKey = rowKey };
+ return row;
+ }
+
+ public bool IsHidden(string sectionKey, string rowKey, string fieldKey) =>
+ HiddenSectionKeys.Contains(sectionKey ?? string.Empty) || HiddenFieldKeys.Contains(fieldKey)
+ || rowKey != null && Rows.TryGetValue(RowKey(sectionKey, rowKey), out var row) && row.HiddenFieldKeys.Contains(fieldKey);
+
+ public bool IsRequired(string sectionKey, string rowKey, string fieldKey) =>
+ !IsHidden(sectionKey, rowKey, fieldKey) && (RequiredFieldKeys.Contains(fieldKey)
+ || rowKey != null && Rows.TryGetValue(RowKey(sectionKey, rowKey), out var row) && row.RequiredFieldKeys.Contains(fieldKey));
+ }
+
+ public class RecordRowRuleEvaluation
+ {
+ public string SectionKey { get; set; }
+ public string RowKey { get; set; }
+ public HashSet HiddenFieldKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ public HashSet RequiredFieldKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ }
+
+ // ------------------------------------------------------------------------------------------------------
+ // Units, currencies, countries (RMS-1C, decision 22: originals round-trip, canonical values derive)
+ // ------------------------------------------------------------------------------------------------------
+
+ public sealed class RmsUnit
+ {
+ public RmsUnit(string code, string family, string label, string system, decimal toCanonical, decimal offset = 0m)
+ {
+ Code = code; Family = family; Label = label; System = system; ToCanonical = toCanonical; Offset = offset;
+ }
+
+ public string Code { get; }
+ public string Family { get; }
+ public string Label { get; }
+ /// metric | customary | both
+ public string System { get; }
+ /// Multiplier into the family's canonical unit (after the offset for temperature).
+ public decimal ToCanonical { get; }
+ public decimal Offset { get; }
+ }
+
+ ///
+ /// Bounded unit catalog. Canonical units are metric (m, L, ha, kg, C); U.S. customary values are stored as entered
+ /// with their unit and a derived canonical value for comparison, never silently converted (RMS plan section 4.1).
+ ///
+ public static class RmsUnits
+ {
+ public static readonly IReadOnlyList All = new List
+ {
+ new RmsUnit("m", "length", "metres", "metric", 1m),
+ new RmsUnit("km", "length", "kilometres", "metric", 1000m),
+ new RmsUnit("ft", "length", "feet", "customary", 0.3048m),
+ new RmsUnit("mi", "length", "miles", "customary", 1609.344m),
+ new RmsUnit("L", "volume", "litres", "metric", 1m),
+ new RmsUnit("gal", "volume", "U.S. gallons", "customary", 3.785411784m),
+ new RmsUnit("ha", "area", "hectares", "metric", 1m),
+ new RmsUnit("ac", "area", "acres", "customary", 0.40468564224m),
+ new RmsUnit("kg", "mass", "kilograms", "metric", 1m),
+ new RmsUnit("lb", "mass", "pounds", "customary", 0.45359237m),
+ new RmsUnit("C", "temperature", "degrees Celsius", "metric", 1m),
+ new RmsUnit("F", "temperature", "degrees Fahrenheit", "customary", 5m / 9m, -32m),
+ new RmsUnit("h", "time", "hours", "both", 60m),
+ new RmsUnit("min", "time", "minutes", "both", 1m),
+ new RmsUnit("count", "count", "count", "both", 1m)
+ };
+
+ public static readonly IReadOnlyDictionary CanonicalByFamily = new Dictionary(StringComparer.Ordinal)
+ {
+ ["length"] = "m", ["volume"] = "L", ["area"] = "ha", ["mass"] = "kg", ["temperature"] = "C", ["time"] = "min", ["count"] = "count"
+ };
+
+ public static RmsUnit Find(string code) => All.FirstOrDefault(u => string.Equals(u.Code, code, StringComparison.Ordinal));
+
+ public static IEnumerable ForFamily(string family) => All.Where(u => string.Equals(u.Family, family, StringComparison.OrdinalIgnoreCase));
+
+ /// Converts into the family's canonical unit; null when the unit is unknown.
+ public static (decimal Value, string Unit)? Canonicalize(decimal value, string unitCode)
+ {
+ var unit = Find(unitCode);
+ if (unit == null) return null;
+ var canonical = CanonicalByFamily[unit.Family];
+ if (unit.Code == canonical) return (value, canonical);
+ return (decimal.Round((value + unit.Offset) * unit.ToCanonical, 6), canonical);
+ }
+
+ /// Converts a canonical value back into (display in the reader's system).
+ public static decimal? FromCanonical(decimal canonicalValue, string unitCode)
+ {
+ var unit = Find(unitCode);
+ if (unit == null) return null;
+ var canonical = CanonicalByFamily[unit.Family];
+ if (unit.Code == canonical) return canonicalValue;
+ return decimal.Round(canonicalValue / unit.ToCanonical - unit.Offset, 6);
+ }
+
+ /// The unit a measurement system prefers for a family (customary for U.S., metric elsewhere).
+ public static string PreferredUnit(string family, string measurementSystem)
+ {
+ var customary = string.Equals(measurementSystem, "customary", StringComparison.OrdinalIgnoreCase);
+ var candidate = ForFamily(family).FirstOrDefault(u => customary ? u.System == "customary" : u.System == "metric");
+ return candidate?.Code ?? (CanonicalByFamily.TryGetValue(family ?? string.Empty, out var c) ? c : null);
+ }
+ }
+
+ public static class RmsCurrencies
+ {
+ /// ISO 4217 codes accepted at minimum (RMS plan section 4.1). No foreign-exchange conversion anywhere.
+ public static readonly IReadOnlyList Supported = new List { "USD", "CAD", "EUR", "GBP", "AUD", "MXN" };
+ public static bool IsSupported(string code) => code != null && Supported.Contains(code.ToUpperInvariant());
+ public static string Format(decimal amount, string code) => amount.ToString("N2", CultureInfo.InvariantCulture) + " " + (code ?? string.Empty).ToUpperInvariant();
+ }
+
+ /// ISO 3166-2 style country/subdivision codes for the U.S. and Canada (RMS-1C profile-aware addresses).
+ public static class RmsCountrySubdivisions
+ {
+ public static readonly IReadOnlyDictionary UnitedStates = new Dictionary(StringComparer.Ordinal)
+ {
+ ["AL"] = "Alabama", ["AK"] = "Alaska", ["AZ"] = "Arizona", ["AR"] = "Arkansas", ["CA"] = "California", ["CO"] = "Colorado", ["CT"] = "Connecticut", ["DE"] = "Delaware",
+ ["DC"] = "District of Columbia", ["FL"] = "Florida", ["GA"] = "Georgia", ["HI"] = "Hawaii", ["ID"] = "Idaho", ["IL"] = "Illinois", ["IN"] = "Indiana", ["IA"] = "Iowa",
+ ["KS"] = "Kansas", ["KY"] = "Kentucky", ["LA"] = "Louisiana", ["ME"] = "Maine", ["MD"] = "Maryland", ["MA"] = "Massachusetts", ["MI"] = "Michigan", ["MN"] = "Minnesota",
+ ["MS"] = "Mississippi", ["MO"] = "Missouri", ["MT"] = "Montana", ["NE"] = "Nebraska", ["NV"] = "Nevada", ["NH"] = "New Hampshire", ["NJ"] = "New Jersey", ["NM"] = "New Mexico",
+ ["NY"] = "New York", ["NC"] = "North Carolina", ["ND"] = "North Dakota", ["OH"] = "Ohio", ["OK"] = "Oklahoma", ["OR"] = "Oregon", ["PA"] = "Pennsylvania", ["RI"] = "Rhode Island",
+ ["SC"] = "South Carolina", ["SD"] = "South Dakota", ["TN"] = "Tennessee", ["TX"] = "Texas", ["UT"] = "Utah", ["VT"] = "Vermont", ["VA"] = "Virginia", ["WA"] = "Washington",
+ ["WV"] = "West Virginia", ["WI"] = "Wisconsin", ["WY"] = "Wyoming", ["PR"] = "Puerto Rico", ["GU"] = "Guam", ["VI"] = "U.S. Virgin Islands", ["AS"] = "American Samoa", ["MP"] = "Northern Mariana Islands"
+ };
+
+ public static readonly IReadOnlyDictionary Canada = new Dictionary(StringComparer.Ordinal)
+ {
+ ["AB"] = "Alberta", ["BC"] = "British Columbia", ["MB"] = "Manitoba", ["NB"] = "New Brunswick", ["NL"] = "Newfoundland and Labrador", ["NS"] = "Nova Scotia",
+ ["NT"] = "Northwest Territories", ["NU"] = "Nunavut", ["ON"] = "Ontario", ["PE"] = "Prince Edward Island", ["QC"] = "Quebec", ["SK"] = "Saskatchewan", ["YT"] = "Yukon"
+ };
+
+ public static readonly IReadOnlyDictionary> ByCountry = new Dictionary>(StringComparer.Ordinal)
+ {
+ ["US"] = UnitedStates, ["CA"] = Canada
+ };
+
+ /// "US-CA" -> valid; a country with no bounded list ("MX-...") is accepted as an opaque code of the form CC-XXX.
+ public static bool IsValid(string code)
+ {
+ if (string.IsNullOrWhiteSpace(code)) return false;
+ var parts = code.Split('-');
+ if (parts.Length == 1) return parts[0].Length == 2 && parts[0].All(char.IsLetter);
+ if (parts.Length != 2 || parts[0].Length != 2) return false;
+ return ByCountry.TryGetValue(parts[0], out var list) ? list.ContainsKey(parts[1]) : parts[1].Length >= 1 && parts[1].Length <= 3;
+ }
+
+ public static string Label(string code)
+ {
+ if (string.IsNullOrWhiteSpace(code)) return null;
+ var parts = code.Split('-');
+ var country = parts[0] == "US" ? "United States" : parts[0] == "CA" ? "Canada" : parts[0];
+ if (parts.Length == 1) return country;
+ return ByCountry.TryGetValue(parts[0], out var list) && list.TryGetValue(parts[1], out var name) ? name + ", " + country : parts[1] + ", " + country;
+ }
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RmsRecordWorkAssignment.cs b/Core/Resgrid.Model/Records/RmsRecordWorkAssignment.cs
new file mode 100644
index 00000000..6e127f86
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsRecordWorkAssignment.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Resgrid.Model
+{
+ /// Who a work assignment names (RMS plan section 5.2 RmsRecordWorkAssignment).
+ public enum RmsWorkAssigneeKind
+ {
+ Person = 1,
+ Unit = 2,
+ Group = 3,
+ /// A role on the active command structure of the Record's Call (IC app).
+ CommandRole = 4,
+ /// A dispatch-console role (Dispatch app).
+ DispatchRole = 5
+ }
+
+ public enum RmsWorkAssignmentState
+ {
+ Open = 1,
+ Acknowledged = 2,
+ Completed = 3,
+ Cancelled = 4
+ }
+
+ /// Why the work exists; a closed set so queues and dashboards can count it.
+ public static class RmsWorkAssignmentPurposes
+ {
+ public const string Complete = "complete";
+ public const string Review = "review";
+ public const string Correct = "correct";
+ public const string Attach = "attach";
+ public const string Acknowledge = "acknowledge";
+ public static readonly IReadOnlyList All = new[] { Complete, Review, Correct, Attach, Acknowledge };
+ public static bool IsKnown(string purpose) => purpose != null && Array.IndexOf((string[])All, purpose.Trim().ToLowerInvariant()) >= 0;
+ }
+
+ ///
+ /// An optional person, unit/team, command-role or dispatch-role assignment on a Record with purpose, due /
+ /// acknowledged / completed state, source context and audit (RMS plan section 5.2, RMS-1D). It narrows a
+ /// field work queue; it never replaces live authorization, so a queue row the assignee may no longer read
+ /// is withheld at read time rather than trusted because it was assigned.
+ ///
+ public class RmsRecordWorkAssignment : IEntity
+ {
+ public string RmsRecordWorkAssignmentId { get; set; }
+
+ public int DepartmentId { get; set; }
+
+ public string ProtectionId { get; set; }
+
+ public string RecordId { get; set; }
+
+ /// .
+ public int AssigneeKind { get; set; }
+
+ public string AssigneeUserId { get; set; }
+
+ public int? AssigneeUnitId { get; set; }
+
+ public int? AssigneeGroupId { get; set; }
+
+ /// Command or dispatch role name for the role kinds; null otherwise.
+ public string AssigneeRole { get; set; }
+
+ /// .
+ public string Purpose { get; set; }
+
+ public string Note { get; set; }
+
+ /// Safe source context (call / unit / group / command identifiers only) the assignment was made in.
+ public string SourceContextJson { get; set; }
+
+ public DateTime? DueOn { get; set; }
+
+ /// .
+ public int State { get; set; }
+
+ public DateTime? AcknowledgedOn { get; set; }
+
+ public string AcknowledgedByUserId { get; set; }
+
+ public DateTime? CompletedOn { get; set; }
+
+ public string CompletedByUserId { get; set; }
+
+ public DateTime? CancelledOn { get; set; }
+
+ public string CancelledByUserId { get; set; }
+
+ public string CancelReason { get; set; }
+
+ /// of the client that created the assignment (safe audit metadata).
+ public int OriginClient { get; set; }
+
+ public DateTime CreatedOn { get; set; }
+
+ public string CreatedByUserId { get; set; }
+
+ public DateTime ModifiedOn { get; set; }
+
+ public string ModifiedByUserId { get; set; }
+
+ [Key]
+ [Required]
+ public long RowVersion { get; set; }
+
+ public DateTime? DeletedOn { get; set; }
+
+ [NotMapped]
+ public bool IsOpen => State == (int)RmsWorkAssignmentState.Open || State == (int)RmsWorkAssignmentState.Acknowledged;
+
+ [NotMapped]
+ public object IdValue
+ {
+ get { return RmsRecordWorkAssignmentId; }
+ set { RmsRecordWorkAssignmentId = value?.ToString(); }
+ }
+
+ [NotMapped]
+ public string TableName => "RmsRecordWorkAssignments";
+
+ [NotMapped]
+ public string IdName => "RmsRecordWorkAssignmentId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "IsOpen" };
+ }
+
+ /// Input for creating a work assignment.
+ public class RecordWorkAssignmentInput
+ {
+ public string RecordId { get; set; }
+ public RmsWorkAssigneeKind AssigneeKind { get; set; } = RmsWorkAssigneeKind.Person;
+ public string AssigneeUserId { get; set; }
+ public int? AssigneeUnitId { get; set; }
+ public int? AssigneeGroupId { get; set; }
+ public string AssigneeRole { get; set; }
+ public string Purpose { get; set; } = RmsWorkAssignmentPurposes.Complete;
+ public string Note { get; set; }
+ public DateTime? DueOn { get; set; }
+ public FieldRecordContext SourceContext { get; set; }
+ public RmsOriginClient OriginClient { get; set; } = RmsOriginClient.Web;
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RmsSavedReport.cs b/Core/Resgrid.Model/Records/RmsSavedReport.cs
new file mode 100644
index 00000000..c0a39489
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsSavedReport.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// Bounded aggregation the saved-report runner supports (RMS plan section 4.1 "Reporting and presentation").
+ public enum RmsReportAggregate
+ {
+ Count = 1,
+ Sum = 2,
+ Average = 3,
+ Minimum = 4,
+ Maximum = 5
+ }
+
+ ///
+ /// Department saved report over one definition (RMS plan section 5.2 RmsSavedReportDefinition, registry M0161):
+ /// allowlisted typed fields, bounded filters, one optional group-by, and count/sum/avg/min/max where the pinned
+ /// field allows it. No SQL, no cross-definition joins, no unbounded query.
+ ///
+ public class RmsSavedReportDefinition : IEntity
+ {
+ public const int MaxRows = 5000;
+ public const int MaxColumns = 40;
+ public const int MaxFilters = 12;
+
+ public string RmsSavedReportDefinitionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string DefinitionKey { get; set; }
+ /// Null = current published version, with explicit cross-version mappings declared in MappingsJson.
+ public int? DefinitionVersion { get; set; }
+ /// Serialized .
+ public string SpecJson { get; set; }
+ public int MaxRowsPerRun { get; set; } = MaxRows;
+ public bool IncludeRestricted { get; set; }
+ public DateTime? LastRunOn { get; set; }
+ public string LastRunByUserId { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string CreatedByUserId { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public string ModifiedByUserId { get; set; }
+ public long RowVersion { get; set; }
+ public DateTime? DeletedOn { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsSavedReportDefinitionId; }
+ set { RmsSavedReportDefinitionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsSavedReportDefinitions";
+ [NotMapped] public string IdName => "RmsSavedReportDefinitionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName", "Spec" };
+
+ [NotMapped]
+ [JsonIgnore]
+ public RecordReportSpec Spec
+ {
+ get { return string.IsNullOrWhiteSpace(SpecJson) ? new RecordReportSpec() : JsonConvert.DeserializeObject(SpecJson) ?? new RecordReportSpec(); }
+ set { SpecJson = JsonConvert.SerializeObject(value ?? new RecordReportSpec()); }
+ }
+ }
+
+ public class RecordReportSpec
+ {
+ /// Field keys, plus the built-in record.* columns (record.number, record.state, record.started_on, record.finalized_on, record.author, record.group).
+ public List Columns { get; set; } = new List();
+ public List Filters { get; set; } = new List();
+ public string GroupByFieldKey { get; set; }
+ public List Aggregates { get; set; } = new List();
+ public string SortFieldKey { get; set; }
+ public bool SortDescending { get; set; }
+ /// Only finalized (and amended/accepted) revisions by default; drafts are working data.
+ public bool IncludeDrafts { get; set; }
+ public int? WindowDays { get; set; }
+ /// Explicit per-version field mappings: version -> (reportFieldKey -> fieldKey in that version).
+ public Dictionary> VersionMappings { get; set; } = new Dictionary>();
+ }
+
+ public class RecordReportFilter
+ {
+ public string FieldKey { get; set; }
+ public RmsRuleOperator Operator { get; set; } = RmsRuleOperator.Equals;
+ public string Value { get; set; }
+ public List Values { get; set; }
+ public decimal? Min { get; set; }
+ public decimal? Max { get; set; }
+ public DateTime? MinDate { get; set; }
+ public DateTime? MaxDate { get; set; }
+ }
+
+ public class RecordReportAggregateSpec
+ {
+ public RmsReportAggregate Aggregate { get; set; }
+ /// Null for Count.
+ public string FieldKey { get; set; }
+ }
+
+ public class RecordReportResult
+ {
+ public string ReportId { get; set; }
+ public string Name { get; set; }
+ public string DefinitionKey { get; set; }
+ public int? DefinitionVersion { get; set; }
+ public DateTime RanOn { get; set; }
+ public List Columns { get; set; } = new List();
+ public List ColumnLabels { get; set; } = new List();
+ public List> Rows { get; set; } = new List>();
+ public List Groups { get; set; } = new List();
+ public int TotalMatched { get; set; }
+ public bool Truncated { get; set; }
+ /// Definition versions the run met that had no mapping for a report column; their rows are reported, not coerced.
+ public List UnmappedVersions { get; set; } = new List();
+ public List Warnings { get; set; } = new List();
+ }
+
+ public class RecordReportGroup
+ {
+ public string GroupKey { get; set; }
+ public string GroupLabel { get; set; }
+ public int Count { get; set; }
+ public Dictionary Aggregates { get; set; } = new Dictionary();
+ }
+
+ public class RecordReportValidation
+ {
+ public List Issues { get; set; } = new List();
+ public bool IsValid => Issues.All(i => i.Severity != "error");
+ }
+}
diff --git a/Core/Resgrid.Model/Records/RmsTemplatePacks.cs b/Core/Resgrid.Model/Records/RmsTemplatePacks.cs
new file mode 100644
index 00000000..c6fdd5b9
--- /dev/null
+++ b/Core/Resgrid.Model/Records/RmsTemplatePacks.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// How an artifact produced from a pack may be described (RMS plan section 4.1). Nothing product-shipped is Exact until validated against the current source profile.
+ public enum RmsArtifactStatus
+ {
+ DepartmentLocal = 0,
+ Compatible = 1,
+ Exact = 2
+ }
+
+ ///
+ /// Product-managed vertical pack/version (RMS plan section 5.2 RmsTemplatePackVersion, registry M0162). Rows are
+ /// product scope (DepartmentId 0) and are upserted from the code catalog so departments see release notes, source
+ /// provenance and deprecation without a redeploy. A pack update never mutates a department clone.
+ ///
+ public class RmsTemplatePackVersion : IEntity
+ {
+ public const int ProductDepartmentId = 0;
+
+ public string RmsTemplatePackVersionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string PackKey { get; set; }
+ public int Version { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ public bool IsPreview { get; set; }
+ /// Comma-separated definition keys the pack ships (template.* / pack.* keys).
+ public string DefinitionKeys { get; set; }
+ /// Comma-separated jurisdiction profile keys the pack supports (generic, us, ca, us-ca).
+ public string SupportedProfiles { get; set; }
+ public string SupportedLocales { get; set; }
+ public string ReleaseNotes { get; set; }
+ /// Serialized list of : which published sources shaped the pack and when they were reviewed.
+ public string SourceProvenanceJson { get; set; }
+ public DateTime? ReviewedOn { get; set; }
+ public int ArtifactStatus { get; set; }
+ public string ContentChecksum { get; set; }
+ public bool IsDeprecated { get; set; }
+ public string DeprecatedByPackKey { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsTemplatePackVersionId; }
+ set { RmsTemplatePackVersionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsTemplatePackVersions";
+ [NotMapped] public string IdName => "RmsTemplatePackVersionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ ///
+ /// Locked jurisdiction/standards overlay (RMS plan section 5.2 RmsJurisdictionProfileVersion, registry M0162):
+ /// country, subdivision/agency scope, terminology, units, currency, locales, classification/retention defaults and
+ /// whether artifacts are exact, compatible or department-local. Product scope (DepartmentId 0).
+ ///
+ public class RmsJurisdictionProfileVersion : IEntity
+ {
+ public string RmsJurisdictionProfileVersionId { get; set; }
+ public int DepartmentId { get; set; }
+ public string ProtectionId { get; set; }
+ public string ProfileKey { get; set; }
+ public int Version { get; set; }
+ public string Name { get; set; }
+ /// ISO 3166-1 alpha-2, or "XX" for the generic base and "US-CA" style for a cross-border pair.
+ public string Country { get; set; }
+ public string Subdivision { get; set; }
+ public string AgencyScope { get; set; }
+ public string DefaultLocale { get; set; }
+ public string SupportedLocales { get; set; }
+ /// metric | customary
+ public string MeasurementSystem { get; set; }
+ public string CurrencyCode { get; set; }
+ public string DefaultTimeZone { get; set; }
+ /// Serialized dictionary locale -> (term -> label).
+ public string TerminologyJson { get; set; }
+ /// Serialized list of : form/rule identifiers and source versions.
+ public string StandardsJson { get; set; }
+ public int ClassificationDefault { get; set; }
+ public int? RetentionYearsDefault { get; set; }
+ public string RequiredSections { get; set; }
+ public int ArtifactStatus { get; set; }
+ public DateTime? ReviewedOn { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ public long RowVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return RmsJurisdictionProfileVersionId; }
+ set { RmsJurisdictionProfileVersionId = value?.ToString(); }
+ }
+
+ [NotMapped] public string TableName => "RmsJurisdictionProfileVersions";
+ [NotMapped] public string IdName => "RmsJurisdictionProfileVersionId";
+ [NotMapped] public int IdType => 1;
+ [NotMapped] public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+
+ [NotMapped]
+ [JsonIgnore]
+ public Dictionary> Terminology => string.IsNullOrWhiteSpace(TerminologyJson) ? new Dictionary>() : JsonConvert.DeserializeObject>>(TerminologyJson) ?? new Dictionary>();
+ }
+
+ public class RmsSourceProvenance
+ {
+ public string Identifier { get; set; }
+ public string Title { get; set; }
+ public string Publisher { get; set; }
+ public string Version { get; set; }
+ public string Url { get; set; }
+ public DateTime? ReviewedOn { get; set; }
+ /// operational-aid | named-form
+ public string Kind { get; set; } = "operational-aid";
+ }
+
+ /// A product template or pack definition as shipped in code: the generic base schema plus per-profile overlays.
+ public class RecordTemplateDefinition
+ {
+ public string Key { get; set; }
+ public string PackKey { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ public RmsLifecyclePreset LifecyclePreset { get; set; } = RmsLifecyclePreset.QuickEntry;
+ public string NumberPrefix { get; set; }
+ public string PermittedSubjectTypes { get; set; }
+ public RmsFieldClassification Classification { get; set; }
+ public int? RetentionYears { get; set; }
+ public RecordDefinitionSchema Schema { get; set; } = new RecordDefinitionSchema();
+ public RecordDefinitionClientSurface ClientSurface { get; set; } = new RecordDefinitionClientSurface { Responder = true, Unit = true, IncidentCommand = true, Dispatch = true, AllowOffline = true };
+ /// Profile-specific overrides (RMS-1C): profile key -> overlay.
+ public Dictionary Overlays { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ /// Fields whose classification is forced by the pack (subject, treatment, exposure, manifest, release, regulatory); a clone cannot loosen them.
+ public List LockedClassificationFieldKeys { get; set; } = new List();
+
+ ///
+ /// Per-pack protected-data policies (RMS-1C): the classification floor a category of fields carries in every
+ /// rendering and every department clone. A floor can be raised by the department, never lowered; search, Workflow,
+ /// reports and exports project the field by its classification, so a floor also fixes the safe projection.
+ ///
+ public List ProtectedDataPolicies { get; set; } = new List();
+
+ /// The floor for a field: the strictest policy naming it, or the locked-key floor (Restricted), or none.
+ public RmsFieldClassification? FloorFor(string fieldKey)
+ {
+ RmsFieldClassification? floor = null;
+ foreach (var policy in ProtectedDataPolicies.Where(p => p.FieldKeys.Contains(fieldKey, StringComparer.OrdinalIgnoreCase)))
+ if (!floor.HasValue || policy.Floor > floor.Value) floor = policy.Floor;
+ if (LockedClassificationFieldKeys.Contains(fieldKey, StringComparer.OrdinalIgnoreCase) && (!floor.HasValue || floor.Value < RmsFieldClassification.Restricted))
+ floor = RmsFieldClassification.Restricted;
+ return floor;
+ }
+ }
+
+ /// What a jurisdiction overlay changes on a template: labels by locale, units, currency, added required sections, provenance.
+ /// One protected-data policy of a template: a category of fields and the classification floor they carry.
+ public class RecordTemplateFieldPolicy
+ {
+ /// subject-clue-recovery, treatment-casualty, exposure-health, manifest-travel, facility-security, release, regulatory.
+ public string Category { get; set; }
+ public List FieldKeys { get; set; } = new List();
+ public RmsFieldClassification Floor { get; set; } = RmsFieldClassification.Restricted;
+ public string Rationale { get; set; }
+ }
+
+ public class RecordTemplateOverlay
+ {
+ public string ProfileKey { get; set; }
+ /// locale -> (field or section key -> label)
+ public Dictionary> Labels { get; set; } = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ /// field key -> unit code (quantity fields) in the profile's measurement system.
+ public Dictionary DefaultUnits { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ public string CurrencyCode { get; set; }
+ public List RequiredSectionKeys { get; set; } = new List();
+ public List Sources { get; set; } = new List();
+ public RmsArtifactStatus ArtifactStatus { get; set; } = RmsArtifactStatus.Compatible;
+ }
+
+ /// Catalog entry for the template browser: pack metadata plus the definitions it ships.
+ public class RecordTemplatePackSummary
+ {
+ public string PackKey { get; set; }
+ public int Version { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ public bool IsPreview { get; set; }
+ public string ArtifactStatus { get; set; }
+ public List SupportedProfiles { get; set; } = new List();
+ public List SupportedLocales { get; set; } = new List();
+ public DateTime? ReviewedOn { get; set; }
+ public List Definitions { get; set; } = new List();
+ public List Sources { get; set; } = new List();
+ }
+
+ public class RecordTemplateSummary
+ {
+ public string Key { get; set; }
+ public string Name { get; set; }
+ public string Category { get; set; }
+ public string Description { get; set; }
+ public string LifecyclePreset { get; set; }
+ public int SectionCount { get; set; }
+ public int FieldCount { get; set; }
+ public string MinimumClientCapability { get; set; }
+ public string ArtifactStatus { get; set; }
+ public bool IsPreview { get; set; }
+ }
+
+ /// A published pack definition rendered for one profile and locale (labels, units, currency applied).
+ public class RecordTemplateRendering
+ {
+ /// The pack's protected-data policies as applied to this rendering (RMS-1C).
+ public List Policies { get; set; } = new List();
+ public RecordTemplateDefinition Template { get; set; }
+ public string ProfileKey { get; set; }
+ public string Locale { get; set; }
+ public string MeasurementSystem { get; set; }
+ public string CurrencyCode { get; set; }
+ public RecordDefinitionSchema Schema { get; set; }
+ public RmsArtifactStatus ArtifactStatus { get; set; }
+ public List Sources { get; set; } = new List();
+ /// "Compatible with ; not an exact named form" style statement every generated artifact displays.
+ public string ProvenanceStatement { get; set; }
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IRmsDefinitionRepositories.cs b/Core/Resgrid.Model/Repositories/IRmsDefinitionRepositories.cs
new file mode 100644
index 00000000..343c3612
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IRmsDefinitionRepositories.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ // RMS-1B/1C repositories (registry M0158, M0159, M0161, M0162, M0163). Every query begins at DepartmentId; product
+ // catalog rows (packs, profiles) use DepartmentId 0.
+
+ public interface IRmsRecordDefinitionsRepository : IRepository
+ {
+ Task GetByKeyAsync(int departmentId, string definitionKey);
+ Task GetByIdForDepartmentAsync(int departmentId, string definitionId);
+ Task> GetForDepartmentAsync(int departmentId, bool includeRetired);
+ Task