diff --git a/Core/Resgrid.Config/CertificationConfig.cs b/Core/Resgrid.Config/CertificationConfig.cs
new file mode 100644
index 00000000..aba2209e
--- /dev/null
+++ b/Core/Resgrid.Config/CertificationConfig.cs
@@ -0,0 +1,18 @@
+namespace Resgrid.Config
+{
+ ///
+ /// Certification expiry worker (Workforce & Business Operations plan, Phase D5; worker 34). Environment keys:
+ /// RESGRID:CertificationConfig:SweepEnabled, :SweepLocalHour, :MaxLeadDays.
+ ///
+ public static class CertificationConfig
+ {
+ /// Master switch for the nightly sweep (expire, expiring, unit, enforcement and digest passes).
+ public static bool SweepEnabled = true;
+
+ /// Department-local hour (0-23) at which the worker's hourly tick runs a department's sweep.
+ public static int SweepLocalHour = 6;
+
+ /// Largest lead day a department may configure; keeps the expiring read bounded.
+ public static int MaxLeadDays = 365;
+ }
+}
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.ar.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.ar.resx
new file mode 100644
index 00000000..e040977c
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.ar.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ الشهادات
+ إدارة الشهادات
+ عرض الشهادات
+ إدارة إعداد الشهادات
+ إنشاء سجلات شهادات الأعضاء الآخرين ونقاطهم وسجلات شهادات الوحدات وتعديلها وتعليقها والتحقق منها وحذفها.
+ عرض سجلات شهادات الأعضاء الآخرين وشهادات الوحدات ولوحة انتهاء الصلاحية. يرى الأعضاء دائمًا سجلاتهم الخاصة.
+ إدارة أنواع الشهادات ومتطلبات الأدوار وإعدادات الشهادات في القسم.
+ لوحة المعلومات
+ من يحمل ماذا، وما الذي يوشك على الانتهاء، وما الذي انتهى، للأشخاص والوحدات.
+ أنواع الشهادات
+ الإعدادات
+ تقرير الامتثال
+ مصفوفات قابلة للطباعة شخص × نوع ووحدة × نوع مع حالة انتهاء الصلاحية.
+ أُنشئ في {0}
+ تم الحفظ.
+ تعذر حفظ التغيير.
+ حفظ
+ إلغاء
+ تعديل
+ نشط
+ غير نشط
+ الوصف
+ ملاحظات
+ الحالة
+ التاريخ
+ الساعات
+ الملف
+ ملف PDF أو صورة أو مستند مكتبي، حتى 10 ميغابايت.
+ السبب
+ الرقم
+ صادر عن
+ تاريخ الإصدار
+ ينتهي في
+ تم الاستلام في
+ لا ينتهي أبدًا
+ الأيام حتى الانتهاء
+ متبقٍ {0} يومًا
+ قبل {0} يومًا
+ منتهية
+ تنتهي خلال {0} يومًا
+ معلّقة أو ملغاة
+ تصدير CSV
+ النطاق
+ الأشخاص
+ الوحدات
+ الوحدة
+ العضو
+ الفئة
+ كل الفئات
+ المجموعة
+ كل المجموعات
+ الدور
+ كل الأدوار
+ لا توجد أنواع شهادات بعد.
+ لا توجد سجلات شهادات بعد.
+ ساري
+ ينتهي قريبًا
+ منتهية أو معلّقة أو ملغاة
+ نشط
+ منتهٍ
+ معلّق
+ ملغى
+ بانتظار التحقق
+ متدرب
+ نشط
+ منتهٍ
+ معلّق
+ الإطفاء
+ الإسعاف
+ البحث والإنقاذ
+ حرائق الغابات
+ إدارة الطوارئ
+ صناعي
+ الأمن
+ طبي
+ السائق
+ المركبة
+ أخرى
+ كتالوج الشهادات والتراخيص والفحوصات والتصاريح التي يتابعها قسمك.
+ لكل نوع رمز ثابت وفئة ونطاق (شخص أو وحدة) وصلاحية افتراضية وقواعد اختيارية للتحقق أو ساعات النقاط. اختر الأنواع الشائعة من معرض القوالب.
+ إخفاء غير النشطة
+ إظهار غير النشطة
+ معرض القوالب
+ اعتمادات شائعة في الإطفاء والإسعاف والبحث والإنقاذ وحرائق الغابات وإدارة الطوارئ والصناعة والأمن وسائقي DOT وامتثال المركبات. يصبح كل منها نوعًا يملكه قسمك ويمكنه تعديله.
+ تصفية القوالب…
+ موجود بالفعل في الكتالوج
+ إضافة المحدد
+ تمت إضافة {0}؛ تم التخطي (موجود بالفعل أو مرفوض): {1}
+ نوع جديد
+ تعديل النوع
+ الرمز
+ النوع
+ ينطبق على
+ شخص
+ وحدة
+ الصلاحية
+ {0} شهرًا
+ السجلات
+ المتطلبات
+ هل تريد حذف نوع الشهادة هذا؟
+ الجهة المصدرة
+ يُشتق من الاسم عند تركه فارغًا
+ معرّف ثابت تستخدمه متطلبات الأدوار والتكاملات؛ أحرف وأرقام ونقاط وشرطات.
+ يشير أحد متطلبات الأدوار إلى هذا النوع، لذا لا يمكن تغيير رمزه.
+ الصلاحية الافتراضية (بالأشهر)
+ ساعات النقاط لكل تجديد
+ يتطلب تحقق المشرف
+ تبقى السجلات الجديدة معلّقة حتى يوقّع عليها شخص لديه صلاحيات الإدارة.
+ الكتالوج المصنّف والقوالب
+ إعدادات الشهادات
+ كيفية فرض متطلبات الأدوار ومتى تُرسل إشعارات انتهاء الصلاحية.
+ الفرض
+ وضع الفرض
+ إيقاف – المتطلبات للعلم فقط
+ تحذير فقط – السماح بتغيير الأدوار مع إظهار التحذيرات والإشعار
+ فرض – منع إضافة الأدوار غير المؤهلة وإزالة الأعضاء بعد فترة السماح
+ يزيل وضع الفرض الأعضاء من الأدوار التي انتهت شهاداتها الإلزامية؛ تُسجَّل الإزالة ويُخطر العضو.
+ سيزيل وضع الفرض الأعضاء من الأدوار بعد انتهاء فترة السماح. تم ضبط {0} من المتطلبات الإلزامية. هل تريد المتابعة؟
+ أكّد التبديل إلى وضع الفرض قبل الحفظ.
+ فترة السماح (بالأيام)
+ عدد الأيام بعد انتهاء شهادة إلزامية قبل إزالة العضو من الدور. يمكن لكل متطلب تجاوز هذه القيمة.
+ اعتبار السجلات بانتظار التحقق سارية لمتطلبات الأدوار
+ الإشعارات
+ إشعار (أيام قبل الانتهاء)
+ مفصولة بفواصل، مثل 60,30,14,7,1. إشعار واحد لكل سجل في كل يوم مدرج.
+ إخطار الحامل مباشرة
+ إرسال ملخص ليلي لمسؤولي القسم
+ يتم ضبط جمهور أحداث انتهاء الشهادة / انتهت / إزالة الدور وشهادة الوحدة في
+ إعدادات الإشعارات
+ الشهادات المطلوبة
+ الشهادات المطلوبة – {0}
+ أنواع الشهادات التي يجب أن يحملها العضو لهذا الدور. الصفوف التي تشترك في رقم مجموعة هي بدائل؛ الصفوف الإلزامية تمنع أو تزيل في وضع الفرض.
+ العودة إلى الأدوار
+ الفرض متوقف: هذه المتطلبات للعلم فقط حتى يقوم مسؤول القسم بتفعيل التحذير فقط أو الفرض في
+ لا توجد بعد أنواع شهادات خاصة بالأشخاص.
+ إلزامي
+ مجموعة بدائل
+ الصفوف ذات رقم المجموعة نفسه يحققها أي منها (EMT أو AEMT أو Paramedic)؛ اتركه فارغًا لمتطلب مستقل.
+ السماح للمتدربين
+ فترة سماح خاصة (أيام)
+ إضافة متطلب
+ أهلية الأعضاء
+ لا يوجد أعضاء في هذا الدور.
+ مفقود أو منتهٍ
+ مؤهل
+ اختيارية مفقودة
+ غير مؤهل
+ الإزالة في {0}
+ هؤلاء الأعضاء لا يحملون الشهادات التي يتطلبها هذا الدور ولم تتم إضافتهم: {0}
+ تمت الإضافة مع تحذير – شهادات مفقودة لهذا الدور: {0}
+ تم إنشاء العضو، لكن لم يتم تعيين دور أو أكثر: لا يحمل العضو الشهادات التي تتطلبها تلك الأدوار.
+ شهادات الوحدة
+ الشهادات والفحوصات – {0}
+ الفحوصات والتسجيل والتأمين والتصاريح وشهادات الاختبار ذات تاريخ انتهاء لهذه الوحدة.
+ الشهادات والفحوصات
+ العودة إلى الوحدة
+ إضافة سجل
+ لا توجد بعد أنواع شهادات خاصة بالوحدات.
+ شهادة الوحدة
+ إعادة التفعيل
+ تعليق
+ إلغاء
+ هل تريد حذف هذا السجل؟
+ سجل الشهادة
+ هذا السجل محمي؛ تظهر القيم التعريفية كـ REDACTED دون إذن كشف.
+ بدون نوع
+ تم التحقق بواسطة
+ تحقق
+ تجديد
+ يحدد تاريخ انتهاء جديدًا (ورقمًا جديدًا اختياريًا)، ويعيد تفعيل سجل منتهٍ.
+ تاريخ الانتهاء الجديد
+ الرقم الجديد
+ اتركه فارغًا للاحتفاظ بالحالي
+ وضع علامة متدرب
+ تغيير الحالة
+ نقاط التعليم المستمر
+ إضافة نقاط
+ {0} من {1} ساعة نحو التجديد التالي
+ تم تسجيل {0} ساعة
+ لم يتم تسجيل نقاط.
+ هل تريد حذف إدخال النقاط هذا؟
+ شهادة توشك على الانتهاء
+ تمت إضافة شهادة
+ تم تجديد الشهادة
+ انتهت صلاحية الشهادة
+ تمت إزالة الدور بسبب انتهاء الشهادة
+ تغيرت حالة الشهادة
+ شهادة وحدة توشك على الانتهاء
+ انتهت صلاحية شهادة الوحدة
+ يشير إلى هذا النوع متطلب دور أو سجل نشط ولا يمكن حذفه؛ قم بإلغاء تفعيله بدلاً من ذلك.
+ لم يتم العثور على نوع الشهادة.
+ ينطبق نوع الشهادة هذا على النطاق الآخر (شخص أو وحدة).
+ يحتاج النوع إلى اسم.
+ يجب أن تكون الصلاحية الافتراضية بين 1 و600 شهر.
+ لا يمكن أن تكون ساعات النقاط سالبة.
+ يستخدم نوع آخر في هذا القسم هذا الرمز بالفعل.
+ لا يمكن تغيير الرمز بينما يشير متطلب دور إلى هذا النوع.
+ لا يمكن تغيير النطاق بعد وجود سجلات من هذا النوع.
+ لم يتم العثور على القالب.
+ لم يتم العثور على سجل الشهادة.
+ لا يمكن تعيين هذه الحالة يدويًا.
+ يمكن التحقق فقط من سجل بانتظار التحقق.
+ هذا النوع ينتهي، لذا يلزم تاريخ انتهاء جديد.
+ يجب أن يكون تاريخ الانتهاء الجديد بعد الحالي.
+ يجب أن تكون ساعات النقاط أكبر من الصفر.
+ لم يتم العثور على إدخال النقاط.
+ لم يتم العثور على الوحدة في هذا القسم.
+ لم يتم العثور على الدور في هذا القسم.
+ يجب أن تكون أيام السماح بين 0 و3650.
+ وضع فرض غير معروف.
+ يجب أن تكون أيام الإشعار المسبق موجبة ولا تتجاوز سنة.
+ تعذر تأمين البيانات المحمية في هذا السجل، لذا لم يتم حفظ التغيير.
+ لا يحمل العضو الشهادات التي يتطلبها هذا الدور.
+ لا يمكن رفع هذا النوع من الملفات.
+ حجم الملف أكبر من 10 ميغابايت.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.cs b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.cs
new file mode 100644
index 00000000..c95f410d
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.cs
@@ -0,0 +1,4 @@
+namespace Resgrid.Localization.Areas.User.Certifications
+{
+ public class Certifications { }
+}
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.de.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.de.resx
new file mode 100644
index 00000000..6f613eaf
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.de.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Zertifizierungen
+ Zertifizierungen verwalten
+ Zertifizierungen anzeigen
+ Zertifizierungseinrichtung verwalten
+ Zertifizierungsdatensätze und Fortbildungspunkte anderer Mitglieder sowie Einheitszertifizierungen anlegen, bearbeiten, aussetzen, bestätigen und löschen.
+ Zertifizierungsdatensätze anderer Mitglieder, Einheitszertifizierungen und das Ablauf-Dashboard anzeigen. Eigene Datensätze sind immer sichtbar.
+ Zertifizierungstypen, Rollenanforderungen und die Zertifizierungseinstellungen der Abteilung verwalten.
+ Übersicht
+ Wer was besitzt, was abläuft und was verfallen ist – für Personen und Einheiten.
+ Zertifizierungstypen
+ Einstellungen
+ Compliance-Bericht
+ Druckbare Matrizen Person × Typ und Einheit × Typ mit Ablaufstatus.
+ Erstellt am {0}
+ Gespeichert.
+ Die Änderung konnte nicht gespeichert werden.
+ Speichern
+ Abbrechen
+ Bearbeiten
+ Aktiv
+ Inaktiv
+ Beschreibung
+ Notizen
+ Status
+ Datum
+ Stunden
+ Datei
+ PDF, Bild oder Office-Dokument, bis zu 10 MB.
+ Grund
+ Nummer
+ Ausgestellt von
+ Ausgestellt am
+ Läuft ab am
+ Erhalten am
+ Läuft nie ab
+ Tage bis Ablauf
+ noch {0} Tage
+ vor {0} Tagen
+ Abgelaufen
+ Läuft in {0} Tagen ab
+ Ausgesetzt oder entzogen
+ CSV exportieren
+ Bereich
+ Personen
+ Einheiten
+ Einheit
+ Mitglied
+ Kategorie
+ Alle Kategorien
+ Gruppe
+ Alle Gruppen
+ Rolle
+ Alle Rollen
+ Noch keine Zertifizierungstypen.
+ Noch keine Zertifizierungsdatensätze.
+ Gültig
+ Läuft bald ab
+ Abgelaufen, ausgesetzt oder entzogen
+ Aktiv
+ Abgelaufen
+ Ausgesetzt
+ Entzogen
+ Bestätigung ausstehend
+ In Ausbildung
+ Aktiv
+ Abgelaufen
+ Ausgesetzt
+ Feuerwehr
+ Rettungsdienst
+ Such- und Rettungsdienst
+ Vegetationsbrand
+ Katastrophenschutz
+ Industrie
+ Sicherheitsdienst
+ Medizin
+ Fahrer
+ Fahrzeug
+ Sonstige
+ Der Katalog der Zertifizierungen, Lizenzen, Prüfungen und Genehmigungen, die Ihre Abteilung verfolgt.
+ Ein Typ hat einen festen Code, eine Kategorie, einen Bereich (Person oder Einheit), eine Standardgültigkeit und optionale Bestätigungs- oder Fortbildungsregeln. Gängige Typen finden Sie in der Vorlagengalerie.
+ Inaktive ausblenden
+ Inaktive anzeigen
+ Vorlagengalerie
+ Gängige Nachweise aus Feuerwehr, Rettungsdienst, SAR, Vegetationsbrand, Katastrophenschutz, Industrie, Sicherheitsdienst, DOT-Fahrer- und Fahrzeug-Compliance. Jeder wird zu einem Typ, den Ihre Abteilung besitzt und bearbeiten kann.
+ Vorlagen filtern …
+ Bereits in Ihrem Katalog
+ Ausgewählte hinzufügen
+ {0} hinzugefügt; übersprungen (bereits vorhanden oder abgelehnt): {1}
+ Neuer Typ
+ Typ bearbeiten
+ Code
+ Typ
+ Gilt für
+ Person
+ Einheit
+ Gültigkeit
+ {0} Monate
+ Datensätze
+ Anforderungen
+ Diesen Zertifizierungstyp löschen?
+ Ausstellende Stelle
+ Wird bei Leerfeld aus dem Namen abgeleitet
+ Fester Bezeichner für Rollenanforderungen und Integrationen; Buchstaben, Ziffern, Punkte und Bindestriche.
+ Eine Rollenanforderung verweist auf diesen Typ; der Code kann nicht mehr geändert werden.
+ Standardgültigkeit (Monate)
+ Fortbildungsstunden pro Verlängerung
+ Erfordert Bestätigung durch Vorgesetzte
+ Neue Datensätze bleiben ausstehend, bis jemand mit Verwaltungsrechten sie bestätigt.
+ Typisierter Katalog und Vorlagen
+ Zertifizierungseinstellungen
+ Wie Rollenanforderungen durchgesetzt werden und wann Ablaufhinweise verschickt werden.
+ Durchsetzung
+ Durchsetzungsmodus
+ Aus – Anforderungen sind nur informativ
+ Nur warnen – Rollenänderungen erlauben, Warnungen anzeigen, benachrichtigen
+ Durchsetzen – unqualifizierte Rollenzuweisungen blockieren und Mitglieder nach der Nachfrist entfernen
+ Durchsetzen entfernt Mitglieder aus Rollen, deren Pflichtzertifizierungen verfallen sind; die Entfernung wird protokolliert und das Mitglied benachrichtigt.
+ Durchsetzen entfernt Mitglieder aus Rollen, sobald ihre Nachfrist endet. {0} Pflichtanforderung(en) sind konfiguriert. Fortfahren?
+ Bestätigen Sie den Wechsel zu „Durchsetzen“ vor dem Speichern.
+ Nachfrist (Tage)
+ Tage nach Verfall einer Pflichtzertifizierung, bevor das Mitglied aus der Rolle entfernt wird. Jede Anforderung kann den Wert überschreiben.
+ Datensätze mit ausstehender Bestätigung für Rollenanforderungen als gültig behandeln
+ Benachrichtigungen
+ Benachrichtigen (Tage vor Ablauf)
+ Kommagetrennt, z. B. 60,30,14,7,1. Ein Hinweis pro Datensatz und aufgeführtem Tag.
+ Inhaber direkt benachrichtigen
+ Abteilungsadministratoren eine nächtliche Zusammenfassung senden
+ Empfänger der Ereignisse „Zertifizierung läuft ab / abgelaufen / Rolle entfernt“ und „Einheitszertifizierung“ werden konfiguriert unter
+ Benachrichtigungseinstellungen
+ Erforderliche Zertifizierungen
+ Erforderliche Zertifizierungen – {0}
+ Welche Zertifizierungstypen ein Mitglied für diese Rolle haben muss. Zeilen mit derselben Gruppennummer sind Alternativen; Pflichtzeilen blockieren oder entfernen im Modus „Durchsetzen“.
+ Zurück zu den Rollen
+ Die Durchsetzung ist aus: Diese Anforderungen sind nur informativ, bis ein Abteilungsadministrator „Nur warnen“ oder „Durchsetzen“ aktiviert unter
+ Es gibt noch keine personenbezogenen Zertifizierungstypen.
+ Pflicht
+ Alternativgruppe
+ Zeilen mit derselben Gruppennummer werden durch eine beliebige davon erfüllt (EMT oder AEMT oder Paramedic); leer lassen für eine eigenständige Anforderung.
+ Auszubildende zulassen
+ Nachfrist überschreiben (Tage)
+ Anforderung hinzufügen
+ Eignung der Mitglieder
+ Diese Rolle hat keine Mitglieder.
+ Fehlend oder verfallen
+ Qualifiziert
+ Optionale fehlen
+ Nicht qualifiziert
+ Entfernung am {0}
+ Diese Mitglieder besitzen die für diese Rolle erforderlichen Zertifizierungen nicht und wurden nicht hinzugefügt: {0}
+ Mit Warnung hinzugefügt – fehlende Zertifizierungen für diese Rolle: {0}
+ Das Mitglied wurde angelegt, aber eine oder mehrere Rollen wurden nicht zugewiesen: Es fehlen die für diese Rollen erforderlichen Zertifizierungen.
+ Einheitszertifizierungen
+ Zertifizierungen und Prüfungen – {0}
+ Prüfungen, Zulassung, Versicherung, Genehmigungen und Prüfbescheinigungen mit Ablaufdatum für diese Einheit.
+ Zertifizierungen & Prüfungen
+ Zurück zur Einheit
+ Datensatz hinzufügen
+ Es gibt noch keine einheitenbezogenen Zertifizierungstypen.
+ Einheitszertifizierung
+ Wieder aktivieren
+ Aussetzen
+ Entziehen
+ Diesen Datensatz löschen?
+ Zertifizierungsdatensatz
+ Dieser Datensatz ist geschützt; identifizierende Werte werden ohne Freigabe als REDACTED angezeigt.
+ Ohne Typ
+ Bestätigt von
+ Bestätigen
+ Verlängern
+ Setzt ein neues Ablaufdatum (und optional eine neue Nummer) und aktiviert einen abgelaufenen Datensatz erneut.
+ Neues Ablaufdatum
+ Neue Nummer
+ Leer lassen, um die aktuelle zu behalten
+ Als Auszubildende markieren
+ Status ändern
+ Fortbildungspunkte
+ Punkte hinzufügen
+ {0} von {1} Stunden bis zur nächsten Verlängerung
+ {0} Stunden erfasst
+ Keine Punkte erfasst.
+ Diesen Punkteeintrag löschen?
+ Zertifizierung läuft ab
+ Zertifizierung hinzugefügt
+ Zertifizierung verlängert
+ Zertifizierung abgelaufen
+ Rolle wegen verfallener Zertifizierung entfernt
+ Zertifizierungsstatus geändert
+ Einheitszertifizierung läuft ab
+ Einheitszertifizierung abgelaufen
+ Auf diesen Typ verweist eine Rollenanforderung oder ein aktiver Datensatz; er kann nicht gelöscht werden. Deaktivieren Sie ihn stattdessen.
+ Der Zertifizierungstyp wurde nicht gefunden.
+ Dieser Zertifizierungstyp gilt für den anderen Bereich (Person bzw. Einheit).
+ Ein Typ benötigt einen Namen.
+ Die Standardgültigkeit muss zwischen 1 und 600 Monaten liegen.
+ Fortbildungsstunden dürfen nicht negativ sein.
+ Ein anderer Typ in dieser Abteilung verwendet diesen Code bereits.
+ Der Code kann nicht geändert werden, solange eine Rollenanforderung auf diesen Typ verweist.
+ Der Bereich kann nicht geändert werden, sobald Datensätze dieses Typs existieren.
+ Die Vorlage wurde nicht gefunden.
+ Der Zertifizierungsdatensatz wurde nicht gefunden.
+ Dieser Status kann nicht manuell gesetzt werden.
+ Nur ein Datensatz mit ausstehender Bestätigung kann bestätigt werden.
+ Dieser Typ läuft ab, daher ist ein neues Ablaufdatum erforderlich.
+ Das neue Ablaufdatum muss nach dem aktuellen liegen.
+ Fortbildungsstunden müssen größer als null sein.
+ Der Punkteeintrag wurde nicht gefunden.
+ Die Einheit wurde in dieser Abteilung nicht gefunden.
+ Die Rolle wurde in dieser Abteilung nicht gefunden.
+ Die Nachfrist muss zwischen 0 und 3650 Tagen liegen.
+ Unbekannter Durchsetzungsmodus.
+ Vorlauftage müssen positiv sein und höchstens ein Jahr betragen.
+ Geschützte Daten dieses Datensatzes konnten nicht gesichert werden; die Änderung wurde nicht gespeichert.
+ Das Mitglied besitzt die für diese Rolle erforderlichen Zertifizierungen nicht.
+ Dieser Dateityp kann nicht hochgeladen werden.
+ Die Datei ist größer als 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.el.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.el.resx
new file mode 100644
index 00000000..75a64c7c
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.el.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Πιστοποιήσεις
+ Διαχείριση πιστοποιήσεων
+ Προβολή πιστοποιήσεων
+ Διαχείριση ρύθμισης πιστοποιήσεων
+ Δημιουργία, επεξεργασία, αναστολή, επαλήθευση και διαγραφή εγγραφών πιστοποίησης και μονάδων άλλων μελών, καθώς και εγγραφών πιστοποίησης μονάδων.
+ Προβολή εγγραφών πιστοποίησης άλλων μελών, πιστοποιήσεων μονάδων και του πίνακα λήξεων. Τα μέλη βλέπουν πάντα τις δικές τους εγγραφές.
+ Διαχείριση τύπων πιστοποίησης, απαιτήσεων ρόλων και ρυθμίσεων πιστοποίησης του τμήματος.
+ Πίνακας
+ Ποιος κατέχει τι, τι λήγει και τι έχει λήξει, για άτομα και μονάδες.
+ Τύποι πιστοποίησης
+ Ρυθμίσεις
+ Αναφορά συμμόρφωσης
+ Εκτυπώσιμοι πίνακες άτομο × τύπος και μονάδα × τύπος με κατάσταση λήξης.
+ Δημιουργήθηκε στις {0}
+ Αποθηκεύτηκε.
+ Η αλλαγή δεν ήταν δυνατό να αποθηκευτεί.
+ Αποθήκευση
+ Ακύρωση
+ Επεξεργασία
+ Ενεργό
+ Ανενεργό
+ Περιγραφή
+ Σημειώσεις
+ Κατάσταση
+ Ημερομηνία
+ Ώρες
+ Αρχείο
+ PDF, εικόνα ή έγγραφο γραφείου, έως 10 MB.
+ Αιτία
+ Αριθμός
+ Εκδόθηκε από
+ Ημερομηνία έκδοσης
+ Λήγει στις
+ Ελήφθη στις
+ Δεν λήγει ποτέ
+ Ημέρες έως τη λήξη
+ {0} ημέρες απομένουν
+ πριν από {0} ημέρες
+ Ληγμένα
+ Λήγει εντός {0} ημερών
+ Σε αναστολή ή ανακληθέντα
+ Εξαγωγή CSV
+ Εύρος
+ Άτομα
+ Μονάδες
+ Μονάδα
+ Μέλος
+ Κατηγορία
+ Όλες οι κατηγορίες
+ Ομάδα
+ Όλες οι ομάδες
+ Ρόλος
+ Όλοι οι ρόλοι
+ Δεν υπάρχουν ακόμη τύποι πιστοποίησης.
+ Δεν υπάρχουν ακόμη εγγραφές πιστοποίησης.
+ Έγκυρο
+ Λήγει σύντομα
+ Ληγμένο, σε αναστολή ή ανακληθέν
+ Ενεργό
+ Ληγμένο
+ Σε αναστολή
+ Ανακληθέν
+ Εκκρεμεί επαλήθευση
+ Εκπαιδευόμενος
+ Ενεργό
+ Ληγμένο
+ Σε αναστολή
+ Πυροσβεστική
+ Επείγουσα ιατρική
+ Έρευνα και διάσωση
+ Δασικές πυρκαγιές
+ Διαχείριση εκτάκτων αναγκών
+ Βιομηχανικό
+ Ασφάλεια
+ Ιατρικό
+ Οδηγός
+ Όχημα
+ Άλλο
+ Ο κατάλογος πιστοποιήσεων, αδειών, επιθεωρήσεων και εγκρίσεων που παρακολουθεί το τμήμα σας.
+ Ένας τύπος έχει σταθερό κωδικό, κατηγορία, εύρος (άτομο ή μονάδα), προεπιλεγμένη ισχύ και προαιρετικούς κανόνες επαλήθευσης ή ωρών μονάδων. Επιλέξτε συνηθισμένους τύπους από τη συλλογή προτύπων.
+ Απόκρυψη ανενεργών
+ Εμφάνιση ανενεργών
+ Συλλογή προτύπων
+ Συνηθισμένα διαπιστευτήρια για πυροσβεστική, επείγουσα ιατρική, έρευνα και διάσωση, δασικές πυρκαγιές, διαχείριση εκτάκτων αναγκών, βιομηχανία, ασφάλεια, οδηγούς DOT και συμμόρφωση οχημάτων. Καθένα γίνεται τύπος που κατέχει και μπορεί να επεξεργαστεί το τμήμα σας.
+ Φιλτράρισμα προτύπων…
+ Ήδη στον κατάλογό σας
+ Προσθήκη επιλεγμένων
+ {0} προστέθηκαν· παραλείφθηκαν (υπάρχουν ήδη ή απορρίφθηκαν): {1}
+ Νέος τύπος
+ Επεξεργασία τύπου
+ Κωδικός
+ Τύπος
+ Ισχύει για
+ Άτομο
+ Μονάδα
+ Ισχύς
+ {0} μήνες
+ Εγγραφές
+ Απαιτήσεις
+ Διαγραφή αυτού του τύπου πιστοποίησης;
+ Εκδούσα αρχή
+ Προκύπτει από το όνομα όταν είναι κενό
+ Σταθερό αναγνωριστικό που χρησιμοποιείται από απαιτήσεις ρόλων και ενσωματώσεις· γράμματα, ψηφία, τελείες και παύλες.
+ Μια απαίτηση ρόλου αναφέρεται σε αυτόν τον τύπο, οπότε ο κωδικός του δεν μπορεί πλέον να αλλάξει.
+ Προεπιλεγμένη ισχύς (μήνες)
+ Ώρες μονάδων ανά ανανέωση
+ Απαιτεί επαλήθευση επόπτη
+ Οι νέες εγγραφές παραμένουν σε εκκρεμότητα μέχρι να τις εγκρίνει κάποιος με δικαιώματα διαχείρισης.
+ Κατάλογος τύπων και πρότυπα
+ Ρυθμίσεις πιστοποιήσεων
+ Πώς επιβάλλονται οι απαιτήσεις ρόλων και πότε αποστέλλονται οι ειδοποιήσεις λήξης.
+ Επιβολή
+ Λειτουργία επιβολής
+ Ανενεργή – οι απαιτήσεις είναι μόνο ενημερωτικές
+ Μόνο προειδοποίηση – επιτρέπονται αλλαγές ρόλων, εμφάνιση προειδοποιήσεων, ειδοποίηση
+ Επιβολή – αποκλεισμός μη πιστοποιημένων αναθέσεων ρόλων και αφαίρεση μελών μετά την περίοδο χάριτος
+ Η επιβολή αφαιρεί μέλη από ρόλους των οποίων οι υποχρεωτικές πιστοποιήσεις έληξαν· η αφαίρεση καταγράφεται και το μέλος ειδοποιείται.
+ Η επιβολή θα αφαιρέσει μέλη από ρόλους μόλις λήξει η περίοδος χάριτος. Έχουν ρυθμιστεί {0} υποχρεωτικές απαιτήσεις. Συνέχεια;
+ Επιβεβαιώστε τη μετάβαση σε Επιβολή πριν την αποθήκευση.
+ Περίοδος χάριτος (ημέρες)
+ Ημέρες μετά τη λήξη υποχρεωτικής πιστοποίησης πριν την αφαίρεση του μέλους από τον ρόλο. Κάθε απαίτηση μπορεί να το παρακάμψει.
+ Θεώρηση εγγραφών σε εκκρεμή επαλήθευση ως έγκυρων για απαιτήσεις ρόλων
+ Ειδοποιήσεις
+ Ειδοποίηση (ημέρες πριν τη λήξη)
+ Διαχωρισμένα με κόμμα, π.χ. 60,30,14,7,1. Μία ειδοποίηση ανά εγγραφή ανά αναφερόμενη ημέρα.
+ Άμεση ειδοποίηση του κατόχου
+ Αποστολή νυχτερινής σύνοψης στους διαχειριστές τμήματος
+ Οι αποδέκτες των συμβάντων Πιστοποίηση λήγει / έληξε / Αφαίρεση ρόλου και Πιστοποίηση μονάδας ρυθμίζονται στις
+ Ρυθμίσεις ειδοποιήσεων
+ Απαιτούμενες πιστοποιήσεις
+ Απαιτούμενες πιστοποιήσεις – {0}
+ Ποιους τύπους πιστοποίησης πρέπει να κατέχει ένα μέλος για αυτόν τον ρόλο. Γραμμές με ίδιο αριθμό ομάδας είναι εναλλακτικές· οι υποχρεωτικές αποκλείουν ή αφαιρούν στη λειτουργία Επιβολής.
+ Επιστροφή στους ρόλους
+ Η επιβολή είναι ανενεργή: αυτές οι απαιτήσεις είναι ενημερωτικές μέχρι ένας διαχειριστής τμήματος ενεργοποιήσει Μόνο προειδοποίηση ή Επιβολή στις
+ Δεν υπάρχουν ακόμη τύποι πιστοποίησης για άτομα.
+ Υποχρεωτικό
+ Ομάδα εναλλακτικών
+ Οι γραμμές με τον ίδιο αριθμό ομάδας ικανοποιούνται από οποιαδήποτε από αυτές (EMT ή AEMT ή Paramedic)· αφήστε κενό για αυτόνομη απαίτηση.
+ Επιτρέπονται εκπαιδευόμενοι
+ Ειδική περίοδος χάριτος (ημέρες)
+ Προσθήκη απαίτησης
+ Επιλεξιμότητα μελών
+ Αυτός ο ρόλος δεν έχει μέλη.
+ Λείπει ή έληξε
+ Πιστοποιημένο
+ Λείπουν προαιρετικές
+ Μη πιστοποιημένο
+ Αφαίρεση στις {0}
+ Αυτά τα μέλη δεν κατέχουν τις πιστοποιήσεις που απαιτεί ο ρόλος και δεν προστέθηκαν: {0}
+ Προστέθηκαν με προειδοποίηση – λείπουν πιστοποιήσεις για αυτόν τον ρόλο: {0}
+ Το μέλος δημιουργήθηκε, αλλά ένας ή περισσότεροι ρόλοι δεν ανατέθηκαν: δεν κατέχει τις πιστοποιήσεις που απαιτούν.
+ Πιστοποιήσεις μονάδας
+ Πιστοποιήσεις και επιθεωρήσεις – {0}
+ Επιθεωρήσεις, άδεια κυκλοφορίας, ασφάλιση, εγκρίσεις και πιστοποιητικά δοκιμών με λήξη για αυτή τη μονάδα.
+ Πιστοποιήσεις και επιθεωρήσεις
+ Επιστροφή στη μονάδα
+ Προσθήκη εγγραφής
+ Δεν υπάρχουν ακόμη τύποι πιστοποίησης για μονάδες.
+ Πιστοποίηση μονάδας
+ Επαναφορά
+ Αναστολή
+ Ανάκληση
+ Διαγραφή αυτής της εγγραφής;
+ Εγγραφή πιστοποίησης
+ Αυτή η εγγραφή είναι προστατευμένη· οι αναγνωριστικές τιμές εμφανίζονται ως REDACTED χωρίς άδεια αποκάλυψης.
+ Χωρίς τύπο
+ Επαληθεύτηκε από
+ Επαλήθευση
+ Ανανέωση
+ Ορίζει νέα λήξη (και προαιρετικά νέο αριθμό), επανενεργοποιώντας ληγμένη εγγραφή.
+ Νέα ημερομηνία λήξης
+ Νέος αριθμός
+ Αφήστε κενό για να διατηρηθεί ο τρέχων
+ Σήμανση εκπαιδευόμενου
+ Αλλαγή κατάστασης
+ Μονάδες συνεχούς εκπαίδευσης
+ Προσθήκη μονάδων
+ {0} από {1} ώρες για την επόμενη ανανέωση
+ {0} ώρες καταγράφηκαν
+ Δεν καταγράφηκαν μονάδες.
+ Διαγραφή αυτής της καταχώρισης μονάδων;
+ Πιστοποίηση προς λήξη
+ Προστέθηκε πιστοποίηση
+ Πιστοποίηση ανανεώθηκε
+ Πιστοποίηση έληξε
+ Αφαίρεση ρόλου λόγω ληγμένης πιστοποίησης
+ Άλλαξε η κατάσταση πιστοποίησης
+ Πιστοποίηση μονάδας προς λήξη
+ Πιστοποίηση μονάδας έληξε
+ Σε αυτόν τον τύπο αναφέρεται απαίτηση ρόλου ή ενεργή εγγραφή και δεν μπορεί να διαγραφεί· απενεργοποιήστε τον.
+ Ο τύπος πιστοποίησης δεν βρέθηκε.
+ Αυτός ο τύπος πιστοποίησης ισχύει για το άλλο εύρος (άτομο ή μονάδα).
+ Ο τύπος χρειάζεται όνομα.
+ Η προεπιλεγμένη ισχύς πρέπει να είναι μεταξύ 1 και 600 μηνών.
+ Οι ώρες μονάδων δεν μπορούν να είναι αρνητικές.
+ Άλλος τύπος σε αυτό το τμήμα χρησιμοποιεί ήδη αυτόν τον κωδικό.
+ Ο κωδικός δεν μπορεί να αλλάξει όσο απαίτηση ρόλου αναφέρεται σε αυτόν τον τύπο.
+ Το εύρος δεν μπορεί να αλλάξει αφού υπάρχουν εγγραφές αυτού του τύπου.
+ Το πρότυπο δεν βρέθηκε.
+ Η εγγραφή πιστοποίησης δεν βρέθηκε.
+ Αυτή η κατάσταση δεν μπορεί να οριστεί χειροκίνητα.
+ Μόνο εγγραφή σε εκκρεμή επαλήθευση μπορεί να επαληθευτεί.
+ Αυτός ο τύπος λήγει, οπότε απαιτείται νέα ημερομηνία λήξης.
+ Η νέα λήξη πρέπει να είναι μεταγενέστερη της τρέχουσας.
+ Οι ώρες μονάδων πρέπει να είναι μεγαλύτερες από μηδέν.
+ Η καταχώριση μονάδων δεν βρέθηκε.
+ Η μονάδα δεν βρέθηκε σε αυτό το τμήμα.
+ Ο ρόλος δεν βρέθηκε σε αυτό το τμήμα.
+ Οι ημέρες χάριτος πρέπει να είναι μεταξύ 0 και 3650.
+ Άγνωστη λειτουργία επιβολής.
+ Οι ημέρες προειδοποίησης πρέπει να είναι θετικές και έως ένα έτος.
+ Τα προστατευμένα δεδομένα αυτής της εγγραφής δεν ήταν δυνατό να ασφαλιστούν, οπότε η αλλαγή δεν αποθηκεύτηκε.
+ Το μέλος δεν κατέχει τις πιστοποιήσεις που απαιτεί αυτός ο ρόλος.
+ Αυτός ο τύπος αρχείου δεν μπορεί να μεταφορτωθεί.
+ Το αρχείο είναι μεγαλύτερο από 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.en.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.en.resx
new file mode 100644
index 00000000..dfeb8b05
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.en.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certifications
+ Manage certifications
+ View certifications
+ Manage certification setup
+ Create, edit, suspend, verify and delete other members' certification records and credits, and unit certification records.
+ View other members' certification records, unit certifications and the expiry dashboard. Members always see their own records.
+ Manage certification types, role requirements and the department certification settings.
+ Dashboard
+ Who holds what, what is expiring and what has lapsed, for people and units.
+ Certification types
+ Settings
+ Compliance report
+ Printable person × type and unit × type certification matrices with expiry status.
+ Run on {0}
+ Saved.
+ The change could not be saved.
+ Save
+ Cancel
+ Edit
+ Active
+ Inactive
+ Description
+ Notes
+ Status
+ Date
+ Hours
+ File
+ PDF, image or office document, up to 10 MB.
+ Reason
+ Number
+ Issued by
+ Issued on
+ Expires on
+ Received on
+ Never expires
+ Days until expiry
+ {0} days left
+ {0} days ago
+ Expired
+ Expiring within {0} days
+ Suspended or revoked
+ Export CSV
+ Scope
+ People
+ Units
+ Unit
+ Member
+ Category
+ All categories
+ Group
+ All groups
+ Role
+ All roles
+ No certification types yet.
+ No certification records yet.
+ Valid
+ Expiring soon
+ Expired, suspended or revoked
+ Active
+ Expired
+ Suspended
+ Revoked
+ Pending verification
+ Trainee
+ Active
+ Expired
+ Suspended
+ Fire
+ EMS
+ Search and rescue
+ Wildland
+ Emergency management
+ Industrial
+ Security
+ Medical
+ Driver
+ Vehicle
+ Other
+ The catalog of certifications, licenses, inspections and permits your department tracks.
+ A type has a stable code, a category, a scope (person or unit), a default validity and optional verification or credit-hour rules. Pick common ones from the template gallery.
+ Hide inactive
+ Show inactive
+ Template gallery
+ Common credentials across fire, EMS, SAR, wildland, emergency management, industrial, security, DOT driver and vehicle compliance. Each one becomes a type your department owns and can edit.
+ Filter templates…
+ Already in your catalog
+ Add selected
+ {0} added; skipped (already present or refused): {1}
+ New type
+ Edit type
+ Code
+ Type
+ Applies to
+ Person
+ Unit
+ Validity
+ {0} months
+ Records
+ Requirements
+ Delete this certification type?
+ Issuing authority
+ Derived from the name when empty
+ Stable identifier used by role requirements and integrations; letters, digits, dots and dashes.
+ A role requirement references this type, so its code can no longer change.
+ Default validity (months)
+ Credit hours per renewal
+ Requires supervisor verification
+ New records stay pending until someone with manage rights signs them off.
+ Typed catalog and templates
+ Certification settings
+ How role requirements are enforced and when expiry notices go out.
+ Enforcement
+ Enforcement mode
+ Off – requirements are informational only
+ Warn only – allow role changes, show warnings, notify
+ Enforce – block unqualified role adds and remove members after the grace period
+ Enforce removes members from roles whose mandatory certifications lapsed; the removal is audited and the member is notified.
+ Enforce will remove members from roles once their grace period ends. {0} mandatory requirement(s) are configured. Continue?
+ Confirm the switch to Enforce before saving.
+ Grace period (days)
+ Days after a mandatory certification lapses before the member is removed from the role. Each requirement can override it.
+ Treat records pending verification as valid for role requirements
+ Notifications
+ Notify (days before expiry)
+ Comma-separated, e.g. 60,30,14,7,1. One notice per record per listed day.
+ Notify the holder directly
+ Send department administrators a nightly digest
+ Audiences for the Certification Expiring / Expired / Role Removed and Unit Certification events are configured under
+ Notification settings
+ Required certifications
+ Required certifications – {0}
+ Which certification types a member must hold for this role. Rows sharing a group number are alternatives; mandatory rows block or remove under Enforce.
+ Back to roles
+ Enforcement is off: these requirements are informational until a department administrator turns on Warn only or Enforce in
+ No person-scoped certification types exist yet.
+ Mandatory
+ Any-of group
+ Rows with the same group number are satisfied by any one of them (EMT or AEMT or Paramedic); leave blank for a stand-alone requirement.
+ Allow trainee
+ Grace override (days)
+ Add requirement
+ Member eligibility
+ This role has no members.
+ Missing or lapsed
+ Qualified
+ Optional missing
+ Not qualified
+ Removal on {0}
+ These members do not hold the certifications this role requires and were not added: {0}
+ Added with a warning – missing certifications for this role: {0}
+ The member was created, but one or more roles were not assigned: the member does not hold the certifications those roles require.
+ Unit certifications
+ Certifications and inspections – {0}
+ Inspections, registration, insurance, permits and test certificates with an expiry for this unit.
+ Certifications & inspections
+ Back to unit
+ Add record
+ No unit-scoped certification types exist yet.
+ Unit certification
+ Reinstate
+ Suspend
+ Revoke
+ Delete this record?
+ Certification record
+ This record is protected; identifying values show as REDACTED without a reveal grant.
+ Untyped
+ Verified by
+ Verify
+ Renew
+ Sets a new expiry (and optionally a new number), reactivating an expired record.
+ New expiry date
+ New number
+ Leave empty to keep the current one
+ Mark trainee
+ Change status
+ Continuing-education credits
+ Add credit
+ {0} of {1} hours toward the next renewal
+ {0} hours recorded
+ No credits recorded.
+ Delete this credit entry?
+ Certification expiring
+ Certification added
+ Certification renewed
+ Certification expired
+ Role removed for lapsed certification
+ Certification status changed
+ Unit certification expiring
+ Unit certification expired
+ This type is referenced by a role requirement or a live record and cannot be deleted; deactivate it instead.
+ The certification type was not found.
+ That certification type applies to the other scope (person vs. unit).
+ A type needs a name.
+ Default validity must be between 1 and 600 months.
+ Credit hours cannot be negative.
+ Another type in this department already uses that code.
+ The code cannot change while a role requirement references this type.
+ The scope cannot change once records of this type exist.
+ The template was not found.
+ The certification record was not found.
+ That status cannot be set by hand.
+ Only a record pending verification can be verified.
+ This type expires, so a new expiry date is required.
+ The new expiry must be later than the current one.
+ Credit hours must be greater than zero.
+ The credit entry was not found.
+ The unit was not found in this department.
+ The role was not found in this department.
+ Grace days must be between 0 and 3650.
+ Unknown enforcement mode.
+ Lead days must be positive and no more than a year.
+ Protected data on this record could not be secured, so the change was not saved.
+ The member does not hold the certifications this role requires.
+ That file type cannot be uploaded.
+ The file is larger than 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.es.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.es.resx
new file mode 100644
index 00000000..cd207c65
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.es.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certificaciones
+ Gestionar certificaciones
+ Ver certificaciones
+ Gestionar la configuración de certificaciones
+ Crear, editar, suspender, verificar y eliminar los registros de certificación y créditos de otros miembros, así como los registros de certificación de unidades.
+ Ver los registros de certificación de otros miembros, las certificaciones de unidades y el panel de vencimientos. Cada miembro siempre ve sus propios registros.
+ Gestionar los tipos de certificación, los requisitos por rol y la configuración de certificaciones del departamento.
+ Panel
+ Quién tiene qué, qué vence y qué ha caducado, para personas y unidades.
+ Tipos de certificación
+ Configuración
+ Informe de cumplimiento
+ Matrices imprimibles persona × tipo y unidad × tipo con el estado de vencimiento.
+ Generado el {0}
+ Guardado.
+ No se pudo guardar el cambio.
+ Guardar
+ Cancelar
+ Editar
+ Activo
+ Inactivo
+ Descripción
+ Notas
+ Estado
+ Fecha
+ Horas
+ Archivo
+ PDF, imagen o documento de oficina, hasta 10 MB.
+ Motivo
+ Número
+ Emitido por
+ Fecha de emisión
+ Vence el
+ Recibido el
+ Nunca vence
+ Días hasta el vencimiento
+ quedan {0} días
+ hace {0} días
+ Vencido
+ Vence en {0} días
+ Suspendido o revocado
+ Exportar CSV
+ Ámbito
+ Personas
+ Unidades
+ Unidad
+ Miembro
+ Categoría
+ Todas las categorías
+ Grupo
+ Todos los grupos
+ Rol
+ Todos los roles
+ Aún no hay tipos de certificación.
+ Aún no hay registros de certificación.
+ Válido
+ Vence pronto
+ Vencido, suspendido o revocado
+ Activo
+ Vencido
+ Suspendido
+ Revocado
+ Pendiente de verificación
+ En formación
+ Activo
+ Vencido
+ Suspendido
+ Bomberos
+ Emergencias médicas
+ Búsqueda y rescate
+ Incendios forestales
+ Gestión de emergencias
+ Industrial
+ Seguridad
+ Médico
+ Conductor
+ Vehículo
+ Otro
+ El catálogo de certificaciones, licencias, inspecciones y permisos que su departamento controla.
+ Un tipo tiene un código estable, una categoría, un ámbito (persona o unidad), una validez por defecto y reglas opcionales de verificación u horas de crédito. Elija los más comunes en la galería de plantillas.
+ Ocultar inactivos
+ Mostrar inactivos
+ Galería de plantillas
+ Credenciales comunes de bomberos, emergencias médicas, búsqueda y rescate, incendios forestales, gestión de emergencias, industria, seguridad, conductores DOT y cumplimiento de vehículos. Cada una se convierte en un tipo propio de su departamento que puede editar.
+ Filtrar plantillas…
+ Ya está en su catálogo
+ Añadir seleccionados
+ {0} añadidos; omitidos (ya existen o rechazados): {1}
+ Nuevo tipo
+ Editar tipo
+ Código
+ Tipo
+ Se aplica a
+ Persona
+ Unidad
+ Validez
+ {0} meses
+ Registros
+ Requisitos
+ ¿Eliminar este tipo de certificación?
+ Autoridad emisora
+ Se deriva del nombre si está vacío
+ Identificador estable usado por los requisitos de rol y las integraciones; letras, dígitos, puntos y guiones.
+ Un requisito de rol hace referencia a este tipo, por lo que su código ya no puede cambiar.
+ Validez por defecto (meses)
+ Horas de crédito por renovación
+ Requiere verificación del supervisor
+ Los registros nuevos quedan pendientes hasta que alguien con derechos de gestión los apruebe.
+ Catálogo tipificado y plantillas
+ Configuración de certificaciones
+ Cómo se aplican los requisitos de rol y cuándo se envían los avisos de vencimiento.
+ Aplicación
+ Modo de aplicación
+ Desactivado: los requisitos son solo informativos
+ Solo avisar: permitir cambios de rol, mostrar avisos y notificar
+ Aplicar: bloquear asignaciones de rol no cualificadas y retirar miembros tras el periodo de gracia
+ Aplicar retira a los miembros de los roles cuyas certificaciones obligatorias caducaron; la retirada se audita y se notifica al miembro.
+ Aplicar retirará a los miembros de los roles cuando termine su periodo de gracia. Hay {0} requisito(s) obligatorio(s) configurado(s). ¿Continuar?
+ Confirme el cambio a Aplicar antes de guardar.
+ Periodo de gracia (días)
+ Días tras la caducidad de una certificación obligatoria antes de retirar al miembro del rol. Cada requisito puede anularlo.
+ Considerar válidos para los requisitos de rol los registros pendientes de verificación
+ Notificaciones
+ Avisar (días antes del vencimiento)
+ Separados por comas, p. ej. 60,30,14,7,1. Un aviso por registro y día indicado.
+ Notificar directamente al titular
+ Enviar un resumen nocturno a los administradores del departamento
+ Los destinatarios de los eventos Certificación por vencer / vencida / Rol retirado y Certificación de unidad se configuran en
+ Configuración de notificaciones
+ Certificaciones requeridas
+ Certificaciones requeridas – {0}
+ Qué tipos de certificación debe tener un miembro para este rol. Las filas con el mismo número de grupo son alternativas; las obligatorias bloquean o retiran en modo Aplicar.
+ Volver a roles
+ La aplicación está desactivada: estos requisitos son informativos hasta que un administrador del departamento active Solo avisar o Aplicar en
+ Aún no existen tipos de certificación de persona.
+ Obligatorio
+ Grupo alternativo
+ Las filas con el mismo número de grupo se cumplen con cualquiera de ellas (EMT o AEMT o Paramédico); deje en blanco para un requisito independiente.
+ Permitir en formación
+ Gracia personalizada (días)
+ Añadir requisito
+ Elegibilidad de los miembros
+ Este rol no tiene miembros.
+ Faltante o caducado
+ Cualificado
+ Faltan opcionales
+ No cualificado
+ Retirada el {0}
+ Estos miembros no tienen las certificaciones que requiere este rol y no se añadieron: {0}
+ Añadidos con aviso: faltan certificaciones para este rol: {0}
+ El miembro se creó, pero uno o más roles no se asignaron: no tiene las certificaciones que esos roles requieren.
+ Certificaciones de unidad
+ Certificaciones e inspecciones – {0}
+ Inspecciones, matriculación, seguro, permisos y certificados de prueba con vencimiento para esta unidad.
+ Certificaciones e inspecciones
+ Volver a la unidad
+ Añadir registro
+ Aún no existen tipos de certificación de unidad.
+ Certificación de unidad
+ Reactivar
+ Suspender
+ Revocar
+ ¿Eliminar este registro?
+ Registro de certificación
+ Este registro está protegido; los valores identificativos se muestran como REDACTED sin una autorización de revelado.
+ Sin tipo
+ Verificado por
+ Verificar
+ Renovar
+ Establece un nuevo vencimiento (y opcionalmente un nuevo número), reactivando un registro vencido.
+ Nueva fecha de vencimiento
+ Nuevo número
+ Deje vacío para conservar el actual
+ Marcar en formación
+ Cambiar estado
+ Créditos de formación continua
+ Añadir crédito
+ {0} de {1} horas para la próxima renovación
+ {0} horas registradas
+ No hay créditos registrados.
+ ¿Eliminar esta entrada de crédito?
+ Certificación por vencer
+ Certificación añadida
+ Certificación renovada
+ Certificación vencida
+ Rol retirado por certificación caducada
+ Estado de certificación cambiado
+ Certificación de unidad por vencer
+ Certificación de unidad vencida
+ Un requisito de rol o un registro activo hace referencia a este tipo y no se puede eliminar; desactívelo en su lugar.
+ No se encontró el tipo de certificación.
+ Ese tipo de certificación corresponde al otro ámbito (persona frente a unidad).
+ Un tipo necesita un nombre.
+ La validez por defecto debe estar entre 1 y 600 meses.
+ Las horas de crédito no pueden ser negativas.
+ Otro tipo de este departamento ya usa ese código.
+ El código no puede cambiar mientras un requisito de rol haga referencia a este tipo.
+ El ámbito no puede cambiar una vez que existen registros de este tipo.
+ No se encontró la plantilla.
+ No se encontró el registro de certificación.
+ Ese estado no se puede establecer manualmente.
+ Solo se puede verificar un registro pendiente de verificación.
+ Este tipo vence, por lo que se requiere una nueva fecha de vencimiento.
+ El nuevo vencimiento debe ser posterior al actual.
+ Las horas de crédito deben ser mayores que cero.
+ No se encontró la entrada de crédito.
+ No se encontró la unidad en este departamento.
+ No se encontró el rol en este departamento.
+ Los días de gracia deben estar entre 0 y 3650.
+ Modo de aplicación desconocido.
+ Los días de antelación deben ser positivos y no superar un año.
+ No se pudieron proteger los datos de este registro, por lo que el cambio no se guardó.
+ El miembro no tiene las certificaciones que requiere este rol.
+ Ese tipo de archivo no se puede subir.
+ El archivo supera los 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.fr.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.fr.resx
new file mode 100644
index 00000000..64094a1e
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.fr.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certifications
+ Gérer les certifications
+ Voir les certifications
+ Gérer le paramétrage des certifications
+ Créer, modifier, suspendre, vérifier et supprimer les certifications et crédits des autres membres, ainsi que les certifications d'unités.
+ Voir les certifications des autres membres, celles des unités et le tableau de bord des expirations. Chaque membre voit toujours ses propres certifications.
+ Gérer les types de certification, les exigences par rôle et les paramètres de certification du service.
+ Tableau de bord
+ Qui détient quoi, ce qui expire et ce qui est échu, pour les personnes et les unités.
+ Types de certification
+ Paramètres
+ Rapport de conformité
+ Matrices imprimables personne × type et unité × type avec l'état d'expiration.
+ Généré le {0}
+ Enregistré.
+ La modification n'a pas pu être enregistrée.
+ Enregistrer
+ Annuler
+ Modifier
+ Actif
+ Inactif
+ Description
+ Notes
+ Statut
+ Date
+ Heures
+ Fichier
+ PDF, image ou document bureautique, jusqu'à 10 Mo.
+ Motif
+ Numéro
+ Délivré par
+ Délivré le
+ Expire le
+ Reçu le
+ N'expire jamais
+ Jours avant expiration
+ {0} jours restants
+ il y a {0} jours
+ Expiré
+ Expire dans {0} jours
+ Suspendu ou révoqué
+ Exporter en CSV
+ Portée
+ Personnes
+ Unités
+ Unité
+ Membre
+ Catégorie
+ Toutes les catégories
+ Groupe
+ Tous les groupes
+ Rôle
+ Tous les rôles
+ Aucun type de certification pour le moment.
+ Aucune certification enregistrée pour le moment.
+ Valide
+ Expire bientôt
+ Expiré, suspendu ou révoqué
+ Actif
+ Expiré
+ Suspendu
+ Révoqué
+ En attente de vérification
+ Stagiaire
+ Actif
+ Expiré
+ Suspendu
+ Incendie
+ Secours médicaux
+ Recherche et sauvetage
+ Feux de forêt
+ Gestion des urgences
+ Industriel
+ Sécurité
+ Médical
+ Conducteur
+ Véhicule
+ Autre
+ Le catalogue des certifications, licences, inspections et permis suivis par votre service.
+ Un type a un code stable, une catégorie, une portée (personne ou unité), une validité par défaut et des règles optionnelles de vérification ou d'heures de crédit. Choisissez les plus courants dans la galerie de modèles.
+ Masquer les inactifs
+ Afficher les inactifs
+ Galerie de modèles
+ Certifications courantes en incendie, secours médicaux, recherche et sauvetage, feux de forêt, gestion des urgences, industrie, sécurité, conducteurs DOT et conformité des véhicules. Chacune devient un type que votre service possède et peut modifier.
+ Filtrer les modèles…
+ Déjà dans votre catalogue
+ Ajouter la sélection
+ {0} ajoutés ; ignorés (déjà présents ou refusés) : {1}
+ Nouveau type
+ Modifier le type
+ Code
+ Type
+ S'applique à
+ Personne
+ Unité
+ Validité
+ {0} mois
+ Enregistrements
+ Exigences
+ Supprimer ce type de certification ?
+ Autorité émettrice
+ Dérivé du nom si vide
+ Identifiant stable utilisé par les exigences de rôle et les intégrations ; lettres, chiffres, points et tirets.
+ Une exigence de rôle référence ce type ; son code ne peut plus être modifié.
+ Validité par défaut (mois)
+ Heures de crédit par renouvellement
+ Nécessite une vérification par un responsable
+ Les nouvelles certifications restent en attente jusqu'à validation par une personne ayant les droits de gestion.
+ Catalogue typé et modèles
+ Paramètres des certifications
+ Comment les exigences de rôle sont appliquées et quand les avis d'expiration sont envoyés.
+ Application
+ Mode d'application
+ Désactivé – les exigences sont purement informatives
+ Avertir seulement – autoriser les changements de rôle, afficher des avertissements, notifier
+ Appliquer – bloquer les ajouts de rôle non qualifiés et retirer les membres après le délai de grâce
+ Appliquer retire les membres des rôles dont les certifications obligatoires sont échues ; le retrait est audité et le membre est notifié.
+ Appliquer retirera les membres des rôles à la fin de leur délai de grâce. {0} exigence(s) obligatoire(s) configurée(s). Continuer ?
+ Confirmez le passage en mode Appliquer avant d'enregistrer.
+ Délai de grâce (jours)
+ Jours après l'échéance d'une certification obligatoire avant le retrait du membre du rôle. Chaque exigence peut le remplacer.
+ Considérer les certifications en attente de vérification comme valides pour les exigences de rôle
+ Notifications
+ Notifier (jours avant l'expiration)
+ Séparés par des virgules, p. ex. 60,30,14,7,1. Un avis par certification et par jour listé.
+ Notifier directement le titulaire
+ Envoyer un résumé nocturne aux administrateurs du service
+ Les destinataires des événements Certification expirant / expirée / Rôle retiré et Certification d'unité se configurent sous
+ Paramètres de notification
+ Certifications requises
+ Certifications requises – {0}
+ Les types de certification qu'un membre doit détenir pour ce rôle. Les lignes partageant un numéro de groupe sont des alternatives ; les lignes obligatoires bloquent ou retirent en mode Appliquer.
+ Retour aux rôles
+ L'application est désactivée : ces exigences restent informatives jusqu'à ce qu'un administrateur active Avertir seulement ou Appliquer dans
+ Aucun type de certification pour les personnes n'existe encore.
+ Obligatoire
+ Groupe alternatif
+ Les lignes portant le même numéro de groupe sont satisfaites par l'une d'elles (EMT ou AEMT ou Paramedic) ; laissez vide pour une exigence indépendante.
+ Autoriser les stagiaires
+ Délai de grâce spécifique (jours)
+ Ajouter une exigence
+ Éligibilité des membres
+ Ce rôle n'a aucun membre.
+ Manquant ou échu
+ Qualifié
+ Optionnelles manquantes
+ Non qualifié
+ Retrait le {0}
+ Ces membres ne détiennent pas les certifications requises pour ce rôle et n'ont pas été ajoutés : {0}
+ Ajoutés avec un avertissement – certifications manquantes pour ce rôle : {0}
+ Le membre a été créé, mais un ou plusieurs rôles n'ont pas été attribués : il ne détient pas les certifications requises.
+ Certifications d'unité
+ Certifications et inspections – {0}
+ Inspections, immatriculation, assurance, permis et certificats d'essai avec expiration pour cette unité.
+ Certifications et inspections
+ Retour à l'unité
+ Ajouter un enregistrement
+ Aucun type de certification pour les unités n'existe encore.
+ Certification d'unité
+ Rétablir
+ Suspendre
+ Révoquer
+ Supprimer cet enregistrement ?
+ Fiche de certification
+ Cette fiche est protégée ; les valeurs identifiantes s'affichent comme REDACTED sans autorisation de révélation.
+ Sans type
+ Vérifié par
+ Vérifier
+ Renouveler
+ Fixe une nouvelle expiration (et éventuellement un nouveau numéro), réactivant une certification expirée.
+ Nouvelle date d'expiration
+ Nouveau numéro
+ Laisser vide pour conserver l'actuel
+ Marquer stagiaire
+ Changer le statut
+ Crédits de formation continue
+ Ajouter un crédit
+ {0} heures sur {1} pour le prochain renouvellement
+ {0} heures enregistrées
+ Aucun crédit enregistré.
+ Supprimer cette entrée de crédit ?
+ Certification arrivant à expiration
+ Certification ajoutée
+ Certification renouvelée
+ Certification expirée
+ Rôle retiré pour certification échue
+ Statut de certification modifié
+ Certification d'unité arrivant à expiration
+ Certification d'unité expirée
+ Ce type est référencé par une exigence de rôle ou un enregistrement actif et ne peut pas être supprimé ; désactivez-le plutôt.
+ Le type de certification est introuvable.
+ Ce type de certification s'applique à l'autre portée (personne ou unité).
+ Un type doit avoir un nom.
+ La validité par défaut doit être comprise entre 1 et 600 mois.
+ Les heures de crédit ne peuvent pas être négatives.
+ Un autre type de ce service utilise déjà ce code.
+ Le code ne peut pas changer tant qu'une exigence de rôle référence ce type.
+ La portée ne peut plus changer dès lors que des enregistrements de ce type existent.
+ Le modèle est introuvable.
+ La certification est introuvable.
+ Ce statut ne peut pas être défini manuellement.
+ Seule une certification en attente de vérification peut être vérifiée.
+ Ce type expire ; une nouvelle date d'expiration est requise.
+ La nouvelle expiration doit être postérieure à l'actuelle.
+ Les heures de crédit doivent être supérieures à zéro.
+ L'entrée de crédit est introuvable.
+ L'unité est introuvable dans ce service.
+ Le rôle est introuvable dans ce service.
+ Le délai de grâce doit être compris entre 0 et 3650 jours.
+ Mode d'application inconnu.
+ Les jours d'anticipation doivent être positifs et ne pas dépasser un an.
+ Les données protégées de cet enregistrement n'ont pas pu être sécurisées ; la modification n'a pas été enregistrée.
+ Le membre ne détient pas les certifications requises pour ce rôle.
+ Ce type de fichier ne peut pas être téléversé.
+ Le fichier dépasse 10 Mo.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.it.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.it.resx
new file mode 100644
index 00000000..061b97f2
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.it.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certificazioni
+ Gestire le certificazioni
+ Visualizzare le certificazioni
+ Gestire la configurazione delle certificazioni
+ Creare, modificare, sospendere, verificare ed eliminare i record di certificazione e i crediti di altri membri e i record di certificazione delle unità.
+ Visualizzare i record di certificazione di altri membri, le certificazioni delle unità e il cruscotto delle scadenze. Ogni membro vede sempre i propri record.
+ Gestire i tipi di certificazione, i requisiti dei ruoli e le impostazioni di certificazione del dipartimento.
+ Cruscotto
+ Chi possiede cosa, cosa sta per scadere e cosa è scaduto, per persone e unità.
+ Tipi di certificazione
+ Impostazioni
+ Rapporto di conformità
+ Matrici stampabili persona × tipo e unità × tipo con lo stato di scadenza.
+ Generato il {0}
+ Salvato.
+ La modifica non è stata salvata.
+ Salva
+ Annulla
+ Modifica
+ Attivo
+ Inattivo
+ Descrizione
+ Note
+ Stato
+ Data
+ Ore
+ File
+ PDF, immagine o documento office, fino a 10 MB.
+ Motivo
+ Numero
+ Rilasciato da
+ Rilasciato il
+ Scade il
+ Ricevuto il
+ Non scade mai
+ Giorni alla scadenza
+ {0} giorni rimanenti
+ {0} giorni fa
+ Scaduto
+ In scadenza entro {0} giorni
+ Sospeso o revocato
+ Esporta CSV
+ Ambito
+ Persone
+ Unità
+ Unità
+ Membro
+ Categoria
+ Tutte le categorie
+ Gruppo
+ Tutti i gruppi
+ Ruolo
+ Tutti i ruoli
+ Nessun tipo di certificazione ancora.
+ Nessun record di certificazione ancora.
+ Valido
+ In scadenza
+ Scaduto, sospeso o revocato
+ Attivo
+ Scaduto
+ Sospeso
+ Revocato
+ In attesa di verifica
+ Tirocinante
+ Attivo
+ Scaduto
+ Sospeso
+ Antincendio
+ Soccorso sanitario
+ Ricerca e soccorso
+ Incendi boschivi
+ Gestione delle emergenze
+ Industriale
+ Sicurezza
+ Medico
+ Autista
+ Veicolo
+ Altro
+ Il catalogo di certificazioni, licenze, ispezioni e permessi che il dipartimento monitora.
+ Un tipo ha un codice stabile, una categoria, un ambito (persona o unità), una validità predefinita e regole opzionali di verifica o ore di credito. Scegli quelli comuni dalla galleria dei modelli.
+ Nascondi inattivi
+ Mostra inattivi
+ Galleria dei modelli
+ Credenziali comuni per antincendio, soccorso sanitario, ricerca e soccorso, incendi boschivi, gestione delle emergenze, industria, sicurezza, conducenti DOT e conformità dei veicoli. Ognuna diventa un tipo che il dipartimento possiede e può modificare.
+ Filtra i modelli…
+ Già nel catalogo
+ Aggiungi selezionati
+ {0} aggiunti; saltati (già presenti o rifiutati): {1}
+ Nuovo tipo
+ Modifica tipo
+ Codice
+ Tipo
+ Si applica a
+ Persona
+ Unità
+ Validità
+ {0} mesi
+ Record
+ Requisiti
+ Eliminare questo tipo di certificazione?
+ Ente emittente
+ Derivato dal nome se vuoto
+ Identificatore stabile usato da requisiti dei ruoli e integrazioni; lettere, cifre, punti e trattini.
+ Un requisito di ruolo fa riferimento a questo tipo, quindi il codice non può più cambiare.
+ Validità predefinita (mesi)
+ Ore di credito per rinnovo
+ Richiede la verifica di un supervisore
+ I nuovi record restano in attesa finché qualcuno con diritti di gestione non li approva.
+ Catalogo tipizzato e modelli
+ Impostazioni certificazioni
+ Come vengono applicati i requisiti dei ruoli e quando partono gli avvisi di scadenza.
+ Applicazione
+ Modalità di applicazione
+ Disattivata – i requisiti sono solo informativi
+ Solo avvisi – consenti le modifiche ai ruoli, mostra avvisi, notifica
+ Applica – blocca le assegnazioni di ruolo non qualificate e rimuovi i membri dopo il periodo di tolleranza
+ Applica rimuove i membri dai ruoli le cui certificazioni obbligatorie sono scadute; la rimozione viene registrata e il membro notificato.
+ Applica rimuoverà i membri dai ruoli al termine del periodo di tolleranza. Sono configurati {0} requisiti obbligatori. Continuare?
+ Conferma il passaggio ad Applica prima di salvare.
+ Periodo di tolleranza (giorni)
+ Giorni dopo la scadenza di una certificazione obbligatoria prima che il membro venga rimosso dal ruolo. Ogni requisito può sovrascriverlo.
+ Considera validi per i requisiti dei ruoli i record in attesa di verifica
+ Notifiche
+ Notifica (giorni prima della scadenza)
+ Separati da virgola, ad es. 60,30,14,7,1. Un avviso per record per ogni giorno elencato.
+ Notifica direttamente il titolare
+ Invia un riepilogo notturno agli amministratori del dipartimento
+ I destinatari degli eventi Certificazione in scadenza / scaduta / Ruolo rimosso e Certificazione unità si configurano in
+ Impostazioni di notifica
+ Certificazioni richieste
+ Certificazioni richieste – {0}
+ Quali tipi di certificazione un membro deve possedere per questo ruolo. Le righe con lo stesso numero di gruppo sono alternative; quelle obbligatorie bloccano o rimuovono in modalità Applica.
+ Torna ai ruoli
+ L'applicazione è disattivata: questi requisiti sono informativi finché un amministratore non attiva Solo avvisi o Applica in
+ Non esistono ancora tipi di certificazione per persone.
+ Obbligatorio
+ Gruppo alternativo
+ Le righe con lo stesso numero di gruppo sono soddisfatte da una qualsiasi di esse (EMT o AEMT o Paramedico); lascia vuoto per un requisito autonomo.
+ Consenti tirocinanti
+ Tolleranza specifica (giorni)
+ Aggiungi requisito
+ Idoneità dei membri
+ Questo ruolo non ha membri.
+ Mancante o scaduto
+ Qualificato
+ Opzionali mancanti
+ Non qualificato
+ Rimozione il {0}
+ Questi membri non possiedono le certificazioni richieste da questo ruolo e non sono stati aggiunti: {0}
+ Aggiunti con un avviso – certificazioni mancanti per questo ruolo: {0}
+ Il membro è stato creato, ma uno o più ruoli non sono stati assegnati: non possiede le certificazioni richieste da quei ruoli.
+ Certificazioni unità
+ Certificazioni e ispezioni – {0}
+ Ispezioni, immatricolazione, assicurazione, permessi e certificati di prova con scadenza per questa unità.
+ Certificazioni e ispezioni
+ Torna all'unità
+ Aggiungi record
+ Non esistono ancora tipi di certificazione per unità.
+ Certificazione unità
+ Ripristina
+ Sospendi
+ Revoca
+ Eliminare questo record?
+ Record di certificazione
+ Questo record è protetto; i valori identificativi appaiono come REDACTED senza un permesso di rivelazione.
+ Senza tipo
+ Verificato da
+ Verifica
+ Rinnova
+ Imposta una nuova scadenza (e opzionalmente un nuovo numero), riattivando un record scaduto.
+ Nuova data di scadenza
+ Nuovo numero
+ Lascia vuoto per mantenere l'attuale
+ Segna tirocinante
+ Cambia stato
+ Crediti di formazione continua
+ Aggiungi credito
+ {0} di {1} ore verso il prossimo rinnovo
+ {0} ore registrate
+ Nessun credito registrato.
+ Eliminare questa voce di credito?
+ Certificazione in scadenza
+ Certificazione aggiunta
+ Certificazione rinnovata
+ Certificazione scaduta
+ Ruolo rimosso per certificazione scaduta
+ Stato certificazione modificato
+ Certificazione unità in scadenza
+ Certificazione unità scaduta
+ Questo tipo è referenziato da un requisito di ruolo o da un record attivo e non può essere eliminato; disattivalo invece.
+ Tipo di certificazione non trovato.
+ Quel tipo di certificazione si applica all'altro ambito (persona o unità).
+ Un tipo ha bisogno di un nome.
+ La validità predefinita deve essere tra 1 e 600 mesi.
+ Le ore di credito non possono essere negative.
+ Un altro tipo in questo dipartimento usa già quel codice.
+ Il codice non può cambiare finché un requisito di ruolo fa riferimento a questo tipo.
+ L'ambito non può cambiare una volta che esistono record di questo tipo.
+ Modello non trovato.
+ Record di certificazione non trovato.
+ Quello stato non può essere impostato manualmente.
+ Solo un record in attesa di verifica può essere verificato.
+ Questo tipo scade, quindi è richiesta una nuova data di scadenza.
+ La nuova scadenza deve essere successiva a quella attuale.
+ Le ore di credito devono essere maggiori di zero.
+ Voce di credito non trovata.
+ L'unità non è stata trovata in questo dipartimento.
+ Il ruolo non è stato trovato in questo dipartimento.
+ I giorni di tolleranza devono essere tra 0 e 3650.
+ Modalità di applicazione sconosciuta.
+ I giorni di anticipo devono essere positivi e non superare un anno.
+ I dati protetti di questo record non sono stati messi in sicurezza; la modifica non è stata salvata.
+ Il membro non possiede le certificazioni richieste da questo ruolo.
+ Questo tipo di file non può essere caricato.
+ Il file supera i 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.pl.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.pl.resx
new file mode 100644
index 00000000..c51dabc3
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.pl.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certyfikaty
+ Zarządzaj certyfikatami
+ Przeglądaj certyfikaty
+ Zarządzaj konfiguracją certyfikatów
+ Tworzenie, edycja, zawieszanie, weryfikacja i usuwanie rekordów certyfikatów i punktów innych członków oraz rekordów certyfikatów jednostek.
+ Przeglądanie rekordów certyfikatów innych członków, certyfikatów jednostek i pulpitu wygaśnięć. Członkowie zawsze widzą własne rekordy.
+ Zarządzanie typami certyfikatów, wymaganiami ról i ustawieniami certyfikatów oddziału.
+ Pulpit
+ Kto co posiada, co wygasa i co wygasło – dla osób i jednostek.
+ Typy certyfikatów
+ Ustawienia
+ Raport zgodności
+ Drukowalne macierze osoba × typ i jednostka × typ ze stanem ważności.
+ Wygenerowano {0}
+ Zapisano.
+ Nie udało się zapisać zmiany.
+ Zapisz
+ Anuluj
+ Edytuj
+ Aktywny
+ Nieaktywny
+ Opis
+ Notatki
+ Status
+ Data
+ Godziny
+ Plik
+ PDF, obraz lub dokument biurowy, do 10 MB.
+ Powód
+ Numer
+ Wydany przez
+ Data wydania
+ Wygasa
+ Otrzymano
+ Nigdy nie wygasa
+ Dni do wygaśnięcia
+ pozostało {0} dni
+ {0} dni temu
+ Wygasłe
+ Wygasa w ciągu {0} dni
+ Zawieszone lub cofnięte
+ Eksportuj CSV
+ Zakres
+ Osoby
+ Jednostki
+ Jednostka
+ Członek
+ Kategoria
+ Wszystkie kategorie
+ Grupa
+ Wszystkie grupy
+ Rola
+ Wszystkie role
+ Brak typów certyfikatów.
+ Brak rekordów certyfikatów.
+ Ważny
+ Wkrótce wygasa
+ Wygasłe, zawieszone lub cofnięte
+ Aktywny
+ Wygasły
+ Zawieszony
+ Cofnięty
+ Oczekuje na weryfikację
+ Stażysta
+ Aktywny
+ Wygasły
+ Zawieszony
+ Straż pożarna
+ Ratownictwo medyczne
+ Poszukiwania i ratownictwo
+ Pożary lasów
+ Zarządzanie kryzysowe
+ Przemysłowe
+ Ochrona
+ Medyczne
+ Kierowca
+ Pojazd
+ Inne
+ Katalog certyfikatów, licencji, przeglądów i pozwoleń śledzonych przez oddział.
+ Typ ma stały kod, kategorię, zakres (osoba lub jednostka), domyślną ważność oraz opcjonalne reguły weryfikacji lub godzin punktowych. Popularne typy znajdziesz w galerii szablonów.
+ Ukryj nieaktywne
+ Pokaż nieaktywne
+ Galeria szablonów
+ Popularne uprawnienia ze straży pożarnej, ratownictwa medycznego, poszukiwań i ratownictwa, pożarów lasów, zarządzania kryzysowego, przemysłu, ochrony, kierowców DOT i zgodności pojazdów. Każde staje się typem należącym do oddziału, który można edytować.
+ Filtruj szablony…
+ Już w katalogu
+ Dodaj zaznaczone
+ Dodano {0}; pominięto (już istnieją lub odrzucone): {1}
+ Nowy typ
+ Edytuj typ
+ Kod
+ Typ
+ Dotyczy
+ Osoba
+ Jednostka
+ Ważność
+ {0} mies.
+ Rekordy
+ Wymagania
+ Usunąć ten typ certyfikatu?
+ Organ wydający
+ Wyprowadzany z nazwy, gdy pusty
+ Stały identyfikator używany przez wymagania ról i integracje; litery, cyfry, kropki i myślniki.
+ Wymaganie roli odwołuje się do tego typu, więc jego kodu nie można już zmienić.
+ Domyślna ważność (miesiące)
+ Godziny punktowe na odnowienie
+ Wymaga weryfikacji przełożonego
+ Nowe rekordy pozostają oczekujące, dopóki osoba z uprawnieniami nie je zatwierdzi.
+ Katalog typów i szablony
+ Ustawienia certyfikatów
+ Jak egzekwowane są wymagania ról i kiedy wysyłane są powiadomienia o wygaśnięciu.
+ Egzekwowanie
+ Tryb egzekwowania
+ Wyłączone – wymagania są tylko informacyjne
+ Tylko ostrzegaj – zezwalaj na zmiany ról, pokazuj ostrzeżenia, powiadamiaj
+ Egzekwuj – blokuj niekwalifikowane przypisania ról i usuwaj członków po okresie karencji
+ Egzekwowanie usuwa członków z ról, których obowiązkowe certyfikaty wygasły; usunięcie jest audytowane, a członek powiadamiany.
+ Egzekwowanie usunie członków z ról po zakończeniu okresu karencji. Skonfigurowano {0} obowiązkowych wymagań. Kontynuować?
+ Potwierdź przełączenie na Egzekwuj przed zapisem.
+ Okres karencji (dni)
+ Dni po wygaśnięciu obowiązkowego certyfikatu przed usunięciem członka z roli. Każde wymaganie może to nadpisać.
+ Traktuj rekordy oczekujące na weryfikację jako ważne dla wymagań ról
+ Powiadomienia
+ Powiadamiaj (dni przed wygaśnięciem)
+ Rozdzielone przecinkami, np. 60,30,14,7,1. Jedno powiadomienie na rekord na każdy wskazany dzień.
+ Powiadamiaj posiadacza bezpośrednio
+ Wysyłaj administratorom oddziału nocne podsumowanie
+ Odbiorców zdarzeń Certyfikat wygasa / wygasł / Rola usunięta oraz Certyfikat jednostki konfiguruje się w
+ Ustawienia powiadomień
+ Wymagane certyfikaty
+ Wymagane certyfikaty – {0}
+ Jakie typy certyfikatów musi posiadać członek dla tej roli. Wiersze z tym samym numerem grupy są alternatywami; obowiązkowe blokują lub usuwają w trybie Egzekwuj.
+ Wróć do ról
+ Egzekwowanie jest wyłączone: te wymagania są informacyjne, dopóki administrator oddziału nie włączy trybu Tylko ostrzegaj lub Egzekwuj w
+ Nie ma jeszcze typów certyfikatów dla osób.
+ Obowiązkowe
+ Grupa alternatyw
+ Wiersze z tym samym numerem grupy spełnia dowolny z nich (EMT lub AEMT lub Paramedyk); zostaw puste dla samodzielnego wymagania.
+ Zezwól na stażystów
+ Własna karencja (dni)
+ Dodaj wymaganie
+ Kwalifikacje członków
+ Ta rola nie ma członków.
+ Brak lub wygasłe
+ Kwalifikuje się
+ Brak opcjonalnych
+ Nie kwalifikuje się
+ Usunięcie {0}
+ Ci członkowie nie posiadają certyfikatów wymaganych przez tę rolę i nie zostali dodani: {0}
+ Dodano z ostrzeżeniem – brak certyfikatów dla tej roli: {0}
+ Członek został utworzony, ale co najmniej jedna rola nie została przypisana: nie posiada certyfikatów wymaganych przez te role.
+ Certyfikaty jednostki
+ Certyfikaty i przeglądy – {0}
+ Przeglądy, rejestracja, ubezpieczenie, pozwolenia i certyfikaty testów z datą ważności dla tej jednostki.
+ Certyfikaty i przeglądy
+ Wróć do jednostki
+ Dodaj rekord
+ Nie ma jeszcze typów certyfikatów dla jednostek.
+ Certyfikat jednostki
+ Przywróć
+ Zawieś
+ Cofnij
+ Usunąć ten rekord?
+ Rekord certyfikatu
+ Ten rekord jest chroniony; wartości identyfikujące są wyświetlane jako REDACTED bez uprawnienia do ujawnienia.
+ Bez typu
+ Zweryfikowany przez
+ Zweryfikuj
+ Odnów
+ Ustawia nową datę ważności (i opcjonalnie nowy numer), przywracając wygasły rekord.
+ Nowa data ważności
+ Nowy numer
+ Pozostaw puste, aby zachować obecny
+ Oznacz jako stażystę
+ Zmień status
+ Punkty kształcenia ustawicznego
+ Dodaj punkty
+ {0} z {1} godzin do następnego odnowienia
+ Zarejestrowano {0} godzin
+ Brak zarejestrowanych punktów.
+ Usunąć ten wpis punktów?
+ Certyfikat wygasa
+ Certyfikat dodany
+ Certyfikat odnowiony
+ Certyfikat wygasł
+ Rola usunięta z powodu wygasłego certyfikatu
+ Zmieniono status certyfikatu
+ Certyfikat jednostki wygasa
+ Certyfikat jednostki wygasł
+ Do tego typu odwołuje się wymaganie roli lub aktywny rekord, więc nie można go usunąć; zamiast tego go dezaktywuj.
+ Nie znaleziono typu certyfikatu.
+ Ten typ certyfikatu dotyczy innego zakresu (osoba lub jednostka).
+ Typ wymaga nazwy.
+ Domyślna ważność musi wynosić od 1 do 600 miesięcy.
+ Godziny punktowe nie mogą być ujemne.
+ Inny typ w tym oddziale już używa tego kodu.
+ Kodu nie można zmienić, dopóki wymaganie roli odwołuje się do tego typu.
+ Zakresu nie można zmienić, gdy istnieją rekordy tego typu.
+ Nie znaleziono szablonu.
+ Nie znaleziono rekordu certyfikatu.
+ Tego statusu nie można ustawić ręcznie.
+ Zweryfikować można tylko rekord oczekujący na weryfikację.
+ Ten typ wygasa, więc wymagana jest nowa data ważności.
+ Nowa data ważności musi być późniejsza niż obecna.
+ Godziny punktowe muszą być większe od zera.
+ Nie znaleziono wpisu punktów.
+ Nie znaleziono jednostki w tym oddziale.
+ Nie znaleziono roli w tym oddziale.
+ Dni karencji muszą wynosić od 0 do 3650.
+ Nieznany tryb egzekwowania.
+ Dni wyprzedzenia muszą być dodatnie i nie większe niż rok.
+ Nie udało się zabezpieczyć chronionych danych tego rekordu, więc zmiana nie została zapisana.
+ Członek nie posiada certyfikatów wymaganych przez tę rolę.
+ Tego typu pliku nie można przesłać.
+ Plik jest większy niż 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.resx
new file mode 100644
index 00000000..dfeb8b05
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certifications
+ Manage certifications
+ View certifications
+ Manage certification setup
+ Create, edit, suspend, verify and delete other members' certification records and credits, and unit certification records.
+ View other members' certification records, unit certifications and the expiry dashboard. Members always see their own records.
+ Manage certification types, role requirements and the department certification settings.
+ Dashboard
+ Who holds what, what is expiring and what has lapsed, for people and units.
+ Certification types
+ Settings
+ Compliance report
+ Printable person × type and unit × type certification matrices with expiry status.
+ Run on {0}
+ Saved.
+ The change could not be saved.
+ Save
+ Cancel
+ Edit
+ Active
+ Inactive
+ Description
+ Notes
+ Status
+ Date
+ Hours
+ File
+ PDF, image or office document, up to 10 MB.
+ Reason
+ Number
+ Issued by
+ Issued on
+ Expires on
+ Received on
+ Never expires
+ Days until expiry
+ {0} days left
+ {0} days ago
+ Expired
+ Expiring within {0} days
+ Suspended or revoked
+ Export CSV
+ Scope
+ People
+ Units
+ Unit
+ Member
+ Category
+ All categories
+ Group
+ All groups
+ Role
+ All roles
+ No certification types yet.
+ No certification records yet.
+ Valid
+ Expiring soon
+ Expired, suspended or revoked
+ Active
+ Expired
+ Suspended
+ Revoked
+ Pending verification
+ Trainee
+ Active
+ Expired
+ Suspended
+ Fire
+ EMS
+ Search and rescue
+ Wildland
+ Emergency management
+ Industrial
+ Security
+ Medical
+ Driver
+ Vehicle
+ Other
+ The catalog of certifications, licenses, inspections and permits your department tracks.
+ A type has a stable code, a category, a scope (person or unit), a default validity and optional verification or credit-hour rules. Pick common ones from the template gallery.
+ Hide inactive
+ Show inactive
+ Template gallery
+ Common credentials across fire, EMS, SAR, wildland, emergency management, industrial, security, DOT driver and vehicle compliance. Each one becomes a type your department owns and can edit.
+ Filter templates…
+ Already in your catalog
+ Add selected
+ {0} added; skipped (already present or refused): {1}
+ New type
+ Edit type
+ Code
+ Type
+ Applies to
+ Person
+ Unit
+ Validity
+ {0} months
+ Records
+ Requirements
+ Delete this certification type?
+ Issuing authority
+ Derived from the name when empty
+ Stable identifier used by role requirements and integrations; letters, digits, dots and dashes.
+ A role requirement references this type, so its code can no longer change.
+ Default validity (months)
+ Credit hours per renewal
+ Requires supervisor verification
+ New records stay pending until someone with manage rights signs them off.
+ Typed catalog and templates
+ Certification settings
+ How role requirements are enforced and when expiry notices go out.
+ Enforcement
+ Enforcement mode
+ Off – requirements are informational only
+ Warn only – allow role changes, show warnings, notify
+ Enforce – block unqualified role adds and remove members after the grace period
+ Enforce removes members from roles whose mandatory certifications lapsed; the removal is audited and the member is notified.
+ Enforce will remove members from roles once their grace period ends. {0} mandatory requirement(s) are configured. Continue?
+ Confirm the switch to Enforce before saving.
+ Grace period (days)
+ Days after a mandatory certification lapses before the member is removed from the role. Each requirement can override it.
+ Treat records pending verification as valid for role requirements
+ Notifications
+ Notify (days before expiry)
+ Comma-separated, e.g. 60,30,14,7,1. One notice per record per listed day.
+ Notify the holder directly
+ Send department administrators a nightly digest
+ Audiences for the Certification Expiring / Expired / Role Removed and Unit Certification events are configured under
+ Notification settings
+ Required certifications
+ Required certifications – {0}
+ Which certification types a member must hold for this role. Rows sharing a group number are alternatives; mandatory rows block or remove under Enforce.
+ Back to roles
+ Enforcement is off: these requirements are informational until a department administrator turns on Warn only or Enforce in
+ No person-scoped certification types exist yet.
+ Mandatory
+ Any-of group
+ Rows with the same group number are satisfied by any one of them (EMT or AEMT or Paramedic); leave blank for a stand-alone requirement.
+ Allow trainee
+ Grace override (days)
+ Add requirement
+ Member eligibility
+ This role has no members.
+ Missing or lapsed
+ Qualified
+ Optional missing
+ Not qualified
+ Removal on {0}
+ These members do not hold the certifications this role requires and were not added: {0}
+ Added with a warning – missing certifications for this role: {0}
+ The member was created, but one or more roles were not assigned: the member does not hold the certifications those roles require.
+ Unit certifications
+ Certifications and inspections – {0}
+ Inspections, registration, insurance, permits and test certificates with an expiry for this unit.
+ Certifications & inspections
+ Back to unit
+ Add record
+ No unit-scoped certification types exist yet.
+ Unit certification
+ Reinstate
+ Suspend
+ Revoke
+ Delete this record?
+ Certification record
+ This record is protected; identifying values show as REDACTED without a reveal grant.
+ Untyped
+ Verified by
+ Verify
+ Renew
+ Sets a new expiry (and optionally a new number), reactivating an expired record.
+ New expiry date
+ New number
+ Leave empty to keep the current one
+ Mark trainee
+ Change status
+ Continuing-education credits
+ Add credit
+ {0} of {1} hours toward the next renewal
+ {0} hours recorded
+ No credits recorded.
+ Delete this credit entry?
+ Certification expiring
+ Certification added
+ Certification renewed
+ Certification expired
+ Role removed for lapsed certification
+ Certification status changed
+ Unit certification expiring
+ Unit certification expired
+ This type is referenced by a role requirement or a live record and cannot be deleted; deactivate it instead.
+ The certification type was not found.
+ That certification type applies to the other scope (person vs. unit).
+ A type needs a name.
+ Default validity must be between 1 and 600 months.
+ Credit hours cannot be negative.
+ Another type in this department already uses that code.
+ The code cannot change while a role requirement references this type.
+ The scope cannot change once records of this type exist.
+ The template was not found.
+ The certification record was not found.
+ That status cannot be set by hand.
+ Only a record pending verification can be verified.
+ This type expires, so a new expiry date is required.
+ The new expiry must be later than the current one.
+ Credit hours must be greater than zero.
+ The credit entry was not found.
+ The unit was not found in this department.
+ The role was not found in this department.
+ Grace days must be between 0 and 3650.
+ Unknown enforcement mode.
+ Lead days must be positive and no more than a year.
+ Protected data on this record could not be secured, so the change was not saved.
+ The member does not hold the certifications this role requires.
+ That file type cannot be uploaded.
+ The file is larger than 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.sv.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.sv.resx
new file mode 100644
index 00000000..ab879c61
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.sv.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Certifieringar
+ Hantera certifieringar
+ Visa certifieringar
+ Hantera certifieringsinställningar
+ Skapa, redigera, stänga av, verifiera och ta bort andra medlemmars certifieringsposter och poäng samt enheters certifieringsposter.
+ Visa andra medlemmars certifieringsposter, enhetscertifieringar och utgångspanelen. Medlemmar ser alltid sina egna poster.
+ Hantera certifieringstyper, rollkrav och avdelningens certifieringsinställningar.
+ Översikt
+ Vem som har vad, vad som går ut och vad som har gått ut, för personer och enheter.
+ Certifieringstyper
+ Inställningar
+ Efterlevnadsrapport
+ Utskrivbara matriser person × typ och enhet × typ med utgångsstatus.
+ Skapad {0}
+ Sparat.
+ Ändringen kunde inte sparas.
+ Spara
+ Avbryt
+ Redigera
+ Aktiv
+ Inaktiv
+ Beskrivning
+ Anteckningar
+ Status
+ Datum
+ Timmar
+ Fil
+ PDF, bild eller kontorsdokument, upp till 10 MB.
+ Orsak
+ Nummer
+ Utfärdad av
+ Utfärdad den
+ Går ut den
+ Mottagen den
+ Går aldrig ut
+ Dagar till utgång
+ {0} dagar kvar
+ för {0} dagar sedan
+ Utgångna
+ Går ut inom {0} dagar
+ Avstängda eller återkallade
+ Exportera CSV
+ Omfattning
+ Personer
+ Enheter
+ Enhet
+ Medlem
+ Kategori
+ Alla kategorier
+ Grupp
+ Alla grupper
+ Roll
+ Alla roller
+ Inga certifieringstyper ännu.
+ Inga certifieringsposter ännu.
+ Giltig
+ Går snart ut
+ Utgången, avstängd eller återkallad
+ Aktiv
+ Utgången
+ Avstängd
+ Återkallad
+ Väntar på verifiering
+ Under utbildning
+ Aktiv
+ Utgången
+ Avstängd
+ Brand
+ Ambulanssjukvård
+ Sök och räddning
+ Skogsbrand
+ Krishantering
+ Industri
+ Säkerhet
+ Medicin
+ Förare
+ Fordon
+ Övrigt
+ Katalogen över certifieringar, licenser, besiktningar och tillstånd som avdelningen följer.
+ En typ har en fast kod, en kategori, en omfattning (person eller enhet), en standardgiltighet och valfria regler för verifiering eller poängtimmar. Välj vanliga typer från mallgalleriet.
+ Dölj inaktiva
+ Visa inaktiva
+ Mallgalleri
+ Vanliga behörigheter inom brand, ambulans, sök och räddning, skogsbrand, krishantering, industri, säkerhet, DOT-förare och fordonsefterlevnad. Var och en blir en typ som avdelningen äger och kan redigera.
+ Filtrera mallar…
+ Finns redan i katalogen
+ Lägg till valda
+ {0} tillagda; hoppade över (finns redan eller nekades): {1}
+ Ny typ
+ Redigera typ
+ Kod
+ Typ
+ Gäller för
+ Person
+ Enhet
+ Giltighet
+ {0} månader
+ Poster
+ Krav
+ Ta bort denna certifieringstyp?
+ Utfärdande myndighet
+ Härleds från namnet om tomt
+ Fast identifierare som används av rollkrav och integrationer; bokstäver, siffror, punkter och bindestreck.
+ Ett rollkrav refererar till denna typ, så koden kan inte längre ändras.
+ Standardgiltighet (månader)
+ Poängtimmar per förnyelse
+ Kräver verifiering av arbetsledare
+ Nya poster förblir väntande tills någon med hanteringsrättigheter godkänner dem.
+ Typkatalog och mallar
+ Certifieringsinställningar
+ Hur rollkrav tillämpas och när utgångsaviseringar skickas.
+ Tillämpning
+ Tillämpningsläge
+ Av – kraven är endast informativa
+ Endast varna – tillåt rolländringar, visa varningar, avisera
+ Tillämpa – blockera okvalificerade rolltilldelningar och ta bort medlemmar efter respittiden
+ Tillämpa tar bort medlemmar från roller vars obligatoriska certifieringar gått ut; borttagningen loggas och medlemmen aviseras.
+ Tillämpa tar bort medlemmar från roller när deras respittid löper ut. {0} obligatoriska krav är konfigurerade. Fortsätt?
+ Bekräfta bytet till Tillämpa innan du sparar.
+ Respittid (dagar)
+ Dagar efter att en obligatorisk certifiering gått ut innan medlemmen tas bort från rollen. Varje krav kan åsidosätta det.
+ Behandla poster som väntar på verifiering som giltiga för rollkrav
+ Aviseringar
+ Avisera (dagar före utgång)
+ Kommaseparerade, t.ex. 60,30,14,7,1. En avisering per post och listad dag.
+ Avisera innehavaren direkt
+ Skicka avdelningsadministratörer en nattlig sammanfattning
+ Mottagare för händelserna Certifiering går ut / utgången / Roll borttagen och Enhetscertifiering konfigureras under
+ Aviseringsinställningar
+ Obligatoriska certifieringar
+ Obligatoriska certifieringar – {0}
+ Vilka certifieringstyper en medlem måste ha för denna roll. Rader med samma gruppnummer är alternativ; obligatoriska rader blockerar eller tar bort i läget Tillämpa.
+ Tillbaka till roller
+ Tillämpning är av: dessa krav är informativa tills en avdelningsadministratör slår på Endast varna eller Tillämpa under
+ Inga personcertifieringstyper finns ännu.
+ Obligatorisk
+ Alternativgrupp
+ Rader med samma gruppnummer uppfylls av vilken som helst av dem (EMT eller AEMT eller Paramedic); lämna tomt för ett fristående krav.
+ Tillåt under utbildning
+ Egen respittid (dagar)
+ Lägg till krav
+ Medlemmars behörighet
+ Denna roll har inga medlemmar.
+ Saknas eller utgången
+ Kvalificerad
+ Valfria saknas
+ Ej kvalificerad
+ Borttagning {0}
+ Dessa medlemmar saknar certifieringarna rollen kräver och lades inte till: {0}
+ Tillagda med en varning – saknade certifieringar för denna roll: {0}
+ Medlemmen skapades, men en eller flera roller tilldelades inte: medlemmen saknar certifieringarna rollerna kräver.
+ Enhetscertifieringar
+ Certifieringar och besiktningar – {0}
+ Besiktningar, registrering, försäkring, tillstånd och provintyg med utgångsdatum för denna enhet.
+ Certifieringar och besiktningar
+ Tillbaka till enheten
+ Lägg till post
+ Inga enhetscertifieringstyper finns ännu.
+ Enhetscertifiering
+ Återinsätt
+ Stäng av
+ Återkalla
+ Ta bort denna post?
+ Certifieringspost
+ Denna post är skyddad; identifierande värden visas som REDACTED utan ett visningsmedgivande.
+ Utan typ
+ Verifierad av
+ Verifiera
+ Förnya
+ Anger ett nytt utgångsdatum (och eventuellt ett nytt nummer) och återaktiverar en utgången post.
+ Nytt utgångsdatum
+ Nytt nummer
+ Lämna tomt för att behålla nuvarande
+ Markera under utbildning
+ Ändra status
+ Fortbildningspoäng
+ Lägg till poäng
+ {0} av {1} timmar till nästa förnyelse
+ {0} timmar registrerade
+ Inga poäng registrerade.
+ Ta bort denna poängpost?
+ Certifiering går ut
+ Certifiering tillagd
+ Certifiering förnyad
+ Certifiering utgången
+ Roll borttagen på grund av utgången certifiering
+ Certifieringsstatus ändrad
+ Enhetscertifiering går ut
+ Enhetscertifiering utgången
+ Denna typ refereras av ett rollkrav eller en aktiv post och kan inte tas bort; inaktivera den i stället.
+ Certifieringstypen hittades inte.
+ Den certifieringstypen gäller den andra omfattningen (person eller enhet).
+ En typ behöver ett namn.
+ Standardgiltigheten måste vara mellan 1 och 600 månader.
+ Poängtimmar kan inte vara negativa.
+ En annan typ i denna avdelning använder redan den koden.
+ Koden kan inte ändras medan ett rollkrav refererar till denna typ.
+ Omfattningen kan inte ändras när poster av denna typ finns.
+ Mallen hittades inte.
+ Certifieringsposten hittades inte.
+ Den statusen kan inte sättas manuellt.
+ Endast en post som väntar på verifiering kan verifieras.
+ Denna typ går ut, så ett nytt utgångsdatum krävs.
+ Det nya utgångsdatumet måste vara senare än det nuvarande.
+ Poängtimmar måste vara större än noll.
+ Poängposten hittades inte.
+ Enheten hittades inte i denna avdelning.
+ Rollen hittades inte i denna avdelning.
+ Respitdagar måste vara mellan 0 och 3650.
+ Okänt tillämpningsläge.
+ Framförhållningsdagar måste vara positiva och högst ett år.
+ Skyddade uppgifter på denna post kunde inte säkras, så ändringen sparades inte.
+ Medlemmen saknar certifieringarna rollen kräver.
+ Den filtypen kan inte laddas upp.
+ Filen är större än 10 MB.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.uk.resx b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.uk.resx
new file mode 100644
index 00000000..2eb4e101
--- /dev/null
+++ b/Core/Resgrid.Localization/Areas/User/Certifications/Certifications.uk.resx
@@ -0,0 +1,222 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms
+ System.Resources.ResXResourceWriter, System.Windows.Forms
+ Сертифікати
+ Керувати сертифікатами
+ Переглядати сертифікати
+ Керувати налаштуванням сертифікатів
+ Створювати, редагувати, призупиняти, підтверджувати та видаляти записи сертифікатів і кредити інших членів, а також записи сертифікатів підрозділів.
+ Переглядати записи сертифікатів інших членів, сертифікати підрозділів і панель термінів дії. Члени завжди бачать власні записи.
+ Керувати типами сертифікатів, вимогами до ролей і налаштуваннями сертифікатів підрозділу.
+ Панель
+ Хто що має, що спливає і що прострочено, для людей і підрозділів.
+ Типи сертифікатів
+ Налаштування
+ Звіт про відповідність
+ Друковані матриці особа × тип і підрозділ × тип зі станом терміну дії.
+ Сформовано {0}
+ Збережено.
+ Не вдалося зберегти зміну.
+ Зберегти
+ Скасувати
+ Редагувати
+ Активний
+ Неактивний
+ Опис
+ Нотатки
+ Стан
+ Дата
+ Години
+ Файл
+ PDF, зображення або офісний документ до 10 МБ.
+ Причина
+ Номер
+ Видано
+ Дата видачі
+ Спливає
+ Отримано
+ Не спливає
+ Днів до закінчення
+ залишилося {0} дн.
+ {0} дн. тому
+ Прострочено
+ Спливає протягом {0} дн.
+ Призупинено або відкликано
+ Експорт CSV
+ Область
+ Люди
+ Підрозділи
+ Підрозділ
+ Член
+ Категорія
+ Усі категорії
+ Група
+ Усі групи
+ Роль
+ Усі ролі
+ Типів сертифікатів ще немає.
+ Записів сертифікатів ще немає.
+ Дійсний
+ Незабаром спливає
+ Прострочено, призупинено або відкликано
+ Активний
+ Прострочений
+ Призупинений
+ Відкликаний
+ Очікує підтвердження
+ Стажер
+ Активний
+ Прострочений
+ Призупинений
+ Пожежна
+ Швидка допомога
+ Пошук і порятунок
+ Лісові пожежі
+ Управління надзвичайними ситуаціями
+ Промислові
+ Охорона
+ Медичні
+ Водій
+ Транспортний засіб
+ Інше
+ Каталог сертифікатів, ліцензій, оглядів і дозволів, які відстежує ваш підрозділ.
+ Тип має сталий код, категорію, область (особа чи підрозділ), типовий термін дії та необов’язкові правила підтвердження чи кредитних годин. Поширені типи виберіть із галереї шаблонів.
+ Сховати неактивні
+ Показати неактивні
+ Галерея шаблонів
+ Поширені кваліфікації для пожежної, швидкої допомоги, пошуку і порятунку, лісових пожеж, управління НС, промисловості, охорони, водіїв DOT та відповідності транспорту. Кожна стає типом, яким володіє й може редагувати ваш підрозділ.
+ Фільтрувати шаблони…
+ Уже у вашому каталозі
+ Додати вибрані
+ Додано {0}; пропущено (уже є або відхилено): {1}
+ Новий тип
+ Редагувати тип
+ Код
+ Тип
+ Стосується
+ Особа
+ Підрозділ
+ Термін дії
+ {0} міс.
+ Записи
+ Вимоги
+ Видалити цей тип сертифіката?
+ Орган видачі
+ Утворюється з назви, якщо порожньо
+ Сталий ідентифікатор для вимог ролей та інтеграцій; літери, цифри, крапки й дефіси.
+ На цей тип посилається вимога ролі, тому його код більше не можна змінити.
+ Типовий термін дії (місяців)
+ Кредитні години на поновлення
+ Потребує підтвердження керівника
+ Нові записи залишаються в очікуванні, доки їх не підтвердить особа з правами керування.
+ Типізований каталог і шаблони
+ Налаштування сертифікатів
+ Як застосовуються вимоги ролей і коли надсилаються сповіщення про закінчення терміну.
+ Застосування
+ Режим застосування
+ Вимкнено – вимоги лише інформаційні
+ Лише попереджати – дозволяти зміни ролей, показувати попередження, сповіщати
+ Застосовувати – блокувати некваліфіковані призначення ролей і вилучати членів після пільгового періоду
+ Режим застосування вилучає членів із ролей, обов’язкові сертифікати яких прострочено; вилучення аудитується, член отримує сповіщення.
+ Режим застосування вилучатиме членів із ролей після завершення пільгового періоду. Налаштовано обов’язкових вимог: {0}. Продовжити?
+ Підтвердьте перемикання на режим застосування перед збереженням.
+ Пільговий період (днів)
+ Кількість днів після прострочення обов’язкового сертифіката до вилучення члена з ролі. Кожна вимога може перевизначити значення.
+ Вважати записи, що очікують підтвердження, дійсними для вимог ролей
+ Сповіщення
+ Сповіщати (днів до закінчення)
+ Через кому, наприклад 60,30,14,7,1. Одне сповіщення на запис у кожен зазначений день.
+ Сповіщати власника напряму
+ Надсилати адміністраторам підрозділу нічний дайджест
+ Одержувачі подій «Сертифікат спливає / прострочено / роль вилучено» та «Сертифікат підрозділу» налаштовуються в
+ Налаштування сповіщень
+ Обов’язкові сертифікати
+ Обов’язкові сертифікати – {0}
+ Які типи сертифікатів має мати член для цієї ролі. Рядки зі спільним номером групи є альтернативами; обов’язкові блокують або вилучають у режимі застосування.
+ Назад до ролей
+ Застосування вимкнено: ці вимоги інформаційні, доки адміністратор підрозділу не ввімкне «Лише попереджати» або «Застосовувати» в
+ Типів сертифікатів для осіб ще немає.
+ Обов’язково
+ Група альтернатив
+ Рядки з однаковим номером групи задовольняє будь-який із них (EMT або AEMT або Paramedic); залиште порожнім для окремої вимоги.
+ Дозволити стажерів
+ Власний пільговий період (днів)
+ Додати вимогу
+ Відповідність членів
+ У цієї ролі немає членів.
+ Відсутній або прострочений
+ Кваліфікований
+ Бракує необов’язкових
+ Не кваліфікований
+ Вилучення {0}
+ Ці члени не мають сертифікатів, яких вимагає роль, і не були додані: {0}
+ Додано з попередженням – бракує сертифікатів для цієї ролі: {0}
+ Члена створено, але одну чи кілька ролей не призначено: він не має сертифікатів, яких вимагають ці ролі.
+ Сертифікати підрозділу
+ Сертифікати та огляди – {0}
+ Огляди, реєстрація, страхування, дозволи та свідоцтва випробувань із терміном дії для цього підрозділу.
+ Сертифікати та огляди
+ Назад до підрозділу
+ Додати запис
+ Типів сертифікатів для підрозділів ще немає.
+ Сертифікат підрозділу
+ Поновити
+ Призупинити
+ Відкликати
+ Видалити цей запис?
+ Запис сертифіката
+ Цей запис захищено; ідентифікаційні значення показуються як REDACTED без дозволу на розкриття.
+ Без типу
+ Підтвердив
+ Підтвердити
+ Поновити термін
+ Встановлює новий термін дії (і за потреби новий номер), поновлюючи прострочений запис.
+ Нова дата закінчення
+ Новий номер
+ Залиште порожнім, щоб зберегти поточний
+ Позначити стажером
+ Змінити стан
+ Кредити безперервної освіти
+ Додати кредит
+ {0} з {1} годин до наступного поновлення
+ Зафіксовано годин: {0}
+ Кредитів не зафіксовано.
+ Видалити цей запис кредиту?
+ Сертифікат спливає
+ Сертифікат додано
+ Сертифікат поновлено
+ Сертифікат прострочено
+ Роль вилучено через прострочений сертифікат
+ Стан сертифіката змінено
+ Сертифікат підрозділу спливає
+ Сертифікат підрозділу прострочено
+ На цей тип посилається вимога ролі або активний запис; його не можна видалити — натомість деактивуйте.
+ Тип сертифіката не знайдено.
+ Цей тип сертифіката стосується іншої області (особа або підрозділ).
+ Тип потребує назви.
+ Типовий термін дії має бути від 1 до 600 місяців.
+ Кредитні години не можуть бути від’ємними.
+ Інший тип у цьому підрозділі вже використовує цей код.
+ Код не можна змінити, доки на цей тип посилається вимога ролі.
+ Область не можна змінити, коли вже існують записи цього типу.
+ Шаблон не знайдено.
+ Запис сертифіката не знайдено.
+ Цей стан не можна встановити вручну.
+ Підтвердити можна лише запис, що очікує підтвердження.
+ Цей тип спливає, тому потрібна нова дата закінчення.
+ Нова дата закінчення має бути пізнішою за поточну.
+ Кредитні години мають бути більшими за нуль.
+ Запис кредиту не знайдено.
+ Підрозділ у цьому відділі не знайдено.
+ Роль у цьому підрозділі не знайдено.
+ Пільгові дні мають бути від 0 до 3650.
+ Невідомий режим застосування.
+ Дні попередження мають бути додатними й не більшими за рік.
+ Захищені дані цього запису не вдалося убезпечити, тому зміну не збережено.
+ Член не має сертифікатів, яких вимагає ця роль.
+ Цей тип файлу не можна завантажити.
+ Файл більший за 10 МБ.
+
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx
index 8cd690bb..8671b7ca 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx
@@ -486,6 +486,7 @@
أصبحت الخطط المسبقة لهذه الإدارة تُدار الآن كمبانٍ في السجلات. هذه الصفحة للقراءة فقط؛ عدّل المبنى من السجلات.
+ لا يمكن حذف جهة الاتصال هذه ما دام لديها ملف فوترة يحتوي على فواتير غير ملغاة. ألغِ الفواتير أو سوِّها أولاً.
فتح المبنى
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.de.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.de.resx
index 2db0a8ec..007ee0f9 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.de.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.de.resx
@@ -437,6 +437,7 @@
Einsatzpläne dieser Organisation werden jetzt als Objekte in den Berichten gepflegt. Diese Seite ist schreibgeschützt; bearbeiten Sie das Objekt unter Berichte.
+ Dieser Kontakt kann nicht gelöscht werden, solange er ein Abrechnungsprofil mit nicht stornierten Rechnungen hat. Stornieren oder begleichen Sie die Rechnungen zuerst.
Objekt öffnen
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx
index 3bc6831c..0cd4e5d2 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx
@@ -507,6 +507,7 @@
Τα προσχέδια αυτής της υπηρεσίας διατηρούνται πλέον ως κτίρια στα Αρχεία. Η σελίδα είναι μόνο για ανάγνωση· επεξεργαστείτε το κτίριο στα Αρχεία.
+ Αυτή η επαφή δεν μπορεί να διαγραφεί όσο έχει προφίλ χρέωσης με τιμολόγια που δεν έχουν ακυρωθεί. Ακυρώστε ή εξοφλήστε πρώτα τα τιμολόγια.
Άνοιγμα κτιρίου
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx
index 578104c4..3a0d80e4 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx
@@ -696,6 +696,7 @@
Pre-plans for this department are now maintained as Records occupancies. This page is read-only; edit the occupancy under Records.
+ This contact cannot be deleted while it has a billing profile with invoices that are not void. Void or settle the invoices first.
Open occupancy
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.es.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.es.resx
index 55cbff58..12dc49c2 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.es.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.es.resx
@@ -437,6 +437,7 @@
Los planes previos de este departamento ahora se mantienen como ocupaciones en Registros. Esta página es de solo lectura; edite la ocupación en Registros.
+ Este contacto no se puede eliminar mientras tenga un perfil de facturación con facturas no anuladas. Anule o liquide primero las facturas.
Abrir ocupación
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.fr.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.fr.resx
index f1e30310..3f313aba 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.fr.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.fr.resx
@@ -437,6 +437,7 @@
Les plans d'intervention de ce service sont désormais gérés comme des occupations dans les Dossiers. Cette page est en lecture seule ; modifiez l'occupation dans les Dossiers.
+ Ce contact ne peut pas être supprimé tant qu'il possède un profil de facturation avec des factures non annulées. Annulez ou réglez d'abord les factures.
Ouvrir l'occupation
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.it.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.it.resx
index 3cc366e7..7ba901b1 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.it.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.it.resx
@@ -437,6 +437,7 @@
I piani preventivi di questo dipartimento sono ora gestiti come occupazioni nei Registri. Questa pagina è di sola lettura; modifica l'occupazione nei Registri.
+ Questo contatto non può essere eliminato finché ha un profilo di fatturazione con fatture non annullate. Annulla o salda prima le fatture.
Apri occupazione
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.pl.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.pl.resx
index 620ba685..f712893f 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.pl.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.pl.resx
@@ -437,6 +437,7 @@
Plany operacyjne tej jednostki są teraz prowadzone jako obiekty w Rejestrach. Ta strona jest tylko do odczytu; edytuj obiekt w Rejestrach.
+ Nie można usunąć tego kontaktu, dopóki ma profil rozliczeniowy z nieanulowanymi fakturami. Najpierw anuluj lub rozlicz faktury.
Otwórz obiekt
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.sv.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.sv.resx
index 56c40671..78a6843d 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.sv.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.sv.resx
@@ -437,6 +437,7 @@
Insatsplaner för den här organisationen underhålls nu som objekt i Register. Sidan är skrivskyddad; redigera objektet under Register.
+ Kontakten kan inte tas bort så länge den har en faktureringsprofil med fakturor som inte är makulerade. Makulera eller reglera fakturorna först.
Öppna objekt
diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.uk.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.uk.resx
index 8683492f..865aa767 100644
--- a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.uk.resx
+++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.uk.resx
@@ -437,6 +437,7 @@
Плани реагування цього підрозділу тепер ведуться як об'єкти в Записах. Ця сторінка лише для читання; редагуйте об'єкт у Записах.
+ Цей контакт не можна видалити, поки в нього є платіжний профіль із неанульованими рахунками. Спочатку анулюйте або оплатіть рахунки.
Відкрити об'єкт
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.ar.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.ar.resx
index 428a23fa..b08cc674 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.ar.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.ar.resx
@@ -39,6 +39,8 @@
افتراضي القسم (يُستخدم عندما لا يكون للعميل بطاقة أسعار مثبتة)
هل تريد حذف بطاقة الأسعار هذه؟
هل تريد حذف هذا العنصر؟
+ حذف بطاقة الأسعار
+ حذف العنصر
تم حذف بطاقة الأسعار.
النوع
بالساعة – وقت الوحدة في الموقع
@@ -257,9 +259,101 @@
لا يمكن إلغاء فاتورة مدفوعة.
هذه الفاتورة ملغاة.
لم يُحدد بريد إلكتروني للفوترة لهذا العميل.
+ تعذّر إرسال البريد الإلكتروني للفاتورة. تحقق من العنوان وحاول مرة أخرى.
+ هذه المعاملة لدى مزوّد الدفع مسجّلة بالفعل على فاتورة قسم آخر.
لم يتم العثور على الدفعة.
تعذر إنشاء ملف PDF للفاتورة.
ملف الفوترة هذا له فواتير ولا يمكن حذفه.
يحتاج العميل أولًا إلى ملف فوترة نشط.
لم يتم العثور على بطاقة الأسعار.
+ يمكن لمسؤول القسم فقط القيام بذلك.
+ اسمح للعملاء بدفع الفواتير عبر الإنترنت من خلال حساب Stripe الخاص بك. تذهب الأموال مباشرة إلى حسابك؛ Resgrid لا تحتفظ بها أبدًا ولا تأخذ رسومًا ولا يمكنها رد المبالغ.
+ حساب Stripe الخاص بك
+ لم يتم ربط حساب Stripe بعد.
+ الربط مع Stripe
+ إعادة الربط مع Stripe
+ ستسجل الدخول إلى Stripe وتسمح لـ Resgrid بإنشاء صفحات دفع في حسابك. تخزن Resgrid معرّف حسابك فقط، وليس بيانات اعتماد Stripe أبدًا.
+ يمكن لمسؤول القسم فقط ربط حساب Stripe أو فصله.
+ الحساب المرتبط
+ اسم النشاط
+ تاريخ الربط
+ آخر تحقق
+ الإمكانات
+ آخر خطأ
+ مباشر
+ تجريبي
+ فصل
+ هل تريد فصل حساب Stripe؟ ستتوقف روابط الدفع المفتوحة عن العمل ولا يمكن إنشاء روابط جديدة حتى يتم ربط حساب مرة أخرى.
+ إعدادات التحصيل
+ تقديم الدفع عبر الإنترنت في الفواتير
+ طرق الدفع المقبولة
+ البطاقات والمحافظ
+ الخصم البنكي (ACH، حسابات أمريكية)
+ يعتمد التوفر على ما يمكن لحساب Stripe الخاص بك قبوله؛ لا تُعرض طريقة لا يستطيع الحساب قبولها.
+ روابط الدفع
+ تحصيل المدفوعات عبر الإنترنت معلّق من قبل المشغّل في هذه المنطقة. يبقى الربط محفوظًا؛ لا تُعرض روابط دفع جديدة حتى يتم تفعيله مجددًا.
+ المدفوعات عبر الإنترنت غير مفعّلة بعد لقسمك. تواصل مع دعم Resgrid لتفعيلها.
+ تم ربط حساب Stripe {0}. فعّل "تقديم الدفع عبر الإنترنت في الفواتير" لبدء تقديم روابط الدفع.
+ تم فصل حساب Stripe.
+ الدفع عبر الإنترنت
+ رابط الدفع
+ شارك هذا الرابط مع العميل؛ يُطبع على الفاتورة ويُدرج في البريد الإلكتروني. يبقى الرابط صالحًا للمدة المحددة في إعدادات الفوترة.
+ نسخ الرابط
+ تم نسخ الرابط.
+ طلب دفع مفتوح
+ ينتهي
+ فتح صفحة الدفع الآن
+ تحديث صفحة الدفع
+ صفحة الدفع مفتوحة؛ يؤدي رابط الدفع إليها مباشرة الآن.
+ الدفع عبر الإنترنت غير متاح لهذه الفاتورة حاليًا.
+ بدأت
+ الرسوم
+ الإيصال
+ تم الإنشاء
+ بانتظار الدفع
+ الدفع قيد المعالجة (تسوية الخصم البنكي)
+ مدفوع
+ منتهي
+ ملغى
+ فشل
+ غير معروف
+ قيد الانتظار
+ مرتبط
+ إجراء مطلوب في Stripe
+ مفصول
+ تم إلغاء الوصول
+ دفع الفاتورة
+ المبلغ المستحق
+ ادفع
+ تتم معالجة الدفع بواسطة Stripe نيابة عن {0}. تُدخل بيانات بطاقتك أو حسابك البنكي في صفحة Stripe الآمنة ولا تراها Resgrid أبدًا.
+ صفحة الدفع مفتوحة بالفعل لهذه الفاتورة؛ الدفع مرة أخرى يستأنفها.
+ رابط الدفع هذا غير صالح.
+ انتهت صلاحية رابط الدفع هذا. يرجى طلب رابط جديد من القسم.
+ لم تعد هذه الفاتورة متاحة للدفع.
+ تم دفع هذه الفاتورة. شكرًا لك.
+ الدفع عبر الإنترنت غير متاح لهذه الفاتورة. يرجى التواصل مع القسم لمعرفة طرق الدفع الأخرى.
+ يجري تأكيد دفعة بنكية لهذه الفاتورة. لا حاجة لأي إجراء الآن.
+ شكرًا لك — يجري تأكيد دفعتك
+ سيرى القسم الدفعة بمجرد تأكيد Stripe لها، عادة خلال دقيقة؛ قد تستغرق المدفوعات البنكية بضعة أيام عمل. ستتلقى إيصال Stripe بالبريد الإلكتروني.
+ تم إلغاء الدفع
+ لم يتم خصم أي مبلغ. يمكنك العودة إلى هذه الصفحة والدفع متى كنت جاهزًا.
+ العودة إلى الفاتورة
+ طلبات كثيرة جدًا. يرجى الانتظار دقيقة ثم المحاولة مجددًا.
+ تحصيل المدفوعات عبر الإنترنت غير متاح في هذه المنطقة.
+ المدفوعات عبر الإنترنت غير مفعّلة لهذا القسم.
+ تتطلب المدفوعات عبر الإنترنت إضافة Business Operations نشطة.
+ الدفع عبر الإنترنت معطّل في إعدادات الفوترة الخاصة بك.
+ لا يوجد حساب Stripe مرتبط.
+ لا يمكن لحساب Stripe المرتبط قبول الرسوم بعد. أكمل متطلبات Stripe وحاول مجددًا.
+ لا يمكن دفع مسودة أو فاتورة ملغاة عبر الإنترنت.
+ هذه الفاتورة مدفوعة بالفعل.
+ لا يوجد رصيد للتحصيل في هذه الفاتورة.
+ عملة الفاتورة ليست من العملات التي يسوّيها حساب Stripe المرتبط.
+ تعذر الوصول إلى Stripe الآن. يرجى المحاولة بعد قليل.
+ لم يُكمل Stripe عملية الربط. يرجى محاولة الربط مرة أخرى.
+ تم رفض الربط في Stripe. لم يتم تغيير أي شيء.
+ محاولة الربط هذه لم تعد صالحة. يرجى البدء من جديد من صفحة الإعدادات.
+ تحصيل المدفوعات عبر الإنترنت معطّل على هذا الخادم.
+ يفيد Stripe بأن Resgrid لم يعد لديها وصول إلى هذا الحساب. اربطه مجددًا للمتابعة.
+ تعذر تأمين البيانات المحمية في هذا السجل، لذا لم يتم حفظ التغيير.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.de.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.de.resx
index 05b3037f..f6e8ff05 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.de.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.de.resx
@@ -39,6 +39,8 @@
Abteilungsstandard (verwendet, wenn ein Kunde keine feste Preisliste hat)
Diese Preisliste löschen?
Diese Position löschen?
+ Preisliste löschen
+ Position löschen
Preisliste gelöscht.
Typ
Stündlich – Einsatzzeit der Einheit
@@ -257,9 +259,101 @@
Eine bezahlte Rechnung kann nicht storniert werden.
Diese Rechnung ist storniert.
Für diesen Kunden ist keine Rechnungs-E-Mail hinterlegt.
+ Die Rechnungs-E-Mail konnte nicht gesendet werden. Prüfen Sie die Adresse und versuchen Sie es erneut.
+ Diese Zahlungstransaktion ist bereits auf der Rechnung einer anderen Abteilung erfasst.
Die Zahlung wurde nicht gefunden.
Das Rechnungs-PDF konnte nicht erstellt werden.
Dieses Abrechnungsprofil hat Rechnungen und kann nicht gelöscht werden.
Der Kunde benötigt zuerst ein aktives Abrechnungsprofil.
Die Preisliste wurde nicht gefunden.
+ Das kann nur ein Abteilungsadministrator tun.
+ Kunden können Rechnungen online über Ihr eigenes Stripe-Konto bezahlen. Das Geld geht direkt auf Ihr Konto; Resgrid hält es nie, nimmt keine Gebühr und kann nicht erstatten.
+ Ihr Stripe-Konto
+ Noch kein Stripe-Konto verbunden.
+ Mit Stripe verbinden
+ Erneut mit Stripe verbinden
+ Sie melden sich bei Stripe an und erlauben Resgrid, Zahlungsseiten in Ihrem Konto zu erstellen. Resgrid speichert nur Ihre Konto-ID, niemals Ihre Stripe-Zugangsdaten.
+ Nur der Abteilungsadministrator kann das Stripe-Konto verbinden oder trennen.
+ Verbundenes Konto
+ Firmenname
+ Verbunden am
+ Zuletzt geprüft
+ Funktionen
+ Letzter Fehler
+ Live
+ Sandbox
+ Trennen
+ Stripe-Konto trennen? Offene Zahlungslinks funktionieren nicht mehr und neue können erst nach erneuter Verbindung erstellt werden.
+ Einstellungen zur Zahlungsannahme
+ Online-Zahlung auf Rechnungen anbieten
+ Akzeptierte Zahlungsarten
+ Karten und Wallets
+ Bankeinzug (ACH, US-Konten)
+ Die Verfügbarkeit richtet sich nach Ihrem Stripe-Konto; eine Zahlungsart, die das Konto nicht annehmen kann, wird nicht angeboten.
+ Zahlungslinks
+ Die Online-Zahlungsannahme wurde vom Betreiber in dieser Region ausgesetzt. Ihre Verbindung bleibt bestehen; neue Zahlungslinks werden erst nach erneuter Freigabe angeboten.
+ Online-Zahlungen sind für Ihre Abteilung noch nicht freigeschaltet. Wenden Sie sich an den Resgrid-Support, um sie zu aktivieren.
+ Stripe-Konto {0} ist verbunden. Aktivieren Sie „Online-Zahlung auf Rechnungen anbieten“, um Zahlungslinks anzubieten.
+ Das Stripe-Konto wurde getrennt.
+ Online bezahlen
+ Zahlungslink
+ Teilen Sie diesen Link mit dem Kunden; er steht auf der Rechnung und in der E-Mail. Der Link bleibt für die in den Abrechnungseinstellungen festgelegte Dauer gültig.
+ Link kopieren
+ Link kopiert.
+ Offene Zahlungsanfrage
+ Läuft ab
+ Zahlungsseite jetzt öffnen
+ Zahlungsseite erneuern
+ Die Zahlungsseite ist geöffnet; der Zahlungslink führt jetzt direkt dorthin.
+ Für diese Rechnung wird derzeit keine Online-Zahlung angeboten.
+ Gestartet
+ Gebühr
+ Beleg
+ Erstellt
+ Wartet auf Zahlung
+ Zahlung ausstehend (Bankeinzug wird verarbeitet)
+ Bezahlt
+ Abgelaufen
+ Abgebrochen
+ Fehlgeschlagen
+ Unbekannt
+ Ausstehend
+ Verbunden
+ Aktion bei Stripe erforderlich
+ Getrennt
+ Zugriff widerrufen
+ Rechnung bezahlen
+ Fälliger Betrag
+ Bezahlen
+ Die Zahlung wird von Stripe im Auftrag von {0} abgewickelt. Ihre Karten- oder Bankdaten geben Sie auf der sicheren Stripe-Seite ein; Resgrid sieht sie nie.
+ Für diese Rechnung ist bereits eine Zahlungsseite geöffnet; erneutes Bezahlen setzt sie fort.
+ Dieser Zahlungslink ist ungültig.
+ Dieser Zahlungslink ist abgelaufen. Bitte fordern Sie bei der Abteilung einen neuen an.
+ Diese Rechnung kann nicht mehr bezahlt werden.
+ Diese Rechnung wurde bezahlt. Vielen Dank.
+ Für diese Rechnung ist keine Online-Zahlung verfügbar. Bitte wenden Sie sich für andere Zahlungswege an die Abteilung.
+ Eine Bankzahlung für diese Rechnung wird gerade bestätigt. Derzeit ist nichts weiter zu tun.
+ Danke – Ihre Zahlung wird bestätigt
+ Die Abteilung sieht die Zahlung, sobald Stripe sie bestätigt, meist innerhalb einer Minute; Bankzahlungen können einige Werktage dauern. Den Stripe-Beleg erhalten Sie per E-Mail.
+ Zahlung abgebrochen
+ Es wurde nichts abgebucht. Sie können jederzeit zu dieser Seite zurückkehren und bezahlen.
+ Zurück zur Rechnung
+ Zu viele Anfragen. Bitte warten Sie eine Minute und versuchen Sie es erneut.
+ Die Online-Zahlungsannahme ist in dieser Region nicht verfügbar.
+ Online-Zahlungen sind für diese Abteilung nicht freigeschaltet.
+ Online-Zahlungen erfordern ein aktives Business-Operations-Add-on.
+ Die Online-Zahlung ist in Ihren Abrechnungseinstellungen deaktiviert.
+ Kein Stripe-Konto verbunden.
+ Das verbundene Stripe-Konto kann noch keine Zahlungen annehmen. Erfüllen Sie die Anforderungen von Stripe und versuchen Sie es erneut.
+ Ein Entwurf oder eine stornierte Rechnung kann nicht online bezahlt werden.
+ Diese Rechnung ist bereits bezahlt.
+ Auf dieser Rechnung ist kein offener Betrag zu kassieren.
+ Die Rechnungswährung wird vom verbundenen Stripe-Konto nicht abgerechnet.
+ Stripe ist gerade nicht erreichbar. Bitte versuchen Sie es gleich noch einmal.
+ Stripe hat die Verbindung nicht abgeschlossen. Bitte versuchen Sie die Verbindung erneut.
+ Die Verbindung wurde bei Stripe abgelehnt. Es wurde nichts geändert.
+ Dieser Verbindungsversuch ist nicht mehr gültig. Bitte beginnen Sie erneut auf der Einstellungsseite.
+ Die Online-Zahlungsannahme ist auf diesem Server deaktiviert.
+ Laut Stripe hat Resgrid keinen Zugriff mehr auf dieses Konto. Verbinden Sie es erneut, um fortzufahren.
+ Geschützte Daten dieses Datensatzes konnten nicht gesichert werden; die Änderung wurde nicht gespeichert.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.el.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.el.resx
index 923323d4..20d957c7 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.el.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.el.resx
@@ -39,6 +39,8 @@
Προεπιλογή τμήματος (όταν ο πελάτης δεν έχει ορισμένο τιμοκατάλογο)
Διαγραφή αυτού του τιμοκαταλόγου;
Διαγραφή αυτού του στοιχείου;
+ Διαγραφή τιμοκαταλόγου
+ Διαγραφή στοιχείου
Ο τιμοκατάλογος διαγράφηκε.
Τύπος
Ωριαία – χρόνος μονάδας επί τόπου
@@ -257,9 +259,101 @@
Εξοφλημένο τιμολόγιο δεν μπορεί να ακυρωθεί.
Το τιμολόγιο είναι ακυρωμένο.
Δεν έχει οριστεί e-mail χρέωσης για τον πελάτη.
+ Το e-mail του τιμολογίου δεν ήταν δυνατό να σταλεί. Ελέγξτε τη διεύθυνση και δοκιμάστε ξανά.
+ Αυτή η συναλλαγή του παρόχου έχει ήδη καταχωρηθεί σε τιμολόγιο άλλου τμήματος.
Η πληρωμή δεν βρέθηκε.
Δεν ήταν δυνατή η δημιουργία του PDF τιμολογίου.
Το προφίλ χρέωσης έχει τιμολόγια και δεν μπορεί να διαγραφεί.
Ο πελάτης χρειάζεται πρώτα ενεργό προφίλ χρέωσης.
Ο τιμοκατάλογος δεν βρέθηκε.
+ Μόνο διαχειριστής τμήματος μπορεί να το κάνει αυτό.
+ Επιτρέψτε στους πελάτες να πληρώνουν τιμολόγια online μέσω του δικού σας λογαριασμού Stripe. Τα χρήματα πηγαίνουν κατευθείαν στον λογαριασμό σας· το Resgrid δεν τα κρατά ποτέ, δεν χρεώνει προμήθεια και δεν μπορεί να επιστρέψει.
+ Ο λογαριασμός σας Stripe
+ Δεν έχει συνδεθεί ακόμη λογαριασμός Stripe.
+ Σύνδεση με Stripe
+ Επανασύνδεση με Stripe
+ Θα συνδεθείτε στο Stripe και θα επιτρέψετε στο Resgrid να δημιουργεί σελίδες πληρωμής στον λογαριασμό σας. Το Resgrid αποθηκεύει μόνο το id του λογαριασμού, ποτέ τα διαπιστευτήρια Stripe.
+ Μόνο ο διαχειριστής τμήματος μπορεί να συνδέσει ή να αποσυνδέσει τον λογαριασμό Stripe.
+ Συνδεδεμένος λογαριασμός
+ Επωνυμία
+ Συνδέθηκε στις
+ Τελευταία επαλήθευση
+ Δυνατότητες
+ Τελευταίο σφάλμα
+ Ζωντανό
+ Δοκιμαστικό
+ Αποσύνδεση
+ Αποσύνδεση του λογαριασμού Stripe; Οι ανοικτοί σύνδεσμοι πληρωμής σταματούν να λειτουργούν και νέοι δεν δημιουργούνται μέχρι να συνδεθεί ξανά λογαριασμός.
+ Ρυθμίσεις είσπραξης
+ Προσφορά διαδικτυακής πληρωμής στα τιμολόγια
+ Αποδεκτοί τρόποι πληρωμής
+ Κάρτες και πορτοφόλια
+ Τραπεζική χρέωση (ACH, λογαριασμοί ΗΠΑ)
+ Η διαθεσιμότητα ακολουθεί όσα δέχεται ο λογαριασμός σας Stripe· ένας τρόπος που δεν δέχεται δεν προσφέρεται.
+ Σύνδεσμοι πληρωμής
+ Η διαδικτυακή είσπραξη έχει ανασταλεί από τον διαχειριστή σε αυτή την περιοχή. Η σύνδεσή σας διατηρείται· νέοι σύνδεσμοι πληρωμής δεν προσφέρονται μέχρι να ενεργοποιηθεί ξανά.
+ Οι διαδικτυακές πληρωμές δεν έχουν ενεργοποιηθεί ακόμη για το τμήμα σας. Επικοινωνήστε με την υποστήριξη Resgrid για να τις ενεργοποιήσετε.
+ Ο λογαριασμός Stripe {0} συνδέθηκε. Ενεργοποιήστε «Προσφορά διαδικτυακής πληρωμής στα τιμολόγια» για να προσφέρετε συνδέσμους πληρωμής.
+ Ο λογαριασμός Stripe αποσυνδέθηκε.
+ Πληρωμή online
+ Σύνδεσμος πληρωμής
+ Μοιραστείτε τον σύνδεσμο με τον πελάτη· εκτυπώνεται στο τιμολόγιο και περιλαμβάνεται στο e-mail. Ισχύει για το διάστημα λήξης που ορίζεται στις ρυθμίσεις χρέωσης.
+ Αντιγραφή συνδέσμου
+ Ο σύνδεσμος αντιγράφηκε.
+ Ανοικτό αίτημα πληρωμής
+ Λήγει
+ Άνοιγμα σελίδας πληρωμής τώρα
+ Ανανέωση σελίδας πληρωμής
+ Η σελίδα πληρωμής είναι ανοικτή· ο σύνδεσμος πληρωμής οδηγεί απευθείας εκεί.
+ Η διαδικτυακή πληρωμή δεν προσφέρεται για αυτό το τιμολόγιο αυτή τη στιγμή.
+ Ξεκίνησε
+ Προμήθεια
+ Απόδειξη
+ Δημιουργήθηκε
+ Αναμονή πληρωμής
+ Πληρωμή σε εκκρεμότητα (εκκαθάριση τραπεζικής χρέωσης)
+ Πληρώθηκε
+ Έληξε
+ Ακυρώθηκε
+ Απέτυχε
+ Άγνωστο
+ Σε εκκρεμότητα
+ Συνδεδεμένος
+ Απαιτείται ενέργεια στο Stripe
+ Αποσυνδεδεμένος
+ Η πρόσβαση ανακλήθηκε
+ Πληρωμή τιμολογίου
+ Οφειλόμενο ποσό
+ Πληρωμή
+ Η πληρωμή διεκπεραιώνεται από το Stripe για λογαριασμό του {0}. Τα στοιχεία κάρτας ή τράπεζας εισάγονται στην ασφαλή σελίδα του Stripe και το Resgrid δεν τα βλέπει ποτέ.
+ Μια σελίδα πληρωμής είναι ήδη ανοικτή για αυτό το τιμολόγιο· η νέα πληρωμή τη συνεχίζει.
+ Αυτός ο σύνδεσμος πληρωμής δεν είναι έγκυρος.
+ Αυτός ο σύνδεσμος πληρωμής έληξε. Ζητήστε νέο από το τμήμα.
+ Αυτό το τιμολόγιο δεν είναι πλέον διαθέσιμο για πληρωμή.
+ Αυτό το τιμολόγιο έχει εξοφληθεί. Ευχαριστούμε.
+ Η διαδικτυακή πληρωμή δεν είναι διαθέσιμη για αυτό το τιμολόγιο. Επικοινωνήστε με το τμήμα για άλλους τρόπους πληρωμής.
+ Μια τραπεζική πληρωμή για αυτό το τιμολόγιο επιβεβαιώνεται. Δεν απαιτείται άλλη ενέργεια τώρα.
+ Ευχαριστούμε — η πληρωμή σας επιβεβαιώνεται
+ Το τμήμα θα δει την πληρωμή μόλις την επιβεβαιώσει το Stripe, συνήθως μέσα σε ένα λεπτό· οι τραπεζικές πληρωμές μπορεί να χρειαστούν λίγες εργάσιμες ημέρες. Θα λάβετε την απόδειξη του Stripe με e-mail.
+ Η πληρωμή ακυρώθηκε
+ Δεν χρεώθηκε τίποτα. Μπορείτε να επιστρέψετε σε αυτή τη σελίδα και να πληρώσετε όποτε θέλετε.
+ Επιστροφή στο τιμολόγιο
+ Πάρα πολλά αιτήματα. Περιμένετε ένα λεπτό και δοκιμάστε ξανά.
+ Η διαδικτυακή είσπραξη δεν είναι διαθέσιμη σε αυτή την περιοχή.
+ Οι διαδικτυακές πληρωμές δεν είναι ενεργοποιημένες για αυτό το τμήμα.
+ Οι διαδικτυακές πληρωμές απαιτούν ενεργό πρόσθετο Business Operations.
+ Η διαδικτυακή πληρωμή είναι απενεργοποιημένη στις ρυθμίσεις χρέωσης.
+ Δεν υπάρχει συνδεδεμένος λογαριασμός Stripe.
+ Ο συνδεδεμένος λογαριασμός Stripe δεν μπορεί ακόμη να δεχθεί χρεώσεις. Ολοκληρώστε τις απαιτήσεις του Stripe και δοκιμάστε ξανά.
+ Ένα πρόχειρο ή ακυρωμένο τιμολόγιο δεν πληρώνεται online.
+ Αυτό το τιμολόγιο έχει ήδη εξοφληθεί.
+ Δεν υπάρχει υπόλοιπο προς είσπραξη σε αυτό το τιμολόγιο.
+ Το νόμισμα του τιμολογίου δεν εκκαθαρίζεται από τον συνδεδεμένο λογαριασμό Stripe.
+ Το Stripe δεν είναι προσβάσιμο αυτή τη στιγμή. Δοκιμάστε ξανά σε λίγο.
+ Το Stripe δεν ολοκλήρωσε τη σύνδεση. Δοκιμάστε να συνδεθείτε ξανά.
+ Η σύνδεση απορρίφθηκε στο Stripe. Δεν άλλαξε τίποτα.
+ Αυτή η απόπειρα σύνδεσης δεν ισχύει πλέον. Ξεκινήστε ξανά από τη σελίδα ρυθμίσεων.
+ Η διαδικτυακή είσπραξη είναι απενεργοποιημένη σε αυτόν τον διακομιστή.
+ Το Stripe αναφέρει ότι το Resgrid δεν έχει πλέον πρόσβαση σε αυτόν τον λογαριασμό. Συνδέστε τον ξανά για συνέχεια.
+ Τα προστατευμένα δεδομένα αυτής της εγγραφής δεν ήταν δυνατό να ασφαλιστούν, οπότε η αλλαγή δεν αποθηκεύτηκε.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.en.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.en.resx
index 4de3d03e..a8d5c1f7 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.en.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.en.resx
@@ -39,6 +39,8 @@
Department default (used when a customer has no pinned rate card)
Delete this rate card?
Delete this item?
+ Delete rate card
+ Delete item
Rate card deleted.
Type
Hourly – unit time on scene
@@ -257,9 +259,101 @@
A paid invoice cannot be voided.
This invoice is void.
No billing e-mail is set for this customer.
+ The invoice e-mail could not be sent. Check the address and try again.
+ This provider transaction is already recorded on another department's invoice.
The payment was not found.
The invoice PDF could not be generated.
This billing profile has invoices and cannot be deleted.
The customer needs an active billing profile first.
The rate card was not found.
+ Only a department administrator can do this.
+ Let customers pay invoices online through your own Stripe account. Money goes straight to your account; Resgrid never holds it, takes no fee and cannot refund.
+ Your Stripe account
+ No Stripe account is connected yet.
+ Connect with Stripe
+ Reconnect with Stripe
+ You will sign in to Stripe and allow Resgrid to create payment pages on your account. Resgrid stores your account id only, never your Stripe credentials.
+ Only the department administrator can connect or disconnect the Stripe account.
+ Connected account
+ Business name
+ Connected on
+ Last verified
+ Capabilities
+ Last error
+ Live
+ Sandbox
+ Disconnect
+ Disconnect the Stripe account? Open pay links stop working and new ones cannot be created until an account is connected again.
+ Collection settings
+ Offer online payment on invoices
+ Accepted payment methods
+ Cards and wallets
+ Bank debit (ACH, US accounts)
+ Availability follows what your Stripe account can accept; a method the account cannot take is not offered.
+ Pay links
+ Online payment collection is suspended by the operator in this region. Your connection is kept; no new pay links are offered until it is enabled again.
+ Online payments are not yet enabled for your department. Contact Resgrid support to turn them on.
+ Stripe account {0} is connected. Turn on "Offer online payment on invoices" to start offering pay links.
+ The Stripe account was disconnected.
+ Pay online
+ Pay link
+ Share this link with the customer; it is printed on the invoice and included in the e-mail. The link stays valid for the pay-link expiry set in billing settings.
+ Copy link
+ Link copied.
+ Open payment request
+ Expires
+ Open payment page now
+ Refresh payment page
+ The payment page is open; the pay link now leads straight to it.
+ Online payment is not offered for this invoice right now.
+ Started
+ Fee
+ Receipt
+ Created
+ Awaiting payment
+ Payment pending (bank transfer clearing)
+ Paid
+ Expired
+ Cancelled
+ Failed
+ Unknown
+ Pending
+ Connected
+ Action required at Stripe
+ Disconnected
+ Access revoked
+ Pay invoice
+ Amount due
+ Pay
+ Payment is processed by Stripe on behalf of {0}. Your card or bank details are entered on Stripe's secure page and are never seen by Resgrid.
+ A payment page is already open for this invoice; paying again resumes it.
+ This payment link is not valid.
+ This payment link has expired. Please ask the department for a new one.
+ This invoice is no longer available for payment.
+ This invoice has been paid. Thank you.
+ Online payment is not available for this invoice. Please contact the department for other ways to pay.
+ A bank payment for this invoice is being confirmed. No further action is needed right now.
+ Thanks — your payment is being confirmed
+ The department will see the payment as soon as Stripe confirms it, usually within a minute; bank payments can take a few business days. You will receive Stripe's receipt by e-mail.
+ Payment cancelled
+ Nothing was charged. You can come back to this page and pay whenever you are ready.
+ Back to the invoice
+ Too many requests. Please wait a minute and try again.
+ Online payment collection is not available in this region.
+ Online payments are not enabled for this department.
+ Online payments need an active Business Operations add-on.
+ Online payment is switched off in your billing settings.
+ No Stripe account is connected.
+ The connected Stripe account cannot take charges yet. Complete Stripe's requirements and try again.
+ A draft or void invoice cannot be paid online.
+ This invoice is already paid.
+ There is no balance to collect on this invoice.
+ The invoice currency is not one the connected Stripe account settles.
+ Stripe could not be reached right now. Please try again in a moment.
+ Stripe did not complete the connection. Please try connecting again.
+ The connection was declined at Stripe. Nothing was changed.
+ This connection attempt is no longer valid. Please start again from the settings page.
+ Online payment collection is switched off on this server.
+ Stripe reports that Resgrid no longer has access to this account. Connect it again to resume.
+ Protected data on this record could not be secured, so the change was not saved.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.es.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.es.resx
index 376e7b7d..816071a1 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.es.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.es.resx
@@ -39,6 +39,8 @@
Predeterminada del departamento (se usa cuando el cliente no tiene tarifa fija)
¿Eliminar esta tarifa?
¿Eliminar este elemento?
+ Eliminar tarifa
+ Eliminar elemento
Tarifa eliminada.
Tipo
Por hora – tiempo de la unidad en el lugar
@@ -257,9 +259,101 @@
Una factura pagada no se puede anular.
Esta factura está anulada.
No hay correo de facturación para este cliente.
+ No se pudo enviar el correo de la factura. Compruebe la dirección e inténtelo de nuevo.
+ Esta transacción del proveedor ya está registrada en la factura de otro departamento.
No se encontró el pago.
No se pudo generar el PDF de la factura.
Este perfil de facturación tiene facturas y no se puede eliminar.
El cliente necesita primero un perfil de facturación activo.
No se encontró la tarifa.
+ Solo un administrador del departamento puede hacer esto.
+ Permita que los clientes paguen facturas en línea a través de su propia cuenta de Stripe. El dinero va directo a su cuenta; Resgrid nunca lo retiene, no cobra comisión y no puede reembolsar.
+ Su cuenta de Stripe
+ Aún no hay una cuenta de Stripe conectada.
+ Conectar con Stripe
+ Reconectar con Stripe
+ Iniciará sesión en Stripe y permitirá que Resgrid cree páginas de pago en su cuenta. Resgrid solo guarda el id de su cuenta, nunca sus credenciales de Stripe.
+ Solo el administrador del departamento puede conectar o desconectar la cuenta de Stripe.
+ Cuenta conectada
+ Nombre comercial
+ Conectada el
+ Última verificación
+ Capacidades
+ Último error
+ En vivo
+ Pruebas
+ Desconectar
+ ¿Desconectar la cuenta de Stripe? Los enlaces de pago abiertos dejarán de funcionar y no se podrán crear nuevos hasta volver a conectar una cuenta.
+ Configuración de cobro
+ Ofrecer pago en línea en las facturas
+ Métodos de pago aceptados
+ Tarjetas y monederos
+ Débito bancario (ACH, cuentas de EE. UU.)
+ La disponibilidad depende de lo que acepte su cuenta de Stripe; un método que la cuenta no pueda recibir no se ofrece.
+ Enlaces de pago
+ El cobro en línea está suspendido por el operador en esta región. Su conexión se conserva; no se ofrecen nuevos enlaces de pago hasta que se habilite de nuevo.
+ Los pagos en línea aún no están habilitados para su departamento. Contacte con el soporte de Resgrid para activarlos.
+ La cuenta de Stripe {0} está conectada. Active "Ofrecer pago en línea en las facturas" para empezar a ofrecer enlaces de pago.
+ La cuenta de Stripe se desconectó.
+ Pagar en línea
+ Enlace de pago
+ Comparta este enlace con el cliente; se imprime en la factura y se incluye en el correo. El enlace es válido durante la caducidad configurada en los ajustes de facturación.
+ Copiar enlace
+ Enlace copiado.
+ Solicitud de pago abierta
+ Caduca
+ Abrir la página de pago ahora
+ Renovar la página de pago
+ La página de pago está abierta; el enlace de pago lleva directamente a ella.
+ Actualmente no se ofrece el pago en línea para esta factura.
+ Iniciada
+ Comisión
+ Recibo
+ Creada
+ Pendiente de pago
+ Pago pendiente (débito bancario en proceso)
+ Pagada
+ Caducada
+ Cancelada
+ Fallida
+ Desconocido
+ Pendiente
+ Conectada
+ Acción requerida en Stripe
+ Desconectada
+ Acceso revocado
+ Pagar factura
+ Importe a pagar
+ Pagar
+ El pago lo procesa Stripe en nombre de {0}. Sus datos de tarjeta o banco se introducen en la página segura de Stripe y Resgrid nunca los ve.
+ Ya hay una página de pago abierta para esta factura; pagar de nuevo la reanuda.
+ Este enlace de pago no es válido.
+ Este enlace de pago ha caducado. Solicite uno nuevo al departamento.
+ Esta factura ya no está disponible para el pago.
+ Esta factura ya está pagada. Gracias.
+ El pago en línea no está disponible para esta factura. Contacte con el departamento para otras formas de pago.
+ Se está confirmando un pago bancario para esta factura. No se requiere ninguna acción por ahora.
+ Gracias: su pago se está confirmando
+ El departamento verá el pago en cuanto Stripe lo confirme, normalmente en un minuto; los pagos bancarios pueden tardar unos días hábiles. Recibirá el recibo de Stripe por correo.
+ Pago cancelado
+ No se ha realizado ningún cargo. Puede volver a esta página y pagar cuando quiera.
+ Volver a la factura
+ Demasiadas solicitudes. Espere un minuto e inténtelo de nuevo.
+ El cobro en línea no está disponible en esta región.
+ Los pagos en línea no están habilitados para este departamento.
+ Los pagos en línea requieren un complemento Business Operations activo.
+ El pago en línea está desactivado en su configuración de facturación.
+ No hay ninguna cuenta de Stripe conectada.
+ La cuenta de Stripe conectada aún no puede recibir cobros. Complete los requisitos de Stripe e inténtelo de nuevo.
+ Un borrador o una factura anulada no se puede pagar en línea.
+ Esta factura ya está pagada.
+ No hay saldo que cobrar en esta factura.
+ La moneda de la factura no es una que liquide la cuenta de Stripe conectada.
+ No se pudo contactar con Stripe en este momento. Inténtelo de nuevo en un momento.
+ Stripe no completó la conexión. Intente conectar de nuevo.
+ La conexión fue rechazada en Stripe. No se ha cambiado nada.
+ Este intento de conexión ya no es válido. Comience de nuevo desde la página de configuración.
+ El cobro en línea está desactivado en este servidor.
+ Stripe informa de que Resgrid ya no tiene acceso a esta cuenta. Conéctela de nuevo para continuar.
+ No se pudieron proteger los datos de este registro, por lo que el cambio no se guardó.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.fr.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.fr.resx
index e731b8ba..36a6c5a5 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.fr.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.fr.resx
@@ -39,6 +39,8 @@
Grille par défaut du service (utilisée quand le client n'en a pas d'imposée)
Supprimer cette grille tarifaire ?
Supprimer cette ligne ?
+ Supprimer la grille tarifaire
+ Supprimer la ligne
Grille tarifaire supprimée.
Type
Horaire – temps de l'unité sur place
@@ -257,9 +259,101 @@
Une facture payée ne peut pas être annulée.
Cette facture est annulée.
Aucun e-mail de facturation n'est défini pour ce client.
+ L'e-mail de la facture n'a pas pu être envoyé. Vérifiez l'adresse et réessayez.
+ Cette transaction du prestataire est déjà enregistrée sur la facture d'un autre service.
Le paiement est introuvable.
Le PDF de la facture n'a pas pu être généré.
Ce profil de facturation a des factures et ne peut pas être supprimé.
Le client doit d'abord avoir un profil de facturation actif.
La grille tarifaire est introuvable.
+ Seul un administrateur du service peut faire cela.
+ Laissez vos clients régler leurs factures en ligne via votre propre compte Stripe. L'argent arrive directement sur votre compte ; Resgrid ne le détient jamais, ne prélève aucun frais et ne peut pas rembourser.
+ Votre compte Stripe
+ Aucun compte Stripe n'est encore connecté.
+ Connecter avec Stripe
+ Reconnecter avec Stripe
+ Vous vous connecterez à Stripe et autoriserez Resgrid à créer des pages de paiement sur votre compte. Resgrid ne conserve que l'identifiant de votre compte, jamais vos identifiants Stripe.
+ Seul l'administrateur du service peut connecter ou déconnecter le compte Stripe.
+ Compte connecté
+ Nom de l'entreprise
+ Connecté le
+ Dernière vérification
+ Capacités
+ Dernière erreur
+ Production
+ Bac à sable
+ Déconnecter
+ Déconnecter le compte Stripe ? Les liens de paiement ouverts cesseront de fonctionner et aucun nouveau ne pourra être créé avant une nouvelle connexion.
+ Paramètres d'encaissement
+ Proposer le paiement en ligne sur les factures
+ Modes de paiement acceptés
+ Cartes et portefeuilles
+ Prélèvement bancaire (ACH, comptes américains)
+ La disponibilité dépend de ce que votre compte Stripe accepte ; un mode que le compte ne peut pas recevoir n'est pas proposé.
+ Liens de paiement
+ L'encaissement en ligne est suspendu par l'opérateur dans cette région. Votre connexion est conservée ; aucun nouveau lien de paiement n'est proposé tant qu'il n'est pas réactivé.
+ Les paiements en ligne ne sont pas encore activés pour votre service. Contactez le support Resgrid pour les activer.
+ Le compte Stripe {0} est connecté. Activez « Proposer le paiement en ligne sur les factures » pour proposer des liens de paiement.
+ Le compte Stripe a été déconnecté.
+ Payer en ligne
+ Lien de paiement
+ Partagez ce lien avec le client ; il figure sur la facture et dans l'e-mail. Le lien reste valable pendant la durée définie dans les paramètres de facturation.
+ Copier le lien
+ Lien copié.
+ Demande de paiement ouverte
+ Expire le
+ Ouvrir la page de paiement maintenant
+ Renouveler la page de paiement
+ La page de paiement est ouverte ; le lien de paiement y mène directement.
+ Le paiement en ligne n'est pas proposé pour cette facture pour le moment.
+ Démarrée
+ Frais
+ Reçu
+ Créée
+ En attente de paiement
+ Paiement en cours (prélèvement en traitement)
+ Payée
+ Expirée
+ Annulée
+ Échouée
+ Inconnu
+ En attente
+ Connecté
+ Action requise chez Stripe
+ Déconnecté
+ Accès révoqué
+ Payer la facture
+ Montant dû
+ Payer
+ Le paiement est traité par Stripe pour le compte de {0}. Vos données de carte ou bancaires sont saisies sur la page sécurisée de Stripe et Resgrid ne les voit jamais.
+ Une page de paiement est déjà ouverte pour cette facture ; payer à nouveau la reprend.
+ Ce lien de paiement n'est pas valide.
+ Ce lien de paiement a expiré. Demandez-en un nouveau au service.
+ Cette facture n'est plus disponible au paiement.
+ Cette facture a été réglée. Merci.
+ Le paiement en ligne n'est pas disponible pour cette facture. Contactez le service pour d'autres moyens de paiement.
+ Un paiement bancaire pour cette facture est en cours de confirmation. Aucune action n'est requise pour le moment.
+ Merci — votre paiement est en cours de confirmation
+ Le service verra le paiement dès que Stripe le confirmera, généralement en une minute ; les paiements bancaires peuvent prendre quelques jours ouvrés. Vous recevrez le reçu de Stripe par e-mail.
+ Paiement annulé
+ Aucun montant n'a été prélevé. Vous pouvez revenir sur cette page et payer quand vous le souhaitez.
+ Retour à la facture
+ Trop de requêtes. Patientez une minute puis réessayez.
+ L'encaissement en ligne n'est pas disponible dans cette région.
+ Les paiements en ligne ne sont pas activés pour ce service.
+ Les paiements en ligne nécessitent un module Business Operations actif.
+ Le paiement en ligne est désactivé dans vos paramètres de facturation.
+ Aucun compte Stripe n'est connecté.
+ Le compte Stripe connecté ne peut pas encore encaisser. Complétez les exigences de Stripe et réessayez.
+ Un brouillon ou une facture annulée ne peut pas être payé en ligne.
+ Cette facture est déjà réglée.
+ Aucun solde à encaisser sur cette facture.
+ La devise de la facture n'est pas une devise réglée par le compte Stripe connecté.
+ Stripe est injoignable pour le moment. Réessayez dans un instant.
+ Stripe n'a pas finalisé la connexion. Veuillez réessayer.
+ La connexion a été refusée chez Stripe. Rien n'a été modifié.
+ Cette tentative de connexion n'est plus valide. Recommencez depuis la page des paramètres.
+ L'encaissement en ligne est désactivé sur ce serveur.
+ Stripe indique que Resgrid n'a plus accès à ce compte. Reconnectez-le pour reprendre.
+ Les données protégées de cet enregistrement n'ont pas pu être sécurisées ; la modification n'a pas été enregistrée.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.it.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.it.resx
index 3d9c2252..080ff2ab 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.it.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.it.resx
@@ -39,6 +39,8 @@
Predefinito del dipartimento (usato quando il cliente non ha un listino fissato)
Eliminare questo listino?
Eliminare questa voce?
+ Elimina listino
+ Elimina voce
Listino eliminato.
Tipo
Orario – tempo dell'unità sul posto
@@ -257,9 +259,101 @@
Una fattura pagata non può essere annullata.
Questa fattura è annullata.
Nessuna e-mail di fatturazione impostata per questo cliente.
+ Impossibile inviare l'e-mail della fattura. Controlla l'indirizzo e riprova.
+ Questa transazione del provider è già registrata su una fattura di un altro reparto.
Pagamento non trovato.
Impossibile generare il PDF della fattura.
Questo profilo di fatturazione ha fatture e non può essere eliminato.
Il cliente ha bisogno prima di un profilo di fatturazione attivo.
Listino non trovato.
+ Solo un amministratore del dipartimento può farlo.
+ Consenti ai clienti di pagare le fatture online tramite il tuo account Stripe. Il denaro va direttamente sul tuo conto; Resgrid non lo trattiene mai, non applica commissioni e non può rimborsare.
+ Il tuo account Stripe
+ Nessun account Stripe collegato.
+ Collega Stripe
+ Ricollega Stripe
+ Accederai a Stripe e consentirai a Resgrid di creare pagine di pagamento sul tuo account. Resgrid memorizza solo l'id del tuo account, mai le credenziali Stripe.
+ Solo l'amministratore del dipartimento può collegare o scollegare l'account Stripe.
+ Account collegato
+ Nome attività
+ Collegato il
+ Ultima verifica
+ Funzionalità
+ Ultimo errore
+ Live
+ Sandbox
+ Scollega
+ Scollegare l'account Stripe? I link di pagamento aperti smettono di funzionare e non se ne possono creare di nuovi finché un account non viene ricollegato.
+ Impostazioni di incasso
+ Offri il pagamento online sulle fatture
+ Metodi di pagamento accettati
+ Carte e wallet
+ Addebito bancario (ACH, conti USA)
+ La disponibilità dipende da ciò che il tuo account Stripe accetta; un metodo che l'account non può ricevere non viene offerto.
+ Link di pagamento
+ L'incasso online è sospeso dall'operatore in questa regione. La connessione è conservata; nessun nuovo link di pagamento viene offerto finché non viene riattivato.
+ I pagamenti online non sono ancora abilitati per il tuo dipartimento. Contatta il supporto Resgrid per attivarli.
+ L'account Stripe {0} è collegato. Attiva "Offri il pagamento online sulle fatture" per iniziare a offrire link di pagamento.
+ L'account Stripe è stato scollegato.
+ Paga online
+ Link di pagamento
+ Condividi questo link con il cliente; è stampato sulla fattura e incluso nell'e-mail. Il link resta valido per la scadenza impostata nelle impostazioni di fatturazione.
+ Copia link
+ Link copiato.
+ Richiesta di pagamento aperta
+ Scade
+ Apri ora la pagina di pagamento
+ Rinnova la pagina di pagamento
+ La pagina di pagamento è aperta; il link di pagamento porta direttamente lì.
+ Il pagamento online non è offerto per questa fattura al momento.
+ Avviata
+ Commissione
+ Ricevuta
+ Creata
+ In attesa di pagamento
+ Pagamento in sospeso (addebito bancario in elaborazione)
+ Pagata
+ Scaduta
+ Annullata
+ Non riuscita
+ Sconosciuto
+ In sospeso
+ Collegato
+ Azione richiesta su Stripe
+ Scollegato
+ Accesso revocato
+ Paga la fattura
+ Importo dovuto
+ Paga
+ Il pagamento è elaborato da Stripe per conto di {0}. I dati della carta o bancari vengono inseriti nella pagina sicura di Stripe e Resgrid non li vede mai.
+ Una pagina di pagamento è già aperta per questa fattura; pagare di nuovo la riprende.
+ Questo link di pagamento non è valido.
+ Questo link di pagamento è scaduto. Richiedine uno nuovo al dipartimento.
+ Questa fattura non è più disponibile per il pagamento.
+ Questa fattura è stata pagata. Grazie.
+ Il pagamento online non è disponibile per questa fattura. Contatta il dipartimento per altre modalità di pagamento.
+ Un pagamento bancario per questa fattura è in fase di conferma. Al momento non serve altro.
+ Grazie: il pagamento è in fase di conferma
+ Il dipartimento vedrà il pagamento non appena Stripe lo confermerà, di solito entro un minuto; i pagamenti bancari possono richiedere alcuni giorni lavorativi. Riceverai la ricevuta di Stripe via e-mail.
+ Pagamento annullato
+ Non è stato addebitato nulla. Puoi tornare su questa pagina e pagare quando vuoi.
+ Torna alla fattura
+ Troppe richieste. Attendi un minuto e riprova.
+ L'incasso online non è disponibile in questa regione.
+ I pagamenti online non sono abilitati per questo dipartimento.
+ I pagamenti online richiedono un componente Business Operations attivo.
+ Il pagamento online è disattivato nelle impostazioni di fatturazione.
+ Nessun account Stripe collegato.
+ L'account Stripe collegato non può ancora ricevere addebiti. Completa i requisiti di Stripe e riprova.
+ Una bozza o una fattura annullata non può essere pagata online.
+ Questa fattura è già pagata.
+ Non c'è alcun saldo da incassare su questa fattura.
+ La valuta della fattura non è tra quelle regolate dall'account Stripe collegato.
+ Stripe non è raggiungibile al momento. Riprova tra poco.
+ Stripe non ha completato il collegamento. Riprova a collegare.
+ Il collegamento è stato rifiutato su Stripe. Nulla è cambiato.
+ Questo tentativo di collegamento non è più valido. Ricomincia dalla pagina delle impostazioni.
+ L'incasso online è disattivato su questo server.
+ Stripe segnala che Resgrid non ha più accesso a questo account. Ricollegalo per riprendere.
+ I dati protetti di questo record non sono stati messi in sicurezza; la modifica non è stata salvata.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.pl.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.pl.resx
index af5cee72..3ef2ea8d 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.pl.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.pl.resx
@@ -39,6 +39,8 @@
Domyślny dla oddziału (używany, gdy klient nie ma przypiętego cennika)
Usunąć ten cennik?
Usunąć tę pozycję?
+ Usuń cennik
+ Usuń pozycję
Cennik usunięty.
Typ
Godzinowo – czas jednostki na miejscu
@@ -257,9 +259,101 @@
Nie można anulować opłaconej faktury.
Ta faktura jest anulowana.
Brak e-maila do faktur dla tego klienta.
+ Nie udało się wysłać e-maila z fakturą. Sprawdź adres i spróbuj ponownie.
+ Ta transakcja operatora płatności jest już zapisana na fakturze innego oddziału.
Nie znaleziono płatności.
Nie można wygenerować PDF faktury.
Ten profil rozliczeniowy ma faktury i nie można go usunąć.
Klient musi najpierw mieć aktywny profil rozliczeniowy.
Nie znaleziono cennika.
+ Tylko administrator oddziału może to zrobić.
+ Pozwól klientom płacić faktury online przez własne konto Stripe. Pieniądze trafiają prosto na Twoje konto; Resgrid nigdy ich nie przechowuje, nie pobiera opłat i nie może zwracać.
+ Twoje konto Stripe
+ Nie połączono jeszcze konta Stripe.
+ Połącz ze Stripe
+ Połącz ponownie ze Stripe
+ Zalogujesz się do Stripe i pozwolisz Resgrid tworzyć strony płatności na Twoim koncie. Resgrid przechowuje tylko identyfikator konta, nigdy dane logowania Stripe.
+ Tylko administrator oddziału może połączyć lub odłączyć konto Stripe.
+ Połączone konto
+ Nazwa firmy
+ Połączono
+ Ostatnia weryfikacja
+ Możliwości
+ Ostatni błąd
+ Produkcja
+ Testowe
+ Odłącz
+ Odłączyć konto Stripe? Otwarte linki płatności przestaną działać, a nowych nie można utworzyć do ponownego połączenia konta.
+ Ustawienia pobierania płatności
+ Oferuj płatność online na fakturach
+ Akceptowane metody płatności
+ Karty i portfele
+ Polecenie zapłaty (ACH, konta w USA)
+ Dostępność zależy od tego, co akceptuje Twoje konto Stripe; metoda, której konto nie obsługuje, nie jest oferowana.
+ Linki płatności
+ Pobieranie płatności online zostało zawieszone przez operatora w tym regionie. Połączenie jest zachowane; nowe linki płatności nie są oferowane do ponownego włączenia.
+ Płatności online nie są jeszcze włączone dla Twojego oddziału. Skontaktuj się z pomocą Resgrid, aby je włączyć.
+ Konto Stripe {0} jest połączone. Włącz „Oferuj płatność online na fakturach”, aby zacząć oferować linki płatności.
+ Konto Stripe zostało odłączone.
+ Zapłać online
+ Link płatności
+ Udostępnij ten link klientowi; jest drukowany na fakturze i dołączany do e-maila. Link pozostaje ważny przez okres ustawiony w ustawieniach rozliczeń.
+ Kopiuj link
+ Skopiowano link.
+ Otwarte żądanie płatności
+ Wygasa
+ Otwórz teraz stronę płatności
+ Odśwież stronę płatności
+ Strona płatności jest otwarta; link płatności prowadzi teraz bezpośrednio do niej.
+ Płatność online nie jest obecnie oferowana dla tej faktury.
+ Rozpoczęto
+ Opłata
+ Potwierdzenie
+ Utworzone
+ Oczekuje na płatność
+ Płatność w toku (rozliczanie polecenia zapłaty)
+ Opłacone
+ Wygasłe
+ Anulowane
+ Nieudane
+ Nieznane
+ Oczekujące
+ Połączone
+ Wymagane działanie w Stripe
+ Odłączone
+ Dostęp cofnięty
+ Zapłać fakturę
+ Kwota do zapłaty
+ Zapłać
+ Płatność jest realizowana przez Stripe w imieniu {0}. Dane karty lub konta wpisujesz na bezpiecznej stronie Stripe; Resgrid nigdy ich nie widzi.
+ Strona płatności dla tej faktury jest już otwarta; ponowna płatność ją wznawia.
+ Ten link płatności jest nieprawidłowy.
+ Ten link płatności wygasł. Poproś oddział o nowy.
+ Ta faktura nie jest już dostępna do zapłaty.
+ Ta faktura została opłacona. Dziękujemy.
+ Płatność online nie jest dostępna dla tej faktury. Skontaktuj się z oddziałem w sprawie innych sposobów płatności.
+ Płatność bankowa za tę fakturę jest potwierdzana. Na razie nie trzeba nic robić.
+ Dziękujemy – Twoja płatność jest potwierdzana
+ Oddział zobaczy płatność, gdy tylko Stripe ją potwierdzi, zwykle w ciągu minuty; płatności bankowe mogą potrwać kilka dni roboczych. Potwierdzenie Stripe otrzymasz e-mailem.
+ Płatność anulowana
+ Nic nie zostało pobrane. Możesz wrócić na tę stronę i zapłacić w dowolnym momencie.
+ Wróć do faktury
+ Zbyt wiele żądań. Odczekaj minutę i spróbuj ponownie.
+ Pobieranie płatności online nie jest dostępne w tym regionie.
+ Płatności online nie są włączone dla tego oddziału.
+ Płatności online wymagają aktywnego dodatku Business Operations.
+ Płatność online jest wyłączona w ustawieniach rozliczeń.
+ Nie połączono konta Stripe.
+ Połączone konto Stripe nie może jeszcze przyjmować obciążeń. Uzupełnij wymagania Stripe i spróbuj ponownie.
+ Wersji roboczej ani anulowanej faktury nie można opłacić online.
+ Ta faktura jest już opłacona.
+ Na tej fakturze nie ma salda do pobrania.
+ Waluta faktury nie jest rozliczana przez połączone konto Stripe.
+ Nie można teraz połączyć się ze Stripe. Spróbuj ponownie za chwilę.
+ Stripe nie zakończył połączenia. Spróbuj połączyć ponownie.
+ Połączenie zostało odrzucone w Stripe. Nic nie zmieniono.
+ Ta próba połączenia nie jest już ważna. Zacznij ponownie ze strony ustawień.
+ Pobieranie płatności online jest wyłączone na tym serwerze.
+ Stripe zgłasza, że Resgrid nie ma już dostępu do tego konta. Połącz je ponownie, aby wznowić.
+ Nie udało się zabezpieczyć chronionych danych tego rekordu, więc zmiana nie została zapisana.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.resx
index 4de3d03e..a8d5c1f7 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.resx
@@ -39,6 +39,8 @@
Department default (used when a customer has no pinned rate card)
Delete this rate card?
Delete this item?
+ Delete rate card
+ Delete item
Rate card deleted.
Type
Hourly – unit time on scene
@@ -257,9 +259,101 @@
A paid invoice cannot be voided.
This invoice is void.
No billing e-mail is set for this customer.
+ The invoice e-mail could not be sent. Check the address and try again.
+ This provider transaction is already recorded on another department's invoice.
The payment was not found.
The invoice PDF could not be generated.
This billing profile has invoices and cannot be deleted.
The customer needs an active billing profile first.
The rate card was not found.
+ Only a department administrator can do this.
+ Let customers pay invoices online through your own Stripe account. Money goes straight to your account; Resgrid never holds it, takes no fee and cannot refund.
+ Your Stripe account
+ No Stripe account is connected yet.
+ Connect with Stripe
+ Reconnect with Stripe
+ You will sign in to Stripe and allow Resgrid to create payment pages on your account. Resgrid stores your account id only, never your Stripe credentials.
+ Only the department administrator can connect or disconnect the Stripe account.
+ Connected account
+ Business name
+ Connected on
+ Last verified
+ Capabilities
+ Last error
+ Live
+ Sandbox
+ Disconnect
+ Disconnect the Stripe account? Open pay links stop working and new ones cannot be created until an account is connected again.
+ Collection settings
+ Offer online payment on invoices
+ Accepted payment methods
+ Cards and wallets
+ Bank debit (ACH, US accounts)
+ Availability follows what your Stripe account can accept; a method the account cannot take is not offered.
+ Pay links
+ Online payment collection is suspended by the operator in this region. Your connection is kept; no new pay links are offered until it is enabled again.
+ Online payments are not yet enabled for your department. Contact Resgrid support to turn them on.
+ Stripe account {0} is connected. Turn on "Offer online payment on invoices" to start offering pay links.
+ The Stripe account was disconnected.
+ Pay online
+ Pay link
+ Share this link with the customer; it is printed on the invoice and included in the e-mail. The link stays valid for the pay-link expiry set in billing settings.
+ Copy link
+ Link copied.
+ Open payment request
+ Expires
+ Open payment page now
+ Refresh payment page
+ The payment page is open; the pay link now leads straight to it.
+ Online payment is not offered for this invoice right now.
+ Started
+ Fee
+ Receipt
+ Created
+ Awaiting payment
+ Payment pending (bank transfer clearing)
+ Paid
+ Expired
+ Cancelled
+ Failed
+ Unknown
+ Pending
+ Connected
+ Action required at Stripe
+ Disconnected
+ Access revoked
+ Pay invoice
+ Amount due
+ Pay
+ Payment is processed by Stripe on behalf of {0}. Your card or bank details are entered on Stripe's secure page and are never seen by Resgrid.
+ A payment page is already open for this invoice; paying again resumes it.
+ This payment link is not valid.
+ This payment link has expired. Please ask the department for a new one.
+ This invoice is no longer available for payment.
+ This invoice has been paid. Thank you.
+ Online payment is not available for this invoice. Please contact the department for other ways to pay.
+ A bank payment for this invoice is being confirmed. No further action is needed right now.
+ Thanks — your payment is being confirmed
+ The department will see the payment as soon as Stripe confirms it, usually within a minute; bank payments can take a few business days. You will receive Stripe's receipt by e-mail.
+ Payment cancelled
+ Nothing was charged. You can come back to this page and pay whenever you are ready.
+ Back to the invoice
+ Too many requests. Please wait a minute and try again.
+ Online payment collection is not available in this region.
+ Online payments are not enabled for this department.
+ Online payments need an active Business Operations add-on.
+ Online payment is switched off in your billing settings.
+ No Stripe account is connected.
+ The connected Stripe account cannot take charges yet. Complete Stripe's requirements and try again.
+ A draft or void invoice cannot be paid online.
+ This invoice is already paid.
+ There is no balance to collect on this invoice.
+ The invoice currency is not one the connected Stripe account settles.
+ Stripe could not be reached right now. Please try again in a moment.
+ Stripe did not complete the connection. Please try connecting again.
+ The connection was declined at Stripe. Nothing was changed.
+ This connection attempt is no longer valid. Please start again from the settings page.
+ Online payment collection is switched off on this server.
+ Stripe reports that Resgrid no longer has access to this account. Connect it again to resume.
+ Protected data on this record could not be secured, so the change was not saved.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.sv.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.sv.resx
index ef041ad6..d4999506 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.sv.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.sv.resx
@@ -39,6 +39,8 @@
Avdelningens standard (används när kunden saknar vald prislista)
Ta bort denna prislista?
Ta bort denna post?
+ Ta bort prislista
+ Ta bort post
Prislistan togs bort.
Typ
Per timme – enhetens tid på plats
@@ -257,9 +259,101 @@
En betald faktura kan inte makuleras.
Fakturan är makulerad.
Ingen faktura-e-post är angiven för kunden.
+ Faktura-e-posten kunde inte skickas. Kontrollera adressen och försök igen.
+ Den här leverantörstransaktionen är redan registrerad på en annan avdelnings faktura.
Betalningen hittades inte.
Faktura-PDF:en kunde inte skapas.
Faktureringsprofilen har fakturor och kan inte tas bort.
Kunden behöver först en aktiv faktureringsprofil.
Prislistan hittades inte.
+ Endast en avdelningsadministratör kan göra detta.
+ Låt kunder betala fakturor online via ert eget Stripe-konto. Pengarna går direkt till ert konto; Resgrid håller dem aldrig, tar ingen avgift och kan inte återbetala.
+ Ert Stripe-konto
+ Inget Stripe-konto är anslutet ännu.
+ Anslut med Stripe
+ Anslut igen med Stripe
+ Du loggar in på Stripe och låter Resgrid skapa betalsidor på ert konto. Resgrid sparar bara ert konto-id, aldrig era Stripe-uppgifter.
+ Endast avdelningsadministratören kan ansluta eller koppla från Stripe-kontot.
+ Anslutet konto
+ Företagsnamn
+ Ansluten den
+ Senast verifierad
+ Funktioner
+ Senaste fel
+ Skarpt läge
+ Sandlåda
+ Koppla från
+ Koppla från Stripe-kontot? Öppna betallänkar slutar fungera och nya kan inte skapas förrän ett konto ansluts igen.
+ Inställningar för betalning
+ Erbjud onlinebetalning på fakturor
+ Godkända betalsätt
+ Kort och plånböcker
+ Bankdebitering (ACH, amerikanska konton)
+ Tillgängligheten följer vad ert Stripe-konto kan ta emot; ett sätt kontot inte klarar erbjuds inte.
+ Betallänkar
+ Onlinebetalning är avstängd av operatören i denna region. Er anslutning behålls; inga nya betallänkar erbjuds förrän den aktiveras igen.
+ Onlinebetalningar är inte aktiverade för er avdelning ännu. Kontakta Resgrids support för att aktivera dem.
+ Stripe-kontot {0} är anslutet. Slå på ”Erbjud onlinebetalning på fakturor” för att börja erbjuda betallänkar.
+ Stripe-kontot kopplades från.
+ Betala online
+ Betallänk
+ Dela länken med kunden; den trycks på fakturan och ingår i e-posten. Länken gäller så länge som anges i faktureringsinställningarna.
+ Kopiera länk
+ Länk kopierad.
+ Öppen betalningsbegäran
+ Går ut
+ Öppna betalsidan nu
+ Förnya betalsidan
+ Betalsidan är öppen; betallänken leder nu direkt dit.
+ Onlinebetalning erbjuds inte för denna faktura just nu.
+ Startad
+ Avgift
+ Kvitto
+ Skapad
+ Väntar på betalning
+ Betalning pågår (bankdebitering behandlas)
+ Betald
+ Utgången
+ Avbruten
+ Misslyckad
+ Okänd
+ Väntande
+ Ansluten
+ Åtgärd krävs hos Stripe
+ Frånkopplad
+ Åtkomst återkallad
+ Betala faktura
+ Belopp att betala
+ Betala
+ Betalningen hanteras av Stripe för {0}. Kort- eller bankuppgifter anges på Stripes säkra sida och ses aldrig av Resgrid.
+ En betalsida är redan öppen för denna faktura; att betala igen återupptar den.
+ Denna betallänk är inte giltig.
+ Denna betallänk har gått ut. Be avdelningen om en ny.
+ Denna faktura är inte längre tillgänglig för betalning.
+ Denna faktura är betald. Tack.
+ Onlinebetalning är inte tillgänglig för denna faktura. Kontakta avdelningen för andra betalsätt.
+ En bankbetalning för denna faktura bekräftas. Inget mer behövs just nu.
+ Tack – din betalning bekräftas
+ Avdelningen ser betalningen så snart Stripe bekräftar den, oftast inom en minut; bankbetalningar kan ta några bankdagar. Du får Stripes kvitto via e-post.
+ Betalning avbruten
+ Inget debiterades. Du kan komma tillbaka hit och betala när du vill.
+ Tillbaka till fakturan
+ För många förfrågningar. Vänta en minut och försök igen.
+ Onlinebetalning är inte tillgänglig i denna region.
+ Onlinebetalningar är inte aktiverade för denna avdelning.
+ Onlinebetalningar kräver ett aktivt Business Operations-tillägg.
+ Onlinebetalning är avstängd i era faktureringsinställningar.
+ Inget Stripe-konto är anslutet.
+ Det anslutna Stripe-kontot kan inte ta emot betalningar ännu. Slutför Stripes krav och försök igen.
+ Ett utkast eller en makulerad faktura kan inte betalas online.
+ Denna faktura är redan betald.
+ Det finns inget saldo att ta betalt för på denna faktura.
+ Fakturans valuta avräknas inte av det anslutna Stripe-kontot.
+ Stripe kunde inte nås just nu. Försök igen om en stund.
+ Stripe slutförde inte anslutningen. Försök ansluta igen.
+ Anslutningen avböjdes hos Stripe. Inget ändrades.
+ Detta anslutningsförsök är inte längre giltigt. Börja om från inställningssidan.
+ Onlinebetalning är avstängd på denna server.
+ Stripe uppger att Resgrid inte längre har åtkomst till detta konto. Anslut det igen för att fortsätta.
+ Skyddade uppgifter på denna post kunde inte säkras, så ändringen sparades inte.
diff --git a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.uk.resx b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.uk.resx
index 64a290d3..290202c3 100644
--- a/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.uk.resx
+++ b/Core/Resgrid.Localization/Areas/User/Invoicing/Invoicing.uk.resx
@@ -39,6 +39,8 @@
Типовий для підрозділу (коли клієнт не має закріпленого тарифу)
Видалити цей тариф?
Видалити цю позицію?
+ Видалити тариф
+ Видалити позицію
Тариф видалено.
Тип
Погодинно – час підрозділу на місці
@@ -257,9 +259,101 @@
Оплачений рахунок не можна анулювати.
Цей рахунок анульовано.
Для цього клієнта не вказано ел. пошту для рахунків.
+ Не вдалося надіслати ел. лист із рахунком. Перевірте адресу та спробуйте ще раз.
+ Цю транзакцію платіжного провайдера вже записано в рахунку іншого підрозділу.
Платіж не знайдено.
Не вдалося створити PDF рахунку.
Цей профіль оплати має рахунки й не може бути видалений.
Клієнту спочатку потрібен активний профіль оплати.
Тариф не знайдено.
+ Це може зробити лише адміністратор підрозділу.
+ Дозвольте клієнтам оплачувати рахунки онлайн через ваш власний рахунок Stripe. Гроші надходять прямо на ваш рахунок; Resgrid ніколи їх не утримує, не бере комісії й не може повертати.
+ Ваш рахунок Stripe
+ Рахунок Stripe ще не підключено.
+ Підключити Stripe
+ Підключити Stripe знову
+ Ви увійдете в Stripe і дозволите Resgrid створювати сторінки оплати у вашому рахунку. Resgrid зберігає лише ідентифікатор рахунку, ніколи облікові дані Stripe.
+ Лише адміністратор підрозділу може підключити або відключити рахунок Stripe.
+ Підключений рахунок
+ Назва бізнесу
+ Підключено
+ Остання перевірка
+ Можливості
+ Остання помилка
+ Робочий режим
+ Тестовий режим
+ Відключити
+ Відключити рахунок Stripe? Відкриті посилання на оплату перестануть працювати, а нові не створюватимуться до повторного підключення.
+ Налаштування збору платежів
+ Пропонувати онлайн-оплату на рахунках
+ Прийнятні способи оплати
+ Картки та гаманці
+ Банківське списання (ACH, рахунки США)
+ Доступність залежить від того, що приймає ваш рахунок Stripe; спосіб, який рахунок не підтримує, не пропонується.
+ Посилання на оплату
+ Онлайн-збір платежів призупинено оператором у цьому регіоні. Ваше підключення збережено; нові посилання на оплату не пропонуються до повторного ввімкнення.
+ Онлайн-платежі ще не ввімкнено для вашого підрозділу. Зверніться до підтримки Resgrid, щоб їх увімкнути.
+ Рахунок Stripe {0} підключено. Увімкніть «Пропонувати онлайн-оплату на рахунках», щоб пропонувати посилання на оплату.
+ Рахунок Stripe відключено.
+ Сплатити онлайн
+ Посилання на оплату
+ Поділіться цим посиланням із клієнтом; воно друкується на рахунку та додається до листа. Посилання дійсне протягом терміну, заданого в налаштуваннях.
+ Копіювати посилання
+ Посилання скопійовано.
+ Відкритий запит на оплату
+ Спливає
+ Відкрити сторінку оплати зараз
+ Оновити сторінку оплати
+ Сторінку оплати відкрито; посилання на оплату тепер веде прямо на неї.
+ Онлайн-оплата для цього рахунку зараз не пропонується.
+ Розпочато
+ Комісія
+ Квитанція
+ Створено
+ Очікує оплати
+ Платіж в обробці (банківське списання)
+ Оплачено
+ Спливло
+ Скасовано
+ Не вдалося
+ Невідомо
+ Очікує
+ Підключено
+ Потрібна дія у Stripe
+ Відключено
+ Доступ відкликано
+ Сплатити рахунок
+ Сума до сплати
+ Сплатити
+ Платіж обробляє Stripe від імені {0}. Дані картки чи банку вводяться на захищеній сторінці Stripe, і Resgrid їх ніколи не бачить.
+ Сторінка оплати для цього рахунку вже відкрита; повторна оплата продовжить її.
+ Це посилання на оплату недійсне.
+ Термін дії цього посилання минув. Попросіть підрозділ надати нове.
+ Цей рахунок більше не доступний для оплати.
+ Цей рахунок оплачено. Дякуємо.
+ Онлайн-оплата недоступна для цього рахунку. Зверніться до підрозділу щодо інших способів оплати.
+ Банківський платіж за цим рахунком підтверджується. Наразі нічого робити не потрібно.
+ Дякуємо — ваш платіж підтверджується
+ Підрозділ побачить платіж, щойно Stripe його підтвердить, зазвичай протягом хвилини; банківські платежі можуть тривати кілька робочих днів. Квитанцію Stripe ви отримаєте ел. поштою.
+ Оплату скасовано
+ Нічого не списано. Ви можете повернутися на цю сторінку та сплатити, коли будете готові.
+ Назад до рахунку
+ Забагато запитів. Зачекайте хвилину та спробуйте ще раз.
+ Онлайн-збір платежів недоступний у цьому регіоні.
+ Онлайн-платежі не ввімкнено для цього підрозділу.
+ Онлайн-платежі потребують активного доповнення Business Operations.
+ Онлайн-оплату вимкнено у ваших налаштуваннях виставлення рахунків.
+ Рахунок Stripe не підключено.
+ Підключений рахунок Stripe ще не може приймати платежі. Виконайте вимоги Stripe і спробуйте знову.
+ Чернетку або анульований рахунок не можна оплатити онлайн.
+ Цей рахунок уже оплачено.
+ На цьому рахунку немає залишку для стягнення.
+ Валюта рахунку не підтримується підключеним рахунком Stripe.
+ Наразі не вдалося зв’язатися зі Stripe. Спробуйте за хвилину.
+ Stripe не завершив підключення. Спробуйте підключитися знову.
+ Підключення відхилено у Stripe. Нічого не змінено.
+ Ця спроба підключення більше не дійсна. Почніть знову зі сторінки налаштувань.
+ Онлайн-збір платежів вимкнено на цьому сервері.
+ Stripe повідомляє, що Resgrid більше не має доступу до цього рахунку. Підключіть його знову, щоб продовжити.
+ Захищені дані цього запису не вдалося убезпечити, тому зміну не збережено.
diff --git a/Core/Resgrid.Model/AuditLogTypes.cs b/Core/Resgrid.Model/AuditLogTypes.cs
index 441b9d8a..de7eb7d8 100644
--- a/Core/Resgrid.Model/AuditLogTypes.cs
+++ b/Core/Resgrid.Model/AuditLogTypes.cs
@@ -241,6 +241,26 @@ public enum AuditLogTypes
InvoicePaymentRequestCreated,
InvoicePaymentRefunded,
InvoicePaymentDisputed,
- PaymentWebhookRejected
+ PaymentWebhookRejected,
+ InvoicePaymentRequestFailed,
+ InvoicePaymentRequestExpired,
+
+ // Workforce & Business Operations plan Phase D (certifications & role qualification). Append-only.
+ CertificationAdded,
+ CertificationUpdated,
+ CertificationRemoved,
+ CertificationStatusChanged,
+ CertificationVerified,
+ CertificationCreditAdded,
+ CertificationCreditRemoved,
+ RoleCertificationRequirementChanged,
+ DepartmentCertificationSettingsChanged,
+ RoleMemberAdded,
+ RoleMemberRemoved,
+ RoleMemberRemovedByCertification,
+ UnitCertificationAdded,
+ UnitCertificationUpdated,
+ UnitCertificationRemoved,
+ UnitCertificationStatusChanged
}
}
diff --git a/Core/Resgrid.Model/Certifications/CertificationModels.cs b/Core/Resgrid.Model/Certifications/CertificationModels.cs
new file mode 100644
index 00000000..d6babdc2
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationModels.cs
@@ -0,0 +1,412 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model
+{
+ /// Vertical a certification type belongs to (Workforce & Business Operations plan, Phase D1.1). Display grouping only.
+ public enum CertificationCategories
+ {
+ Fire = 0,
+ EMS = 1,
+ SAR = 2,
+ Wildland = 3,
+ EmergencyManagement = 4,
+ Industrial = 5,
+ Security = 6,
+ Medical = 7,
+ Driver = 8,
+ Vehicle = 9,
+ Other = 10
+ }
+
+ /// Whether records of a type are held by a person or by a unit (plan D1.1: one catalog, two record scopes).
+ public enum CertificationAppliesTo
+ {
+ Person = 0,
+ Unit = 1
+ }
+
+ /// Lifecycle of a personnel certification record (plan D1.3).
+ public enum PersonnelCertificationStatuses
+ {
+ Active = 0,
+ Expired = 1,
+ Suspended = 2,
+ Revoked = 3,
+ PendingVerification = 4,
+ Trainee = 5
+ }
+
+ /// Lifecycle of a unit certification record (plan D1.8): units do not train and nobody signs them off.
+ public enum UnitCertificationStatuses
+ {
+ Active = 0,
+ Expired = 1,
+ Suspended = 2
+ }
+
+ /// How role certification requirements are applied (plan decision 26, D1.6).
+ public enum CertificationEnforcementModes
+ {
+ Off = 0,
+ WarnOnly = 1,
+ Enforce = 2
+ }
+
+ ///
+ /// Which personnel roles require which certification types (plan D1.5). Rows with the same non-null
+ /// form an OR-set; null rows are AND requirements. Only Person-scoped types may
+ /// be referenced (service-enforced).
+ ///
+ [Table("PersonnelRoleCertificationRequirements")]
+ public class PersonnelRoleCertificationRequirement : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int PersonnelRoleCertificationRequirementId { get; set; }
+
+ [Required]
+ public int PersonnelRoleId { get; set; }
+
+ [Required]
+ public int DepartmentId { get; set; }
+
+ [Required]
+ public int DepartmentCertificationTypeId { get; set; }
+
+ /// Mandatory blocks role membership under Enforce and drives removal; optional only warns and reports.
+ public bool IsMandatory { get; set; }
+
+ /// Same non-null value on several rows = any one of them satisfies ("EMT OR AEMT OR Paramedic").
+ public int? AnyOfGroup { get; set; }
+
+ /// A Trainee-status record satisfies this requirement (wildland trainee seats with an open task book).
+ public bool AllowTrainee { get; set; }
+
+ /// Overrides the department's RoleRemovalGraceDays for this requirement.
+ public int? GraceDaysOverride { get; set; }
+
+ public DateTime AddedOn { get; set; }
+
+ public string AddedByUserId { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return PersonnelRoleCertificationRequirementId; }
+ set { PersonnelRoleCertificationRequirementId = (int)value; }
+ }
+
+ [NotMapped]
+ public string TableName => "PersonnelRoleCertificationRequirements";
+
+ [NotMapped]
+ public string IdName => "PersonnelRoleCertificationRequirementId";
+
+ [NotMapped]
+ public int IdType => 0;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ /// Per-department certification enforcement and notification settings (plan D1.6). One row per department, keyed by DepartmentId.
+ [Table("DepartmentCertificationSettings")]
+ public class DepartmentCertificationSettings : IEntity
+ {
+ public const string DefaultNotifyLeadDaysCsv = "60,30,14,7,1";
+ public const int DefaultRoleRemovalGraceDays = 30;
+
+ [Key]
+ [Required]
+ public int DepartmentId { get; set; }
+
+ /// ; Off until a department opts in.
+ public int EnforcementMode { get; set; }
+
+ public int RoleRemovalGraceDays { get; set; } = DefaultRoleRemovalGraceDays;
+
+ /// Days before expiry on which the expiring notification fires, e.g. "60,30,14,7,1".
+ public string NotifyLeadDaysCsv { get; set; } = DefaultNotifyLeadDaysCsv;
+
+ /// Notify the holder directly from the worker, independent of the department notification rows.
+ public bool NotifyCertificationHolder { get; set; } = true;
+
+ /// A PendingVerification record counts as valid for role requirements.
+ public bool TreatPendingVerificationAsValid { get; set; }
+
+ /// One nightly summary (expired / expiring / in grace / removed) to the department admins.
+ public bool SendAdminDigest { get; set; } = true;
+
+ public DateTime UpdatedOn { get; set; }
+
+ public string UpdatedByUserId { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return DepartmentId; }
+ set { DepartmentId = (int)value; }
+ }
+
+ [NotMapped]
+ public string TableName => "DepartmentCertificationSettings";
+
+ [NotMapped]
+ public string IdName => "DepartmentId";
+
+ [NotMapped]
+ public int IdType => 0;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+
+ /// Parsed, de-duplicated, positive lead days in descending order; the default schedule when the CSV is empty or malformed.
+ public IReadOnlyList GetNotifyLeadDays() => ParseLeadDays(NotifyLeadDaysCsv);
+
+ public static IReadOnlyList ParseLeadDays(string csv)
+ {
+ var days = new SortedSet();
+ foreach (var part in (csv ?? string.Empty).Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries))
+ if (int.TryParse(part.Trim(), out var day) && day > 0 && day <= 3650)
+ days.Add(day);
+ if (days.Count == 0)
+ foreach (var part in DefaultNotifyLeadDaysCsv.Split(','))
+ days.Add(int.Parse(part));
+ var list = new List(days);
+ list.Reverse();
+ return list;
+ }
+ }
+
+ ///
+ /// A unit-scoped typed credential or document with an expiry (plan D1.8): DOT annual inspection, registration,
+ /// insurance, ambulance permit, pump/aerial test. Shares the type catalog, the expiry worker, notifications and the
+ /// dashboard with personnel records; takes no part in role eligibility, verification or credit hours.
+ /// Number, IssuedBy, Notes and Data are ADP catalog 27 protected fields (Unit family).
+ ///
+ [Table("UnitCertifications")]
+ public class UnitCertification : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int UnitCertificationId { get; set; }
+
+ [Required]
+ public int UnitId { get; set; }
+
+ [Required]
+ public int DepartmentId { get; set; }
+
+ [Required]
+ public int DepartmentCertificationTypeId { get; set; }
+
+ public string Number { get; set; }
+
+ public string IssuedBy { get; set; }
+
+ public DateTime? IssuedOn { get; set; }
+
+ public DateTime? ExpiresOn { get; set; }
+
+ /// .
+ public int Status { get; set; }
+
+ public DateTime? StatusChangedOn { get; set; }
+
+ public string StatusChangedByUserId { get; set; }
+
+ public string StatusReason { get; set; }
+
+ public string Notes { get; set; }
+
+ public string FileName { get; set; }
+
+ public string FileType { get; set; }
+
+ public int? FileSize { get; set; }
+
+ public byte[] Data { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public DateTime AddedOn { get; set; }
+
+ public string AddedByUserId { get; set; }
+
+ public DateTime? EditedOn { get; set; }
+
+ public string EditedByUserId { get; set; }
+
+ public bool IsProtected { get; set; }
+
+ public int? ProtectedCatalogVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return UnitCertificationId; }
+ set { UnitCertificationId = (int)value; }
+ }
+
+ [NotMapped]
+ public string TableName => "UnitCertifications";
+
+ [NotMapped]
+ public string IdName => "UnitCertificationId";
+
+ [NotMapped]
+ public int IdType => 0;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ ///
+ /// A continuing-education / CEU entry against a personnel certification (plan D1.4). Hours roll up against the
+ /// type's RenewalCreditHoursRequired. Description and Data are ADP catalog 27 protected fields (Personnel family).
+ ///
+ [Table("PersonnelCertificationCredits")]
+ public class PersonnelCertificationCredit : IEntity
+ {
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int PersonnelCertificationCreditId { get; set; }
+
+ [Required]
+ public int PersonnelCertificationId { get; set; }
+
+ [Required]
+ public int DepartmentId { get; set; }
+
+ public DateTime CreditDate { get; set; }
+
+ public decimal Hours { get; set; }
+
+ public string Category { get; set; }
+
+ public string Description { get; set; }
+
+ public string FileName { get; set; }
+
+ public string FileType { get; set; }
+
+ public byte[] Data { get; set; }
+
+ public string AddedByUserId { get; set; }
+
+ public DateTime AddedOn { get; set; }
+
+ public bool IsProtected { get; set; }
+
+ public int? ProtectedCatalogVersion { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return PersonnelCertificationCreditId; }
+ set { PersonnelCertificationCreditId = (int)value; }
+ }
+
+ [NotMapped]
+ public string TableName => "PersonnelCertificationCredits";
+
+ [NotMapped]
+ public string IdName => "PersonnelCertificationCreditId";
+
+ [NotMapped]
+ public int IdType => 0;
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ // ---- Read models -----------------------------------------------------------------------------------
+
+ /// One requirement's outcome for one member (plan D1.7).
+ public sealed class CertificationRequirementOutcome
+ {
+ public int PersonnelRoleCertificationRequirementId { get; set; }
+ public int DepartmentCertificationTypeId { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public bool IsMandatory { get; set; }
+ public int? AnyOfGroup { get; set; }
+ public bool Satisfied { get; set; }
+ /// The record that satisfied it, when one did.
+ public int? PersonnelCertificationId { get; set; }
+ public DateTime? ExpiresOn { get; set; }
+ /// When the violation began: the record's expiry, or null when the member never held the type.
+ public DateTime? ViolationStartedOn { get; set; }
+ public int GraceDays { get; set; }
+ }
+
+ /// Evaluation of one member against one role (plan D4).
+ public sealed class RoleCertificationEvaluation
+ {
+ public int PersonnelRoleId { get; set; }
+ public string UserId { get; set; }
+ public bool Qualified { get; set; }
+ /// True when only optional requirements fail.
+ public bool WarningsOnly { get; set; }
+ public List Outcomes { get; set; } = new List();
+ public List Violations { get; set; } = new List();
+ /// The earliest date on which a mandatory violation's grace runs out; null when qualified or never qualified for a requirement with no grace anchor.
+ public DateTime? RemovalDueOn { get; set; }
+ }
+
+ /// A row of the person × type or unit × type expiry matrix (plan D9).
+ public sealed class CertificationDashboardCell
+ {
+ public string SubjectId { get; set; }
+ public string SubjectName { get; set; }
+ public int DepartmentCertificationTypeId { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public int Category { get; set; }
+ public int RecordId { get; set; }
+ public int Status { get; set; }
+ public DateTime? ExpiresOn { get; set; }
+ public int? DaysUntilExpiry { get; set; }
+ public bool NeverExpires { get; set; }
+ }
+
+ public sealed class CertificationExpiryDashboard
+ {
+ public List PersonTypes { get; set; } = new List();
+ public List UnitTypes { get; set; } = new List();
+ public List PersonCells { get; set; } = new List();
+ public List UnitCells { get; set; } = new List();
+ public int ExpiredCount { get; set; }
+ public int ExpiringCount { get; set; }
+ public int SuspendedCount { get; set; }
+ public int PendingVerificationCount { get; set; }
+ }
+
+ /// A seed template for a department certification type (plan D1.2).
+ public sealed class CertificationTypeTemplate
+ {
+ public string Id { get; set; }
+ public string Code { get; set; }
+ public string Name { get; set; }
+ public CertificationCategories Category { get; set; }
+ public CertificationAppliesTo AppliesTo { get; set; }
+ public string IssuingAuthority { get; set; }
+ public int? DefaultValidityMonths { get; set; }
+ public bool NeverExpires { get; set; }
+ public bool RequiresVerification { get; set; }
+ public decimal? RenewalCreditHoursRequired { get; set; }
+ public string Description { get; set; }
+ public IReadOnlyList Keywords { get; set; } = Array.Empty();
+ }
+}
diff --git a/Core/Resgrid.Model/Certifications/CertificationPermissionCatalog.cs b/Core/Resgrid.Model/Certifications/CertificationPermissionCatalog.cs
new file mode 100644
index 00000000..c011043a
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationPermissionCatalog.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace Resgrid.Model
+{
+ ///
+ /// Department-configurable certification permissions (Workforce & Business Operations plan, D8; registry 42–44).
+ /// The single source of the no-row defaults: all three fall back to department administrators; a member's own
+ /// records are always visible without any of them. Unit certification records ride the same permissions.
+ ///
+ public static class CertificationPermissionCatalog
+ {
+ public static readonly IReadOnlyList All = new[]
+ {
+ new RecordPermissionDescriptor(PermissionTypes.ManageCertifications, PermissionActions.DepartmentAdminsOnly, false, "ManageCertificationsNote", false),
+ new RecordPermissionDescriptor(PermissionTypes.ViewCertifications, PermissionActions.DepartmentAdminsOnly, false, "ViewCertificationsNote", false),
+ new RecordPermissionDescriptor(PermissionTypes.ManageCertificationSetup, PermissionActions.DepartmentAdminsOnly, false, "ManageCertificationSetupNote", false)
+ };
+ }
+}
diff --git a/Core/Resgrid.Model/Certifications/CertificationProtectedFields.cs b/Core/Resgrid.Model/Certifications/CertificationProtectedFields.cs
new file mode 100644
index 00000000..2f860b25
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationProtectedFields.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+
+namespace Resgrid.Model.Certifications
+{
+ ///
+ /// ADP catalog 27 (Workforce & Business Operations plan, Phase D2; registered with M0213/M0214): the free-text and
+ /// document columns of unit certification records (Operational family, like UnitLogs) and certification credit entries (Personnel family).
+ /// PersonnelCertifications itself stays catalog 6. Accessor maps drive the generic RMS write/read seams.
+ ///
+ public static class CertificationProtectedFields
+ {
+ public const int CatalogVersion = 27;
+ public const string UnitFamily = "Operational";
+ public const string PersonnelFamily = "Personnel";
+ public const string UnitDataFieldId = "unitcertifications.data";
+ public const string CreditDataFieldId = "personnelcertificationcredits.data";
+
+ public static readonly IReadOnlyDictionary Get, Action Set)> Unit =
+ new Dictionary, Action)>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["unitcertifications.number"] = (u => u.Number, (u, v) => u.Number = v),
+ ["unitcertifications.issuedby"] = (u => u.IssuedBy, (u, v) => u.IssuedBy = v),
+ ["unitcertifications.notes"] = (u => u.Notes, (u, v) => u.Notes = v),
+ ["unitcertifications.filename"] = (u => u.FileName, (u, v) => u.FileName = v)
+ };
+
+ public static readonly IReadOnlyDictionary Get, Action Set)> Credit =
+ new Dictionary, Action)>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["personnelcertificationcredits.description"] = (c => c.Description, (c, v) => c.Description = v),
+ ["personnelcertificationcredits.filename"] = (c => c.FileName, (c, v) => c.FileName = v)
+ };
+
+ /// (table, column, family, binary) for the catalog registration.
+ public static IEnumerable<(string Table, string Column, string Family, bool Binary)> All()
+ {
+ yield return ("UnitCertifications", "Number", UnitFamily, false);
+ yield return ("UnitCertifications", "IssuedBy", UnitFamily, false);
+ yield return ("UnitCertifications", "Notes", UnitFamily, false);
+ yield return ("UnitCertifications", "FileName", UnitFamily, false);
+ yield return ("UnitCertifications", "Data", UnitFamily, true);
+ yield return ("PersonnelCertificationCredits", "Description", PersonnelFamily, false);
+ yield return ("PersonnelCertificationCredits", "FileName", PersonnelFamily, false);
+ yield return ("PersonnelCertificationCredits", "Data", PersonnelFamily, true);
+ }
+ }
+}
diff --git a/Core/Resgrid.Model/Certifications/CertificationRequirementEvaluator.cs b/Core/Resgrid.Model/Certifications/CertificationRequirementEvaluator.cs
new file mode 100644
index 00000000..745121e1
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationRequirementEvaluator.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Resgrid.Model.Certifications
+{
+ ///
+ /// The single validity rule (Workforce & Business Operations plan, Phase D1.7), used identically by role add
+ /// validation, the nightly worker, the Phase C wizard and the compliance report so none of them drift.
+ ///
+ /// A requirement is satisfied when the member holds a non-deleted typed record of the required type whose status is
+ /// Active (or Trainee when the requirement allows trainees, or PendingVerification when the department treats it as
+ /// valid) and which either never expires or expires on or after the evaluation date. Rows sharing a non-null
+ /// AnyOfGroup are one OR-set satisfied by any member; null rows are AND requirements. Pure: no clock, no I/O.
+ ///
+ public static class CertificationRequirementEvaluator
+ {
+ /// A record's validity against one requirement on a given date.
+ public static bool IsRecordValid(PersonnelCertification record, DepartmentCertificationType type, DateTime onDate,
+ bool allowTrainee, bool treatPendingVerificationAsValid)
+ {
+ if (record == null || type == null || record.IsDeleted || !record.IsTyped || record.DepartmentCertificationTypeId != type.DepartmentCertificationTypeId)
+ return false;
+
+ var status = (PersonnelCertificationStatuses)record.Status;
+ var statusOk = status == PersonnelCertificationStatuses.Active
+ || (allowTrainee && status == PersonnelCertificationStatuses.Trainee)
+ || (treatPendingVerificationAsValid && status == PersonnelCertificationStatuses.PendingVerification);
+ if (!statusOk)
+ return false;
+
+ if (type.NeverExpires)
+ return true;
+
+ // A typed record with no expiry on a type that does expire is treated as open-ended: the department chose not
+ // to record one. Expiry is compared by date, not instant, so a card valid "through" its printed date stays valid.
+ return !record.ExpiresOn.HasValue || record.ExpiresOn.Value.Date >= onDate.Date;
+ }
+
+ ///
+ /// Evaluates one member against a role's requirements. are that member's records (any
+ /// scope; untyped and deleted rows are ignored). anchors the grace window for a
+ /// requirement the member never satisfied; null means "no anchor" and the outcome carries no violation start.
+ ///
+ public static RoleCertificationEvaluation Evaluate(int personnelRoleId, string userId,
+ IReadOnlyList requirements,
+ IReadOnlyList records,
+ IReadOnlyDictionary types,
+ DepartmentCertificationSettings settings, DateTime onDate, DateTime? memberSince = null)
+ {
+ var evaluation = new RoleCertificationEvaluation { PersonnelRoleId = personnelRoleId, UserId = userId, Qualified = true };
+ var reqs = (requirements ?? Array.Empty()).Where(r => r != null && r.PersonnelRoleId == personnelRoleId).ToList();
+ if (reqs.Count == 0)
+ return evaluation;
+
+ var pendingOk = settings?.TreatPendingVerificationAsValid == true;
+ var defaultGrace = settings?.RoleRemovalGraceDays ?? DepartmentCertificationSettings.DefaultRoleRemovalGraceDays;
+ var mine = (records ?? Array.Empty()).Where(r => r != null && !r.IsDeleted && r.IsTyped && (userId == null || r.UserId == userId)).ToList();
+
+ // Outcome per requirement row, then OR-sets collapse: a group passes when any of its rows passes.
+ var outcomes = new List();
+ foreach (var req in reqs)
+ {
+ types.TryGetValue(req.DepartmentCertificationTypeId, out var type);
+ var candidates = mine.Where(r => r.DepartmentCertificationTypeId == req.DepartmentCertificationTypeId).ToList();
+ var valid = candidates.Where(r => IsRecordValid(r, type, onDate, req.AllowTrainee, pendingOk))
+ .OrderByDescending(r => r.ExpiresOn ?? DateTime.MaxValue).FirstOrDefault();
+ var outcome = new CertificationRequirementOutcome
+ {
+ PersonnelRoleCertificationRequirementId = req.PersonnelRoleCertificationRequirementId,
+ DepartmentCertificationTypeId = req.DepartmentCertificationTypeId,
+ TypeCode = type?.Code,
+ TypeName = type?.Type,
+ IsMandatory = req.IsMandatory,
+ AnyOfGroup = req.AnyOfGroup,
+ Satisfied = valid != null,
+ PersonnelCertificationId = valid?.PersonnelCertificationId,
+ ExpiresOn = valid?.ExpiresOn,
+ GraceDays = Math.Max(0, req.GraceDaysOverride ?? defaultGrace)
+ };
+ if (valid == null)
+ {
+ // Violation anchor: the latest expiry among the member's records of this type (the card they let lapse);
+ // else the membership date when the caller knows it; else the day the requirement was added (role
+ // memberships carry no date, so a member who never held the type starts their grace when the rule did).
+ var lapsed = candidates.Where(r => r.ExpiresOn.HasValue && (type == null || !type.NeverExpires)).OrderByDescending(r => r.ExpiresOn).FirstOrDefault();
+ outcome.ViolationStartedOn = lapsed?.ExpiresOn?.Date ?? memberSince?.Date ?? (req.AddedOn == default ? (DateTime?)null : req.AddedOn.Date);
+ }
+ outcomes.Add(outcome);
+ }
+
+ var failing = new List();
+ foreach (var group in outcomes.GroupBy(o => o.AnyOfGroup))
+ {
+ if (group.Key.HasValue)
+ {
+ if (group.Any(o => o.Satisfied))
+ continue;
+ failing.AddRange(group);
+ }
+ else
+ failing.AddRange(group.Where(o => !o.Satisfied));
+ }
+
+ evaluation.Outcomes = outcomes;
+ evaluation.Violations = failing;
+ var mandatory = failing.Where(f => f.IsMandatory).ToList();
+ evaluation.Qualified = mandatory.Count == 0;
+ evaluation.WarningsOnly = mandatory.Count == 0 && failing.Count > 0;
+ evaluation.RemovalDueOn = mandatory.Where(f => f.ViolationStartedOn.HasValue)
+ .Select(f => (DateTime?)f.ViolationStartedOn.Value.Date.AddDays(f.GraceDays)).Min();
+ return evaluation;
+ }
+
+ /// True once the evaluation date is past the grace deadline (removal happens on day grace + 1, plan D5.3).
+ public static bool IsPastGrace(DateTime? removalDueOn, DateTime onDate)
+ => removalDueOn.HasValue && onDate.Date > removalDueOn.Value.Date;
+
+ /// Days from to the record's expiry date; null when it never expires or has no expiry.
+ public static int? DaysUntilExpiry(DateTime? expiresOn, bool neverExpires, DateTime onDate)
+ => neverExpires || !expiresOn.HasValue ? (int?)null : (int)(expiresOn.Value.Date - onDate.Date).TotalDays;
+ }
+}
diff --git a/Core/Resgrid.Model/Certifications/CertificationTypeTemplateCatalog.cs b/Core/Resgrid.Model/Certifications/CertificationTypeTemplateCatalog.cs
new file mode 100644
index 00000000..5a0a66f2
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationTypeTemplateCatalog.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Resgrid.Model.Certifications
+{
+ ///
+ /// Code-defined seed templates for department certification types (Workforce & Business Operations plan,
+ /// Phase D1.2; structural clone of ). Nothing here
+ /// lives in the database: "add to department" projects a template into an unsaved
+ /// the department then owns and may edit. Validity months are the
+ /// common renewal cycle for the credential; the department can override them per type. Codes are stable and
+ /// unique across the catalog so a department that adds the same template twice gets a duplicate-code refusal.
+ ///
+ public static class CertificationTypeTemplateCatalog
+ {
+ public static IReadOnlyList All { get; } = BuildAll();
+
+ public static CertificationTypeTemplate GetById(string id)
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ return null;
+
+ return All.FirstOrDefault(t => string.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public static IReadOnlyList GetByCategory(CertificationCategories category)
+ => All.Where(t => t.Category == category).ToList();
+
+ public static IReadOnlyList GetByScope(CertificationAppliesTo appliesTo)
+ => All.Where(t => t.AppliesTo == appliesTo).ToList();
+
+ /// Free-text filter over code, name, category, authority, description and keywords (every term must match).
+ public static IReadOnlyList Search(string query)
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ return All;
+
+ var terms = query.ToLowerInvariant().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ return All.Where(t => terms.All(term => SearchText(t).Contains(term))).ToList();
+ }
+
+ /// Projects a template into an unsaved department type (plan D1.2 "add to department").
+ public static DepartmentCertificationType ToDepartmentType(CertificationTypeTemplate template, int departmentId)
+ {
+ if (template == null)
+ return null;
+
+ return new DepartmentCertificationType
+ {
+ DepartmentId = departmentId,
+ Type = template.Name,
+ Code = template.Code,
+ Category = (int)template.Category,
+ AppliesTo = (int)template.AppliesTo,
+ Description = template.Description,
+ IssuingAuthority = template.IssuingAuthority,
+ DefaultValidityMonths = template.NeverExpires ? null : template.DefaultValidityMonths,
+ NeverExpires = template.NeverExpires,
+ RequiresVerification = template.RequiresVerification,
+ RenewalCreditHoursRequired = template.RenewalCreditHoursRequired,
+ IsActive = true
+ };
+ }
+
+ private static string SearchText(CertificationTypeTemplate t)
+ => string.Join(" ", new[] { t.Code, t.Name, t.Category.ToString(), t.AppliesTo.ToString(), t.IssuingAuthority, t.Description }
+ .Concat(t.Keywords ?? Array.Empty()).Where(s => !string.IsNullOrWhiteSpace(s))).ToLowerInvariant();
+
+ #region Builders
+
+ private static CertificationTypeTemplate P(string code, string name, CertificationCategories category, string authority, int? months, string description, params string[] keywords)
+ => new CertificationTypeTemplate { Id = code.ToLowerInvariant(), Code = code, Name = name, Category = category, AppliesTo = CertificationAppliesTo.Person, IssuingAuthority = authority, DefaultValidityMonths = months, NeverExpires = months == null, Description = description, Keywords = keywords };
+
+ private static CertificationTypeTemplate U(string code, string name, CertificationCategories category, string authority, int? months, string description, params string[] keywords)
+ => new CertificationTypeTemplate { Id = code.ToLowerInvariant(), Code = code, Name = name, Category = category, AppliesTo = CertificationAppliesTo.Unit, IssuingAuthority = authority, DefaultValidityMonths = months, NeverExpires = months == null, Description = description, Keywords = keywords };
+
+ private static CertificationTypeTemplate Verified(this CertificationTypeTemplate t) { t.RequiresVerification = true; return t; }
+ private static CertificationTypeTemplate Credits(this CertificationTypeTemplate t, decimal hours) { t.RenewalCreditHoursRequired = hours; return t; }
+
+ private static IReadOnlyList BuildAll()
+ {
+ const CertificationCategories Fire = CertificationCategories.Fire, EMS = CertificationCategories.EMS, SAR = CertificationCategories.SAR,
+ Wild = CertificationCategories.Wildland, EM = CertificationCategories.EmergencyManagement, Ind = CertificationCategories.Industrial,
+ Sec = CertificationCategories.Security, Med = CertificationCategories.Medical, Drv = CertificationCategories.Driver, Veh = CertificationCategories.Vehicle;
+
+ return new List
+ {
+ // ---- Fire (NFPA ladder; Pro Board / IFSAC accredited) --------------------------------------
+ P("FF1", "Firefighter I", Fire, "NFPA 1001 (Pro Board / IFSAC)", null, "Entry-level structural firefighter certification.", "nfpa", "structural"),
+ P("FF2", "Firefighter II", Fire, "NFPA 1001 (Pro Board / IFSAC)", null, "Advanced structural firefighter certification.", "nfpa", "structural"),
+ P("DO-PUMP", "Driver/Operator – Pumper", Fire, "NFPA 1002 (Pro Board / IFSAC)", null, "Apparatus driver/operator for pumping apparatus.", "nfpa", "apparatus", "engine"),
+ P("DO-AERIAL", "Driver/Operator – Aerial", Fire, "NFPA 1002 (Pro Board / IFSAC)", null, "Apparatus driver/operator for aerial apparatus.", "nfpa", "apparatus", "ladder", "truck"),
+ P("HAZMAT-OPS", "Hazardous Materials Operations", Fire, "NFPA 470", 12, "Hazmat operations level; annual refresher under OSHA 1910.120(q).", "hazmat", "osha"),
+ P("HAZMAT-TECH", "Hazardous Materials Technician", Fire, "NFPA 470", 12, "Hazmat technician level; annual refresher.", "hazmat", "osha"),
+ P("FO1", "Fire Officer I", Fire, "NFPA 1021 (Pro Board / IFSAC)", null, "Company officer certification.", "nfpa", "officer"),
+ P("FO2", "Fire Officer II", Fire, "NFPA 1021 (Pro Board / IFSAC)", null, "Fire officer II certification.", "nfpa", "officer"),
+ P("FI1", "Fire Instructor I", Fire, "NFPA 1041 (Pro Board / IFSAC)", null, "Fire service instructor I.", "nfpa", "instructor"),
+ P("FINSP1", "Fire Inspector I", Fire, "NFPA 1031 (Pro Board / IFSAC)", null, "Fire inspector I.", "nfpa", "inspector", "prevention"),
+ P("FIRE-CONED", "Fire Continuing Education", Fire, "State fire marshal / training authority", 24, "Recertification con-ed cycle for state fire certifications.", "recert", "hours").Credits(24m),
+
+ // ---- EMS (national registry AND state license are separate credentials) ---------------------
+ P("NREMT-EMR", "NREMT Emergency Medical Responder", EMS, "National Registry of EMTs", 24, "National registry EMR certification (2-year cycle).", "nremt", "emr").Credits(16m),
+ P("NREMT-EMT", "NREMT Emergency Medical Technician", EMS, "National Registry of EMTs", 24, "National registry EMT certification (2-year cycle, NCCP).", "nremt", "emt").Credits(40m),
+ P("NREMT-AEMT", "NREMT Advanced EMT", EMS, "National Registry of EMTs", 24, "National registry AEMT certification (2-year cycle, NCCP).", "nremt", "aemt").Credits(50m),
+ P("NREMT-P", "NREMT Paramedic", EMS, "National Registry of EMTs", 24, "National registry paramedic certification (2-year cycle, NCCP).", "nremt", "paramedic", "medic").Credits(60m),
+ P("STATE-EMT", "State EMT License", EMS, "State EMS office", 24, "State EMT license or certification; number and expiry differ from the national registry card.", "license", "state", "emt"),
+ P("STATE-AEMT", "State Advanced EMT License", EMS, "State EMS office", 24, "State AEMT license.", "license", "state", "aemt"),
+ P("STATE-P", "State Paramedic License", EMS, "State EMS office", 24, "State paramedic license.", "license", "state", "paramedic"),
+ P("BLS", "BLS Provider (CPR/AED)", Med, "American Heart Association / Red Cross", 24, "Basic Life Support card (eCard number).", "cpr", "aed", "aha"),
+ P("ACLS", "ACLS Provider", Med, "American Heart Association", 24, "Advanced Cardiovascular Life Support card.", "aha", "cardiac"),
+ P("PALS", "PALS Provider", Med, "American Heart Association", 24, "Pediatric Advanced Life Support card.", "aha", "pediatric"),
+ P("PHTLS", "PHTLS Provider", Med, "NAEMT", 48, "Prehospital Trauma Life Support card (4-year cycle).", "naemt", "trauma"),
+ P("MD-SKILLS", "Medical Director Skills Verification", EMS, "Agency medical director", 12, "Annual medical-director skill verification sign-off.", "medical director", "skills").Verified(),
+
+ // ---- SAR --------------------------------------------------------------------------------------
+ P("SARTECH3", "SARTECH III", SAR, "NASAR", null, "NASAR SARTECH III.", "nasar", "search"),
+ P("SARTECH2", "SARTECH II", SAR, "NASAR", 36, "NASAR SARTECH II (3-year recert).", "nasar", "search"),
+ P("SARTECH1", "SARTECH I", SAR, "NASAR", 36, "NASAR SARTECH I (3-year recert).", "nasar", "search"),
+ P("ROPE-TECH", "Rope Rescue Technician", SAR, "NFPA 1006 / agency", 36, "Technical rope rescue technician.", "rope", "rescue", "technical"),
+ P("SWIFTWATER", "Swiftwater Rescue Technician", SAR, "NFPA 1006 / agency", 36, "Swiftwater / flood rescue technician.", "water", "flood", "rescue"),
+ P("AVALANCHE", "Avalanche Rescue", SAR, "AIARE / agency", 36, "Avalanche rescue and companion rescue certification.", "snow", "rescue"),
+ P("WFR", "Wilderness First Responder", Med, "WMA / NOLS / SOLO", 36, "Wilderness First Responder (3-year cycle).", "wilderness", "medical"),
+ P("WFA", "Wilderness First Aid", Med, "WMA / NOLS / SOLO / Red Cross", 24, "Wilderness First Aid (2-year cycle).", "wilderness", "first aid"),
+ P("K9-TEAM", "K9 Search Team Certification", SAR, "NASAR / state K9 standard", 24, "Handler-and-dog team certification (air scent, trailing, HRD).", "canine", "dog", "handler"),
+ P("HELO-OPS", "Helicopter Operations", SAR, "Agency / hoist program", 12, "Helicopter operations and hoist safety currency.", "aviation", "hoist"),
+
+ // ---- Wildland (NWCG) --------------------------------------------------------------------------
+ P("NWCG-FFT2", "NWCG Firefighter Type 2 (FFT2)", Wild, "NWCG", 60, "Entry-level wildland firefighter position qualification (currency 5 years).", "nwcg", "red card", "wildland"),
+ P("NWCG-FFT1", "NWCG Firefighter Type 1 (FFT1)", Wild, "NWCG", 60, "Squad boss position qualification.", "nwcg", "wildland"),
+ P("NWCG-CRWB", "NWCG Crew Boss (CRWB)", Wild, "NWCG", 60, "Crew boss position qualification.", "nwcg", "wildland"),
+ P("NWCG-ENGB", "NWCG Engine Boss (ENGB)", Wild, "NWCG", 60, "Engine boss position qualification.", "nwcg", "wildland"),
+ P("NWCG-DIVS", "NWCG Division/Group Supervisor (DIVS)", Wild, "NWCG", 60, "Division/group supervisor position qualification.", "nwcg", "wildland"),
+ P("RED-CARD", "Incident Qualification Card (Red Card)", Wild, "NWCG / agency", 12, "Annual incident qualification card; carries the WCT fitness level (Arduous / Moderate / Light).", "nwcg", "wct", "fitness", "arduous"),
+ P("RT-130", "RT-130 Annual Fireline Safety Refresher", Wild, "NWCG", 12, "Annual wildland fire safety refresher.", "nwcg", "refresher"),
+ P("WCT", "Work Capacity Test", Wild, "NWCG", 12, "Annual pack test; record the level (Arduous / Moderate / Light) in the number field.", "pack test", "fitness"),
+
+ // ---- Emergency management (FEMA courses never expire) ---------------------------------------
+ P("ICS-100", "ICS-100 Introduction to ICS", EM, "FEMA EMI", null, "FEMA IS-100 / ICS-100 (does not expire).", "fema", "ics", "nims"),
+ P("ICS-200", "ICS-200 Basic ICS", EM, "FEMA EMI", null, "FEMA IS-200 / ICS-200 (does not expire).", "fema", "ics", "nims"),
+ P("ICS-300", "ICS-300 Intermediate ICS", EM, "FEMA EMI / state", null, "Intermediate ICS for expanding incidents (does not expire).", "fema", "ics", "nims"),
+ P("ICS-400", "ICS-400 Advanced ICS", EM, "FEMA EMI / state", null, "Advanced ICS for command and general staff (does not expire).", "fema", "ics", "nims"),
+ P("IS-700", "IS-700 NIMS Introduction", EM, "FEMA EMI", null, "FEMA IS-700 (does not expire).", "fema", "nims"),
+ P("IS-800", "IS-800 National Response Framework", EM, "FEMA EMI", null, "FEMA IS-800 (does not expire).", "fema", "nrf"),
+ P("CEM", "Certified Emergency Manager (CEM)", EM, "IAEM", 60, "IAEM CEM; 5-year recert with documented contact hours.", "iaem", "manager").Credits(100m),
+ P("AEM", "Associate Emergency Manager (AEM)", EM, "IAEM", 60, "IAEM AEM; 5-year recert with documented contact hours.", "iaem", "manager").Credits(100m),
+
+ // ---- Industrial / business --------------------------------------------------------------------
+ P("OSHA-10", "OSHA 10-Hour", Ind, "OSHA Outreach", null, "OSHA 10-hour outreach card (no federal expiry; employer policy may impose one).", "osha", "safety"),
+ P("OSHA-30", "OSHA 30-Hour", Ind, "OSHA Outreach", null, "OSHA 30-hour outreach card.", "osha", "safety"),
+ P("HAZWOPER-40", "HAZWOPER 40-Hour", Ind, "OSHA 1910.120", 12, "HAZWOPER 40-hour initial; the 8-hour annual refresher is a separate type.", "osha", "hazwoper"),
+ P("HAZWOPER-8", "HAZWOPER 8-Hour Annual Refresher", Ind, "OSHA 1910.120", 12, "Annual HAZWOPER refresher.", "osha", "hazwoper", "refresher"),
+ P("FORKLIFT", "Powered Industrial Truck Operator", Ind, "OSHA 1910.178", 36, "Forklift operator evaluation (3-year re-evaluation).", "osha", "forklift", "pit"),
+ P("CONFINED-SPACE", "Confined Space Entry", Ind, "OSHA 1910.146", 12, "Permit-required confined space entrant / attendant / supervisor.", "osha"),
+ P("LOTO", "Lockout/Tagout Authorized Employee", Ind, "OSHA 1910.147", 12, "Lockout/tagout authorized employee training.", "osha"),
+ P("RESP-FIT", "Respirator Fit Test", Ind, "OSHA 1910.134", 12, "Annual respirator fit test.", "osha", "scba", "respirator"),
+ P("HEARING", "Hearing Conservation", Ind, "OSHA 1910.95", 12, "Annual audiometric test / hearing conservation training.", "osha"),
+ P("FA-CPR-AED", "First Aid / CPR / AED", Med, "Red Cross / AHA / NSC", 24, "Workplace first aid, CPR and AED card.", "cpr", "aed", "first aid"),
+ P("BBP", "Bloodborne Pathogens", Ind, "OSHA 1910.1030", 12, "Annual bloodborne pathogens training.", "osha"),
+ P("TWIC", "Transportation Worker Identification Credential", Ind, "TSA", 60, "TWIC card (5-year).", "tsa", "port", "maritime"),
+ P("NCCCO", "NCCCO Crane Operator", Ind, "NCCCO", 60, "Certified crane operator (5-year recert).", "crane", "operator"),
+
+ // ---- Security ---------------------------------------------------------------------------------
+ P("GUARD-CARD", "Security Guard Registration / License", Sec, "State licensing board (e.g. CA BSIS)", 24, "State guard card; annual con-ed hours tracked as credits.", "bsis", "license", "guard").Credits(8m),
+ P("ARMED", "Armed Security Endorsement", Sec, "State licensing board", 24, "Exposed firearm permit / armed endorsement; requalification is a separate short-cycle type.", "firearm", "license"),
+ P("FIREARM-REQUAL", "Firearm Requalification", Sec, "Agency range master / state", 6, "Quarterly or semi-annual firearm requalification.", "firearm", "range", "qualification").Verified(),
+ P("BATON-OC", "Baton / OC Spray Permit", Sec, "State licensing board", 24, "Baton and chemical agent permits.", "baton", "oc", "pepper spray"),
+ P("USE-OF-FORCE", "Use of Force Recertification", Sec, "Agency / state", 12, "Annual use-of-force recertification.", "force", "recert"),
+ P("PI-LICENSE", "Private Investigator License", Sec, "State licensing board", 24, "Private investigator license.", "investigator", "license"),
+ P("BC-SWL", "BC Security Worker Licence", Sec, "BC Security Programs Division", 24, "British Columbia security worker licence.", "canada", "bc", "licence"),
+
+ // ---- Driver qualification file (49 CFR 391) -----------------------------------------------------
+ P("CDL-A", "Commercial Driver's License – Class A", Drv, "State DMV (49 CFR 383)", 48, "Class A CDL; record endorsements as separate types.", "cdl", "dot", "fmcsa", "driver"),
+ P("CDL-B", "Commercial Driver's License – Class B", Drv, "State DMV (49 CFR 383)", 48, "Class B CDL (straight trucks, buses).", "cdl", "dot", "fmcsa", "driver", "bus"),
+ P("CDL-P", "CDL Passenger Endorsement (P)", Drv, "State DMV", 48, "Passenger endorsement; expiry follows the CDL.", "cdl", "endorsement", "bus", "shuttle"),
+ P("CDL-S", "CDL School Bus Endorsement (S)", Drv, "State DMV", 48, "School bus endorsement.", "cdl", "endorsement", "school bus"),
+ P("CDL-H", "CDL Hazmat Endorsement (H)", Drv, "State DMV + TSA STA", 60, "Hazardous materials endorsement with TSA security threat assessment (5-year).", "cdl", "endorsement", "hazmat", "tsa"),
+ P("CDL-N", "CDL Tank Endorsement (N)", Drv, "State DMV", 48, "Tank vehicle endorsement.", "cdl", "endorsement", "tanker"),
+ P("CDL-T", "CDL Doubles/Triples Endorsement (T)", Drv, "State DMV", 48, "Doubles/triples endorsement.", "cdl", "endorsement"),
+ P("DOT-MED", "DOT Medical Examiner's Certificate", Drv, "FMCSA-registered medical examiner (49 CFR 391.43)", 24, "DOT medical card; maximum 24 months, often 12 when restricted. Separate expiry from the CDL.", "dot", "medical", "physical", "fmcsa"),
+ P("MVR-REVIEW", "Annual Motor Vehicle Record Review", Drv, "Motor carrier (49 CFR 391.25)", 12, "Annual MVR review of each driver's record.", "dot", "mvr", "fmcsa"),
+ P("CLEARINGHOUSE", "FMCSA Clearinghouse Annual Query", Drv, "Motor carrier (49 CFR 382.701)", 12, "Annual limited query of the Drug & Alcohol Clearinghouse.", "dot", "fmcsa", "drug", "alcohol"),
+ P("ELDT", "Entry-Level Driver Training Completion", Drv, "FMCSA Training Provider Registry (49 CFR 380)", null, "ELDT completion (one-time; does not expire).", "dot", "fmcsa", "training"),
+ P("ROAD-TEST", "Road Test Certificate", Drv, "Motor carrier (49 CFR 391.31)", null, "Road test certificate or equivalent (does not expire).", "dot", "fmcsa"),
+ P("DA-PROGRAM", "Drug & Alcohol Program Enrollment", Drv, "Motor carrier / consortium (49 CFR 382)", 12, "Enrollment in a DOT drug and alcohol testing program.", "dot", "fmcsa", "testing"),
+ P("SCHOOL-BUS-PERMIT", "State School Bus / Passenger Driver Permit", Drv, "State education / DMV", 12, "State school-bus or passenger driver permit.", "bus", "permit", "state"),
+
+ // ---- Vehicle / apparatus (unit-scoped) -------------------------------------------------------------
+ U("DOT-INSPECTION", "DOT Annual Vehicle Inspection", Veh, "Qualified inspector (49 CFR 396.17)", 12, "Annual periodic inspection of a commercial motor vehicle.", "dot", "fmcsa", "inspection", "vehicle"),
+ U("VEH-REG", "Vehicle Registration", Veh, "State DMV", 12, "Vehicle registration renewal.", "registration", "plate", "vehicle"),
+ U("VEH-INS", "Vehicle Insurance", Veh, "Insurer", 12, "Commercial auto insurance certificate.", "insurance", "vehicle"),
+ U("IFTA", "IFTA Decal / License", Veh, "State base jurisdiction", 12, "International Fuel Tax Agreement decal.", "fuel tax", "vehicle"),
+ U("AMB-PERMIT", "State Ambulance Permit", Veh, "State EMS office", 12, "Per-rig ambulance permit / inspection.", "ambulance", "ems", "permit"),
+ U("NFPA-1911-PUMP", "NFPA 1911 Annual Pump Test", Veh, "Third-party tester / department", 12, "Annual fire pump service test.", "nfpa", "pump", "apparatus", "engine"),
+ U("AERIAL-CERT", "Aerial Ladder Certification", Veh, "Third-party tester (NFPA 1911)", 12, "Annual aerial device inspection and load test.", "nfpa", "aerial", "ladder", "apparatus"),
+ U("GROUND-LADDER", "Ground Ladder Test", Veh, "Third-party tester (NFPA 1932)", 12, "Annual ground ladder service test.", "nfpa", "ladder"),
+ U("CRANE-INSPECTION", "Crane / Hoist Annual Inspection", Veh, "Qualified person (OSHA 1910.179 / 1926.1412)", 12, "Annual inspection of a crane or hoist modeled as a unit.", "osha", "crane", "inspection"),
+ U("EQUIP-INSPECTION", "Equipment Annual Inspection", Veh, "Manufacturer / third party", 12, "Annual safety inspection certificate for a piece of equipment modeled as a unit.", "inspection", "equipment")
+ };
+ }
+
+ #endregion
+ }
+}
diff --git a/Core/Resgrid.Model/Certifications/CertificationWorkflowTriggers.cs b/Core/Resgrid.Model/Certifications/CertificationWorkflowTriggers.cs
new file mode 100644
index 00000000..5df92620
--- /dev/null
+++ b/Core/Resgrid.Model/Certifications/CertificationWorkflowTriggers.cs
@@ -0,0 +1,22 @@
+using System.Linq;
+
+namespace Resgrid.Model.Certifications
+{
+ /// The Phase D workflow triggers (23 activated, 87–93 added; plan D7).
+ public static class CertificationWorkflowTriggers
+ {
+ public static readonly int[] Triggers =
+ {
+ (int)WorkflowTriggerEventType.CertificationExpiring,
+ (int)WorkflowTriggerEventType.CertificationAdded,
+ (int)WorkflowTriggerEventType.CertificationRenewed,
+ (int)WorkflowTriggerEventType.CertificationExpired,
+ (int)WorkflowTriggerEventType.CertificationRoleRemoved,
+ (int)WorkflowTriggerEventType.CertificationStatusChanged,
+ (int)WorkflowTriggerEventType.UnitCertificationExpiring,
+ (int)WorkflowTriggerEventType.UnitCertificationExpired
+ };
+
+ public static bool IsCertification(int trigger) => Triggers.Contains(trigger);
+ }
+}
diff --git a/Core/Resgrid.Model/DepartmentCertificationType.cs b/Core/Resgrid.Model/DepartmentCertificationType.cs
index 1c2bbde9..2eb4db05 100644
--- a/Core/Resgrid.Model/DepartmentCertificationType.cs
+++ b/Core/Resgrid.Model/DepartmentCertificationType.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -19,10 +20,59 @@ public class DepartmentCertificationType : IEntity
[ForeignKey("DepartmentId")]
public virtual Department Department { get; set; }
+ /// Display name and the match key for untyped legacy records (plan D1.1 keeps it as the name).
[Required]
[MaxLength(100)]
public string Type { get; set; }
+ // ---- Phase D typed catalog (M0213, additive; plan D1.1). Code is the stable join key (decision 25):
+ // unique per department while not deleted, backfilled from Type, immutable once a requirement references it.
+
+ [MaxLength(50)]
+ public string Code { get; set; }
+
+ /// .
+ public int Category { get; set; }
+
+ /// ; immutable once records of the type exist.
+ public int AppliesTo { get; set; }
+
+ public string Description { get; set; }
+
+ [MaxLength(200)]
+ public string IssuingAuthority { get; set; }
+
+ /// Suggested validity used to pre-fill a new record's expiry; null when unknown.
+ public int? DefaultValidityMonths { get; set; }
+
+ /// Records of this type never expire (FEMA ICS/IS courses, ELDT, road test).
+ public bool NeverExpires { get; set; }
+
+ /// Continuing-education hours a holder needs per renewal cycle (person-scoped only).
+ public decimal? RenewalCreditHoursRequired { get; set; }
+
+ /// New records stay PendingVerification until a supervisor signs them off (person-scoped only).
+ public bool RequiresVerification { get; set; }
+
+ public bool IsActive { get; set; } = true;
+
+ public bool IsDeleted { get; set; }
+
+ public DateTime? AddedOn { get; set; }
+
+ public string AddedByUserId { get; set; }
+
+ public DateTime? EditedOn { get; set; }
+
+ public string EditedByUserId { get; set; }
+
+ [NotMapped]
+ public bool IsUnitScoped => AppliesTo == (int)CertificationAppliesTo.Unit;
+
+ /// The display name: legacy Type text.
+ [NotMapped]
+ public string Name => Type;
+
[NotMapped]
[JsonIgnore]
public object IdValue
@@ -41,6 +91,6 @@ public object IdValue
public int IdType => 0;
[NotMapped]
- public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department" };
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department", "IsUnitScoped", "Name" };
}
}
diff --git a/Core/Resgrid.Model/Events/CertificationEvents.cs b/Core/Resgrid.Model/Events/CertificationEvents.cs
new file mode 100644
index 00000000..cf545adf
--- /dev/null
+++ b/Core/Resgrid.Model/Events/CertificationEvents.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace Resgrid.Model.Events
+{
+ // Workforce & Business Operations plan, Phase D7 (decision 22). Published through IEventAggregator by the
+ // certification service (87, 88, 91) and the expiry worker 34 (23, 89, 90, 92, 93). The PersonnelCertification
+ // payload keeps its ADP catalog 6 envelopes; the workflow context builder renders them through SafeDisplay.
+
+ /// Trigger 87: a personnel certification record was created.
+ public class CertificationAddedEvent
+ {
+ public int DepartmentId { get; set; }
+ public PersonnelCertification Certification { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ }
+
+ /// Trigger 88: a record was renewed (a new record replaced it or its expiry was extended).
+ public class CertificationRenewedEvent
+ {
+ public int DepartmentId { get; set; }
+ public PersonnelCertification Certification { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public DateTime? PreviousExpiresOn { get; set; }
+ }
+
+ /// Trigger 89: the worker flipped an Active record to Expired.
+ public class CertificationExpiredEvent
+ {
+ public int DepartmentId { get; set; }
+ public PersonnelCertification Certification { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ }
+
+ /// Trigger 90: the enforcement pass removed a member from a role after the grace period.
+ public class CertificationRoleRemovedEvent
+ {
+ public int DepartmentId { get; set; }
+ public string UserId { get; set; }
+ public int PersonnelRoleId { get; set; }
+ public string RoleName { get; set; }
+ public int DepartmentCertificationTypeId { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public DateTime? ExpiresOn { get; set; }
+ public DateTime? GraceDeadline { get; set; }
+ }
+
+ /// Trigger 91: a record's status changed (suspend / revoke / reinstate / verify / trainee).
+ public class CertificationStatusChangedEvent
+ {
+ public int DepartmentId { get; set; }
+ public PersonnelCertification Certification { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public int OldStatus { get; set; }
+ public int NewStatus { get; set; }
+ public string Reason { get; set; }
+ public string ChangedByUserId { get; set; }
+ }
+
+ /// Trigger 92 / notification 28: a unit certification reaches a lead day.
+ public class UnitCertificationExpiringEvent
+ {
+ public int DepartmentId { get; set; }
+ public UnitCertification Certification { get; set; }
+ public string UnitName { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ public int DaysUntilExpiry { get; set; }
+ }
+
+ /// Trigger 93 / notification 29: the worker flipped a unit certification to Expired.
+ public class UnitCertificationExpiredEvent
+ {
+ public int DepartmentId { get; set; }
+ public UnitCertification Certification { get; set; }
+ public string UnitName { get; set; }
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
+ }
+}
diff --git a/Core/Resgrid.Model/Events/CertificationExpiringEvent.cs b/Core/Resgrid.Model/Events/CertificationExpiringEvent.cs
index 68d92f1b..8ab21630 100644
--- a/Core/Resgrid.Model/Events/CertificationExpiringEvent.cs
+++ b/Core/Resgrid.Model/Events/CertificationExpiringEvent.cs
@@ -5,6 +5,10 @@ public class CertificationExpiringEvent
public int DepartmentId { get; set; }
public PersonnelCertification Certification { get; set; }
public int DaysUntilExpiry { get; set; }
+
+ // Phase D (plan D7): typed catalog metadata; null for a legacy free-text record.
+ public string TypeCode { get; set; }
+ public string TypeName { get; set; }
}
}
diff --git a/Core/Resgrid.Model/Events/EventTypes.cs b/Core/Resgrid.Model/Events/EventTypes.cs
index 2ce12f3d..83bed695 100644
--- a/Core/Resgrid.Model/Events/EventTypes.cs
+++ b/Core/Resgrid.Model/Events/EventTypes.cs
@@ -81,6 +81,23 @@ public enum EventTypes
[NotMapped]
ModerationRequestCompleted = 24,
+ // Workforce & Business Operations plan Phase D (registry 25-29; plan D6). Department broadcasts raised by the
+ // certification expiry worker (34); 25-27 resolve the holder's group, 28-29 the unit's station group.
+ [Display(Name = "Certification Expiring")]
+ CertificationExpiring = 25,
+
+ [Display(Name = "Certification Expired")]
+ CertificationExpired = 26,
+
+ [Display(Name = "Role Removed for Certification")]
+ CertificationRoleRemoved = 27,
+
+ [Display(Name = "Unit Certification Expiring")]
+ UnitCertificationExpiring = 28,
+
+ [Display(Name = "Unit Certification Expired")]
+ UnitCertificationExpired = 29,
+
// -- Records (RMS) block 31-33 -- Identifier Allocation Registry section 3.5. 25-29 belong to
// Certifications and 30 is the buffer; neither may be taken here. 31 is raised by
// RecordsService.ReturnForCorrectionAsync and delivered to the author by IRecordsNotificationService
@@ -109,7 +126,13 @@ public static class EventOptions
EventTypes.PersonnelStaffingChanged,
EventTypes.UserAssignedToGroup,
EventTypes.RolesInGroupAvailabilityAlert,
- EventTypes.UnitTypesInGroupAvailabilityAlert
+ EventTypes.UnitTypesInGroupAvailabilityAlert,
+ // Phase D: LockToGroup resolves the holder's group (25-27) or the unit's station group (28-29).
+ EventTypes.CertificationExpiring,
+ EventTypes.CertificationExpired,
+ EventTypes.CertificationRoleRemoved,
+ EventTypes.UnitCertificationExpiring,
+ EventTypes.UnitCertificationExpired
};
public static HashSet PreviousDataEnabled = new HashSet()
diff --git a/Core/Resgrid.Model/Invoicing/InvoiceWorkflowPayload.cs b/Core/Resgrid.Model/Invoicing/InvoiceWorkflowPayload.cs
index e56eb718..09c9f2bf 100644
--- a/Core/Resgrid.Model/Invoicing/InvoiceWorkflowPayload.cs
+++ b/Core/Resgrid.Model/Invoicing/InvoiceWorkflowPayload.cs
@@ -16,7 +16,7 @@ public static readonly (string Variable, string Property)[] Variables =
("currency", "Currency"), ("sub_total", "SubTotal"), ("discount_amount", "DiscountAmount"), ("tax_amount", "TaxAmount"),
("total", "Total"), ("amount_paid", "AmountPaid"), ("balance", "Balance"), ("issued_on", "IssuedOn"), ("due_on", "DueOn"),
("sent_on", "SentOn"), ("paid_on", "PaidOn"), ("payment_amount", "PaymentAmount"), ("payment_method", "PaymentMethod"),
- ("payment_id", "PaymentId"), ("old_status", "OldStatus")
+ ("payment_id", "PaymentId"), ("old_status", "OldStatus"), ("pay_url", "PayUrl")
};
public static readonly int[] Triggers = { 52, 53, 54, 55, 56, 57, 94, 95 };
diff --git a/Core/Resgrid.Model/Invoicing/OnlinePaymentModels.cs b/Core/Resgrid.Model/Invoicing/OnlinePaymentModels.cs
new file mode 100644
index 00000000..fb6b064b
--- /dev/null
+++ b/Core/Resgrid.Model/Invoicing/OnlinePaymentModels.cs
@@ -0,0 +1,450 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace Resgrid.Model.Invoicing
+{
+ /// Live or sandbox keys behind a connection (plan B2.2).
+ public enum PaymentEnvironments
+ {
+ Live = 0,
+ Sandbox = 1
+ }
+
+ /// Where a payment request was started (plan B2.2).
+ public enum PaymentRequestSources
+ {
+ PayPage = 0,
+ Web = 1,
+ Api = 2
+ }
+
+ /// What the receiver did with a provider webhook event (plan B2.2).
+ public enum PaymentEventOutcomes
+ {
+ Applied = 0,
+ Ignored = 1,
+ Duplicate = 2,
+ Failed = 3,
+ Rejected = 4
+ }
+
+ /// Stages of a card dispute against an online payment (plan B2.4): opened flags the payment and fires trigger 95 once; won restores it; lost is treated as a refund.
+ public enum InvoiceDisputeStages
+ {
+ Opened = 0,
+ Won = 1,
+ Lost = 2
+ }
+
+ /// Provider-neutral meaning of a webhook event (plan B2.3).
+ public enum PaymentEventKinds
+ {
+ Unknown = 0,
+ ConnectionRevoked = 1,
+ ConnectionUpdated = 2,
+ PaymentProcessing = 3,
+ PaymentSucceeded = 4,
+ PaymentFailed = 5,
+ RequestExpired = 6,
+ PaymentRefunded = 7,
+ PaymentDisputed = 8,
+ DisputeClosed = 9
+ }
+
+ ///
+ /// A department's own payment-provider account connected to Resgrid (plan B2.2). Stripe stores only the account
+ /// id; the token ciphertext columns are reserved for v2 providers. System credentials: never cataloged for ADP,
+ /// exported, searched or placed in a Workflow payload. Masked in every DTO.
+ ///
+ public class DepartmentPaymentConnection : IEntity
+ {
+ [Required]
+ public string DepartmentPaymentConnectionId { get; set; }
+
+ [Required]
+ public int DepartmentId { get; set; }
+
+ /// .
+ public int Provider { get; set; }
+
+ /// .
+ public int Status { get; set; }
+
+ /// .
+ public int Environment { get; set; }
+
+ /// The provider's account id (acct_…). Not a secret, but masked wherever it is shown.
+ public string ExternalAccountId { get; set; }
+ public string DisplayName { get; set; }
+ public string Country { get; set; }
+ public string DefaultCurrency { get; set; }
+
+ /// Observed capabilities as a JSON object of name → active (card_payments, us_bank_account_ach_payments, charges_enabled).
+ public string CapabilitiesJson { get; set; }
+ public string ScopesCsv { get; set; }
+
+ /// Reserved for v2 token-holding providers; null for Stripe.
+ public string AccessTokenCiphertext { get; set; }
+ public string RefreshTokenCiphertext { get; set; }
+ public DateTime? TokenExpiresOn { get; set; }
+
+ public DateTime ConnectedOn { get; set; }
+ public string ConnectedByUserId { get; set; }
+ public DateTime? DisconnectedOn { get; set; }
+ public DateTime? LastVerifiedOn { get; set; }
+ public string LastError { get; set; }
+ public bool IsDefault { get; set; }
+ public bool IsDeleted { get; set; }
+ public DateTime AddedOn { get; set; }
+ public string AddedByUserId { get; set; }
+ public DateTime? EditedOn { get; set; }
+ public string EditedByUserId { get; set; }
+
+ [NotMapped]
+ public bool IsUsable => Status == (int)PaymentConnectionStatuses.Connected && !IsDeleted;
+
+ /// acct_…1234: the last four characters only (plan B2.6).
+ [NotMapped]
+ public string MaskedExternalAccountId => Mask(ExternalAccountId);
+
+ public static string Mask(string externalAccountId)
+ {
+ if (string.IsNullOrWhiteSpace(externalAccountId)) return null;
+ var prefix = externalAccountId.IndexOf('_') > 0 ? externalAccountId.Substring(0, externalAccountId.IndexOf('_') + 1) : string.Empty;
+ var tail = externalAccountId.Length > 4 ? externalAccountId.Substring(externalAccountId.Length - 4) : externalAccountId;
+ return prefix + "…" + tail;
+ }
+
+ [NotMapped]
+ public string TableName => "DepartmentPaymentConnections";
+
+ [NotMapped]
+ public string IdName => "DepartmentPaymentConnectionId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return DepartmentPaymentConnectionId; }
+ set { DepartmentPaymentConnectionId = (string)value; }
+ }
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "IsUsable", "MaskedExternalAccountId" };
+ }
+
+ /// One attempt to collect an invoice's remaining balance online (plan B2.2): a hosted Checkout Session.
+ public class InvoicePaymentRequest : IEntity
+ {
+ [Required]
+ public string InvoicePaymentRequestId { get; set; }
+
+ [Required]
+ public string InvoiceId { get; set; }
+
+ [Required]
+ public int DepartmentId { get; set; }
+
+ public string DepartmentPaymentConnectionId { get; set; }
+ public int Provider { get; set; }
+ public decimal Amount { get; set; }
+ public string Currency { get; set; }
+
+ /// .
+ public int Status { get; set; }
+
+ /// The provider's session id (cs_…).
+ public string ExternalReference { get; set; }
+ public string PaymentIntentId { get; set; }
+ public string HostedUrl { get; set; }
+ public DateTime ExpiresOn { get; set; }
+ public DateTime? CompletedOn { get; set; }
+ public string InvoicePaymentId { get; set; }
+
+ /// .
+ public int Source { get; set; }
+ public string CreatedByUserId { get; set; }
+ public string LastError { get; set; }
+ public DateTime AddedOn { get; set; }
+ public DateTime UpdatedOn { get; set; }
+
+ [NotMapped]
+ public bool IsOpen => Status is (int)PaymentRequestStatuses.Created or (int)PaymentRequestStatuses.Opened or (int)PaymentRequestStatuses.Processing;
+
+ public static bool IsTerminal(int status) => status is (int)PaymentRequestStatuses.Completed or (int)PaymentRequestStatuses.Expired or (int)PaymentRequestStatuses.Cancelled or (int)PaymentRequestStatuses.Failed;
+
+ [NotMapped]
+ public string TableName => "InvoicePaymentRequests";
+
+ [NotMapped]
+ public string IdName => "InvoicePaymentRequestId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return InvoicePaymentRequestId; }
+ set { InvoicePaymentRequestId = (string)value; }
+ }
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "IsOpen" };
+ }
+
+ ///
+ /// A received provider webhook event (plan B2.2): the idempotency ledger and the forensic copy of the body.
+ /// The body contains no card data by construction (hosted Checkout); rows are purged after
+ /// PaymentConnectConfig.EventRetentionDays.
+ ///
+ public class PaymentConnectEvent : IEntity
+ {
+ [Required]
+ public string PaymentConnectEventId { get; set; }
+
+ public int Provider { get; set; }
+
+ /// The provider's event id (evt_…); unique per provider.
+ public string ExternalEventId { get; set; }
+ public string ExternalAccountId { get; set; }
+ public string EventType { get; set; }
+ public int? DepartmentId { get; set; }
+ public bool LiveMode { get; set; }
+ public DateTime ReceivedOn { get; set; }
+ public DateTime? ProcessedOn { get; set; }
+
+ /// .
+ public int Outcome { get; set; }
+ public string Error { get; set; }
+ public string PayloadJson { get; set; }
+
+ [NotMapped]
+ public string TableName => "PaymentProviderEvents";
+
+ [NotMapped]
+ public string IdName => "PaymentConnectEventId";
+
+ [NotMapped]
+ public int IdType => 1;
+
+ [NotMapped]
+ [JsonIgnore]
+ public object IdValue
+ {
+ get { return PaymentConnectEventId; }
+ set { PaymentConnectEventId = (string)value; }
+ }
+
+ [NotMapped]
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName" };
+ }
+
+ // ---- provider seam DTOs (plan B2.3) -----------------------------------------------------------------------
+
+ /// What a provider reports about a connected account.
+ public class PaymentConnectionFacts
+ {
+ public string ExternalAccountId { get; set; }
+ public string DisplayName { get; set; }
+ public string Country { get; set; }
+ public string DefaultCurrency { get; set; }
+ public bool ChargesEnabled { get; set; }
+ /// Capability name → active.
+ public Dictionary Capabilities { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ public string ScopesCsv { get; set; }
+ public bool LiveMode { get; set; }
+ /// Reserved for v2 token-holding providers; null for Stripe.
+ public string AccessToken { get; set; }
+ public string RefreshToken { get; set; }
+ public DateTime? TokenExpiresOn { get; set; }
+ }
+
+ /// Everything the provider needs to open a hosted payment page for one invoice balance.
+ public class PaymentRequestSpec
+ {
+ public string ExternalAccountId { get; set; }
+ public string PaymentRequestId { get; set; }
+ public int DepartmentId { get; set; }
+ public string InvoiceId { get; set; }
+ public int InvoiceNumber { get; set; }
+ public decimal Amount { get; set; }
+ public string Currency { get; set; }
+ public string CustomerEmail { get; set; }
+ public IReadOnlyList PaymentMethodTypes { get; set; }
+ public string SuccessUrl { get; set; }
+ public string CancelUrl { get; set; }
+ public DateTime ExpiresOn { get; set; }
+ public string IdempotencyKey { get; set; }
+ }
+
+ /// The provider's answer to .
+ public class PaymentRequestCreation
+ {
+ public string ExternalReference { get; set; }
+ public string PaymentIntentId { get; set; }
+ public string HostedUrl { get; set; }
+ public DateTime ExpiresOn { get; set; }
+ }
+
+ /// Current state of a hosted payment request, read back from the provider (reconciliation).
+ public class PaymentRequestState
+ {
+ /// open, complete, expired.
+ public string SessionStatus { get; set; }
+ /// paid, unpaid, no_payment_required.
+ public string PaymentStatus { get; set; }
+ public string PaymentIntentId { get; set; }
+ public PaymentChargeFacts Charge { get; set; }
+ }
+
+ /// Settled-charge facts for the payment row: fee/net from the balance transaction, method summary, receipt.
+ public class PaymentChargeFacts
+ {
+ public string ChargeId { get; set; }
+ public string PaymentIntentId { get; set; }
+ public decimal Amount { get; set; }
+ public string Currency { get; set; }
+ public decimal? Fee { get; set; }
+ public decimal? Net { get; set; }
+ public string PayerEmail { get; set; }
+ public string MethodSummary { get; set; }
+ public string ReceiptUrl { get; set; }
+ public DateTime? PaidOn { get; set; }
+ }
+
+ /// Provider-neutral webhook event (plan B2.3).
+ public class PaymentProviderEventEnvelope
+ {
+ public int Provider { get; set; }
+ public string ExternalEventId { get; set; }
+ public string ExternalAccountId { get; set; }
+ public string EventType { get; set; }
+ public PaymentEventKinds Kind { get; set; }
+ public bool LiveMode { get; set; }
+ public DateTime OccurredOn { get; set; }
+ /// Session id (cs_…) when the event carries one.
+ public string ExternalReference { get; set; }
+ public string PaymentIntentId { get; set; }
+ public string ChargeId { get; set; }
+ public decimal? Amount { get; set; }
+ public string Currency { get; set; }
+ public decimal? RefundedAmount { get; set; }
+ public string PayerEmail { get; set; }
+ public string MethodSummary { get; set; }
+ public string ReceiptUrl { get; set; }
+ /// DisputeClosed: true when the dispute was lost.
+ public bool? DisputeLost { get; set; }
+ /// ConnectionUpdated: charges_enabled and capabilities as observed.
+ public bool? ChargesEnabled { get; set; }
+ public Dictionary Capabilities { get; set; }
+ }
+
+ /// Result of receiving one webhook body.
+ public class PaymentWebhookReceipt
+ {
+ /// True when the receiver answered 2xx (applied, ignored or duplicate).
+ public bool Accepted { get; set; }
+ public PaymentEventOutcomes Outcome { get; set; }
+ public string Error { get; set; }
+ }
+
+ /// The Connect OAuth hand-off (plan B2.4).
+ public class PaymentConnectBegin
+ {
+ public string AuthorizeUrl { get; set; }
+ public string State { get; set; }
+ public DateTime ExpiresOn { get; set; }
+ }
+
+ /// What the anonymous pay page shows (plan B2.5): number, amount, due date, department name. Nothing else.
+ public class PayPageModel
+ {
+ public bool Available { get; set; }
+ /// Resource key explaining why the page is unavailable (expired link, paid, not offered).
+ public string UnavailableReason { get; set; }
+ public string InvoiceId { get; set; }
+ public int DepartmentId { get; set; }
+ public int InvoiceNumber { get; set; }
+ public string DepartmentName { get; set; }
+ public decimal AmountDue { get; set; }
+ public string Currency { get; set; }
+ public DateTime? DueOn { get; set; }
+ public bool Paid { get; set; }
+ /// An open request whose hosted page can be resumed.
+ public string OpenHostedUrl { get; set; }
+ public bool Processing { get; set; }
+ }
+
+ /// The department's online-payments posture for the settings page and the API (plan B2.5).
+ public class OnlinePaymentsStatus
+ {
+ /// PaymentConnectConfig.Enabled and the Payments.StripeConnect cluster flag.
+ public bool AvailableInCluster { get; set; }
+ /// Invoicing.OnlinePayments evaluates true for the department.
+ public bool FlagEnabled { get; set; }
+ /// DepartmentBillingIdentities.OnlinePaymentsEnabled.
+ public bool EnabledByDepartment { get; set; }
+ public bool ShowPayOnlineOnDocuments { get; set; }
+ public int PayLinkExpiryDays { get; set; }
+ public IReadOnlyList AllowedPaymentMethods { get; set; } = new List();
+ /// The default connection, masked; null when none.
+ public DepartmentPaymentConnection Connection { get; set; }
+ public Dictionary Capabilities { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ /// All gates hold: a pay link can be created right now.
+ public bool CanCollect { get; set; }
+ /// The gate that fails first, as a resource key (payments_*), or null.
+ public string BlockedReason { get; set; }
+ }
+
+ ///
+ /// The Phase B columns Advanced Data Protection catalogs (ADP catalog 26, plan B2.2): customer-contact and
+ /// payment-reference text under the Contacts family. Amounts, statuses, numbers and dates stay metadata.
+ ///
+ public static class InvoicingProtectedFields
+ {
+ public const int CatalogVersion = 26;
+ public const string Family = "Contacts";
+
+ public static readonly IReadOnlyDictionary Get, Action Set)> BillingProfile =
+ new Dictionary, Action)>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["customerbillingprofiles.billingemail"] = (p => p.BillingEmail, (p, v) => p.BillingEmail = v)
+ };
+
+ public static readonly IReadOnlyDictionary Get, Action Set)> Invoice =
+ new Dictionary, Action)>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["invoices.senttoemail"] = (i => i.SentToEmail, (i, v) => i.SentToEmail = v),
+ ["invoices.notes"] = (i => i.Notes, (i, v) => i.Notes = v)
+ };
+
+ public static readonly IReadOnlyDictionary Get, Action Set)> Payment =
+ new Dictionary, Action)>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["invoicepayments.payeremail"] = (p => p.PayerEmail, (p, v) => p.PayerEmail = v),
+ ["invoicepayments.reference"] = (p => p.Reference, (p, v) => p.Reference = v),
+ ["invoicepayments.receipturl"] = (p => p.ReceiptUrl, (p, v) => p.ReceiptUrl = v),
+ ["invoicepayments.notes"] = (p => p.Notes, (p, v) => p.Notes = v)
+ };
+
+ /// Every cataloged field id, for tests and the catalog builder.
+ public static IEnumerable<(string Table, string Column)> All()
+ {
+ yield return ("CustomerBillingProfiles", "BillingEmail");
+ yield return ("Invoices", "SentToEmail");
+ yield return ("Invoices", "Notes");
+ yield return ("InvoicePayments", "PayerEmail");
+ yield return ("InvoicePayments", "Reference");
+ yield return ("InvoicePayments", "ReceiptUrl");
+ yield return ("InvoicePayments", "Notes");
+ }
+ }
+}
diff --git a/Core/Resgrid.Model/PermissionTypes.cs b/Core/Resgrid.Model/PermissionTypes.cs
index 2435729b..f40022b1 100644
--- a/Core/Resgrid.Model/PermissionTypes.cs
+++ b/Core/Resgrid.Model/PermissionTypes.cs
@@ -165,7 +165,14 @@ public enum PermissionTypes
/// Workforce & Business Operations plan Phase B (registry 40): create/edit/send/void invoices, record payments, rate cards, billing profiles. Defaults to department administrators.
ManageInvoicing = 40,
/// Workforce & Business Operations plan Phase B (registry 41): view invoices, aging and PDFs. Defaults to department administrators.
- ViewInvoicing = 41
+ ViewInvoicing = 41,
+
+ /// Workforce & Business Operations plan Phase D (registry 42): create/edit/delete/status/verify others' certification records and credits, and unit certification records. Defaults to department administrators.
+ ManageCertifications = 42,
+ /// Workforce & Business Operations plan Phase D (registry 43): view others' certification records and the expiry dashboard; own records are always visible. Defaults to department administrators.
+ ViewCertifications = 43,
+ /// Workforce & Business Operations plan Phase D (registry 44): certification types, role requirements and department certification settings. Defaults to department administrators.
+ ManageCertificationSetup = 44
}
}
diff --git a/Core/Resgrid.Model/PersonnelCertification.cs b/Core/Resgrid.Model/PersonnelCertification.cs
index c9009770..933d1fc9 100644
--- a/Core/Resgrid.Model/PersonnelCertification.cs
+++ b/Core/Resgrid.Model/PersonnelCertification.cs
@@ -54,6 +54,32 @@ public class PersonnelCertification : IEntity
///
public bool IsProtected { get; set; }
+ // ---- Phase D typed records (M0213, additive; plan D1.3). Routing metadata only: never cataloged for ADP,
+ // and the D1.7 evaluator reads nothing else off this row.
+
+ /// The catalog type; null for a legacy free-text record.
+ public int? DepartmentCertificationTypeId { get; set; }
+
+ /// .
+ public int Status { get; set; }
+
+ public DateTime? StatusChangedOn { get; set; }
+
+ public string StatusChangedByUserId { get; set; }
+
+ /// Why the status was last changed (suspension / revocation reason). Not a cataloged field.
+ public string StatusReason { get; set; }
+
+ public string VerifiedByUserId { get; set; }
+
+ public DateTime? VerifiedOn { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ [NotMapped]
+ [JsonIgnore]
+ public bool IsTyped => DepartmentCertificationTypeId.HasValue && DepartmentCertificationTypeId.Value > 0;
+
[NotMapped]
[JsonIgnore]
public object IdValue
@@ -72,6 +98,6 @@ public object IdValue
public int IdType => 0;
[NotMapped]
- public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department", "User" };
+ public IEnumerable IgnoredProperties => new string[] { "IdValue", "IdType", "TableName", "IdName", "Department", "User", "IsTyped" };
}
}
diff --git a/Core/Resgrid.Model/Providers/IPaymentConnectProvider.cs b/Core/Resgrid.Model/Providers/IPaymentConnectProvider.cs
new file mode 100644
index 00000000..e229dbc0
--- /dev/null
+++ b/Core/Resgrid.Model/Providers/IPaymentConnectProvider.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Model.Invoicing;
+
+namespace Resgrid.Model.Providers
+{
+ ///
+ /// One payment provider a department can connect its own account to (Workforce & Business Operations plan,
+ /// B2.3). v1 registers Stripe Connect only; Square, PayPal and Authorize.net are v2 adapters behind this same
+ /// seam. Every method takes the department's external account id rather than a stored token, so a provider
+ /// that issues per-merchant tokens decrypts them itself from the connection row it is handed.
+ ///
+ public interface IPaymentConnectProvider
+ {
+ /// value this adapter serves.
+ int Provider { get; }
+
+ /// True when the platform credentials for this adapter are configured in this process.
+ bool IsConfigured { get; }
+
+ /// The provider's authorize URL for the OAuth hand-off; is opaque and single use.
+ string BuildConnectUrl(string state, string redirectUrl);
+
+ /// Exchanges the callback (code) for the account facts. Throws InvalidOperationException("payments_*") on refusal.
+ Task CompleteConnectAsync(IReadOnlyDictionary callbackParameters, CancellationToken cancellationToken = default);
+
+ /// Re-reads the account (charges_enabled, capabilities, business name).
+ Task VerifyConnectionAsync(DepartmentPaymentConnection connection, CancellationToken cancellationToken = default);
+
+ /// Revokes the platform's access to the account. Idempotent; an already-revoked account is not an error.
+ Task DisconnectAsync(DepartmentPaymentConnection connection, CancellationToken cancellationToken = default);
+
+ /// Opens a hosted payment page for one invoice balance as a direct charge on the connected account.
+ Task CreatePaymentRequestAsync(DepartmentPaymentConnection connection, PaymentRequestSpec spec, CancellationToken cancellationToken = default);
+
+ /// Reads a hosted request back (reconciliation), including the settled charge facts when paid.
+ Task GetPaymentRequestStateAsync(DepartmentPaymentConnection connection, string externalReference, CancellationToken cancellationToken = default);
+
+ /// Expires an open hosted request so its link stops working.
+ Task CancelPaymentRequestAsync(DepartmentPaymentConnection connection, string externalReference, CancellationToken cancellationToken = default);
+
+ /// Settled charge facts for a payment intent (the webhook body carries ids only).
+ Task GetChargeFactsAsync(DepartmentPaymentConnection connection, string paymentIntentId, CancellationToken cancellationToken = default);
+
+ ///
+ /// Verifies the webhook signature and parses the body into an envelope. False (with )
+ /// when the signature is missing, stale or wrong; the body is never trusted before this returns true.
+ ///
+ bool TryParseWebhook(string rawBody, string signatureHeader, out PaymentProviderEventEnvelope envelope, out string error);
+ }
+}
diff --git a/Core/Resgrid.Model/ReportTypes.cs b/Core/Resgrid.Model/ReportTypes.cs
index ca0d049c..74ab17aa 100644
--- a/Core/Resgrid.Model/ReportTypes.cs
+++ b/Core/Resgrid.Model/ReportTypes.cs
@@ -15,6 +15,8 @@ public enum ReportTypes
InventoryTransferHistory = 10,
InventoryIssuance = 11,
InventoryValuation = 12,
- ControlledSubstanceLog = 13
+ ControlledSubstanceLog = 13,
+ /// Workforce & Business Operations plan Phase D (registry 14): person × type and unit × type certification compliance.
+ CertificationCompliance = 14
}
}
diff --git a/Core/Resgrid.Model/Repositories/ICertificationRepositories.cs b/Core/Resgrid.Model/Repositories/ICertificationRepositories.cs
new file mode 100644
index 00000000..34660992
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/ICertificationRepositories.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ /// Role → certification-type requirement rows (Workforce & Business Operations plan, Phase D3; M0213).
+ public interface IPersonnelRoleCertificationRequirementRepository : IRepository
+ {
+ Task> GetByRoleIdAsync(int personnelRoleId);
+ Task> GetAllForDepartmentAsync(int departmentId);
+ Task CountByTypeIdAsync(int departmentCertificationTypeId);
+ Task DeleteByRoleIdAsync(int personnelRoleId, CancellationToken cancellationToken = default);
+ /// Departments with at least one requirement row (worker 34 scope).
+ Task> GetDepartmentIdsAsync();
+ }
+
+ /// One settings row per department, keyed by DepartmentId (M0213). Save is an upsert.
+ public interface IDepartmentCertificationSettingsRepository
+ {
+ Task GetAsync(int departmentId);
+ Task SaveAsync(DepartmentCertificationSettings settings, CancellationToken cancellationToken = default);
+ }
+
+ /// Continuing-education credit entries (M0214).
+ public interface IPersonnelCertificationCreditsRepository : IRepository
+ {
+ /// Credits for one record, newest first, without the file bytes.
+ Task> GetByCertificationIdAsync(int personnelCertificationId);
+ Task GetByIdWithDataAsync(int personnelCertificationCreditId);
+ /// Sum of hours per certification id for the ids given.
+ Task> GetHourTotalsAsync(IEnumerable personnelCertificationIds);
+ Task DeleteByCertificationIdAsync(int personnelCertificationId, CancellationToken cancellationToken = default);
+ }
+
+ /// Unit-scoped certification records (plan D1.8; M0213). Reads exclude the file bytes unless asked.
+ public interface IUnitCertificationRepository : IRepository
+ {
+ Task> GetByUnitIdAsync(int unitId);
+ Task> GetForDepartmentAsync(int departmentId);
+ Task GetByIdWithDataAsync(int unitCertificationId);
+ /// Live, non-deleted records with an expiry on or before the date (department-wide), no bytes.
+ Task> GetExpiringAsync(int departmentId, DateTime onOrBefore);
+ Task CountByTypeIdAsync(int departmentCertificationTypeId);
+ /// Departments holding at least one non-deleted unit record (worker 34 scope).
+ Task> GetDepartmentIdsAsync();
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IContactsRepository.cs b/Core/Resgrid.Model/Repositories/IContactsRepository.cs
index 02e65cd1..82293ae6 100644
--- a/Core/Resgrid.Model/Repositories/IContactsRepository.cs
+++ b/Core/Resgrid.Model/Repositories/IContactsRepository.cs
@@ -11,5 +11,7 @@ namespace Resgrid.Model.Repositories
public interface IContactsRepository : IRepository
{
Task> GetContactsByCategoryIdAsync(int departmentId, string categoryId);
+ /// The department's contacts with the given ids in one query (deleted rows included; callers decide).
+ Task> GetContactsByIdsAsync(int departmentId, IEnumerable contactIds);
}
}
diff --git a/Core/Resgrid.Model/Repositories/IDepartmentCertificationTypeRepository.cs b/Core/Resgrid.Model/Repositories/IDepartmentCertificationTypeRepository.cs
index f97d97aa..fcce7a62 100644
--- a/Core/Resgrid.Model/Repositories/IDepartmentCertificationTypeRepository.cs
+++ b/Core/Resgrid.Model/Repositories/IDepartmentCertificationTypeRepository.cs
@@ -1,11 +1,17 @@
-namespace Resgrid.Model.Repositories
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
{
///
- /// Interface IDepartmentCertificationTypeRepository
- /// Implements the
+ /// Department certification-type catalog. Phase D (plan D3) adds the code lookups and the active-by-scope read;
+ /// GetAllByDepartmentIdAsync keeps returning every row including soft-deleted ones, so callers filter IsDeleted.
///
- ///
- public interface IDepartmentCertificationTypeRepository: IRepository
+ public interface IDepartmentCertificationTypeRepository : IRepository
{
+ Task GetByCodeAsync(int departmentId, string code);
+ Task> GetByCodesAsync(int departmentId, IEnumerable codes);
+ /// Active, non-deleted types for the department, optionally narrowed to one scope ().
+ Task> GetActiveForDepartmentAsync(int departmentId, int? appliesTo = null);
}
}
diff --git a/Core/Resgrid.Model/Repositories/IInvoicingRepositories.cs b/Core/Resgrid.Model/Repositories/IInvoicingRepositories.cs
index 890ab553..976ac103 100644
--- a/Core/Resgrid.Model/Repositories/IInvoicingRepositories.cs
+++ b/Core/Resgrid.Model/Repositories/IInvoicingRepositories.cs
@@ -40,6 +40,7 @@ public class InvoiceAgingRow
public string ContactId { get; set; }
public int Status { get; set; }
public DateTime? DueOn { get; set; }
+ public string Currency { get; set; }
public decimal Total { get; set; }
public decimal AmountPaid { get; set; }
public decimal Balance => Total - AmountPaid;
diff --git a/Core/Resgrid.Model/Repositories/IOnlinePaymentRepositories.cs b/Core/Resgrid.Model/Repositories/IOnlinePaymentRepositories.cs
new file mode 100644
index 00000000..bc98e504
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IOnlinePaymentRepositories.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Resgrid.Model.Invoicing;
+
+namespace Resgrid.Model.Repositories
+{
+ /// Department payment-provider connections (plan B2.2, registry M0212).
+ public interface IDepartmentPaymentConnectionRepository : IRepository
+ {
+ Task GetByIdForDepartmentAsync(string departmentPaymentConnectionId, int departmentId);
+ Task> GetForDepartmentAsync(int departmentId);
+ /// The department's default usable connection, else its newest Connected one, else null.
+ Task GetDefaultForDepartmentAsync(int departmentId);
+ /// Live (not deleted) connection for a provider account, in any department; how a webhook finds its department.
+ Task GetByExternalAccountIdAsync(int provider, string externalAccountId);
+ /// Clears IsDefault on every other live connection of the department.
+ Task ClearDefaultAsync(int departmentId, string exceptConnectionId, CancellationToken cancellationToken = default);
+ /// Connected connections not verified since (the worker's re-verification pass).
+ Task> GetStaleVerifiedAsync(DateTime notVerifiedSinceUtc, int take);
+ }
+
+ /// Hosted payment requests (plan B2.2).
+ public interface IInvoicePaymentRequestRepository : IRepository
+ {
+ Task GetByIdForDepartmentAsync(string invoicePaymentRequestId, int departmentId);
+ Task> GetByInvoiceIdAsync(string invoiceId, int departmentId);
+ /// The one non-terminal request for an invoice, or null.
+ Task GetOpenByInvoiceIdAsync(string invoiceId, int departmentId);
+ Task GetByExternalReferenceAsync(int provider, string externalReference);
+ Task GetByPaymentIntentIdAsync(int provider, string paymentIntentId);
+ /// Open requests created before (the worker's reconciliation pass).
+ Task> GetOpenOlderThanAsync(DateTime createdBeforeUtc, int take);
+ /// Open requests whose ExpiresOn is before .
+ Task> GetOpenExpiredAsync(DateTime asOfUtc, int take);
+ /// Open requests of one connection (cancelled when the connection is revoked).
+ Task> GetOpenByConnectionAsync(string departmentPaymentConnectionId);
+ /// Health: open requests created before .
+ Task CountOpenOlderThanAsync(DateTime createdBeforeUtc);
+ /// Health: a request became Completed or Processing since .
+ Task HasActivitySinceAsync(DateTime sinceUtc);
+ }
+
+ /// Received provider events (plan B2.2): idempotency ledger and forensic bodies.
+ public interface IPaymentProviderEventRepository : IRepository
+ {
+ Task GetByExternalEventIdAsync(int provider, string externalEventId);
+ /// Health: newest ReceivedOn, or null.
+ Task GetNewestReceivedOnAsync();
+ /// Health: newest ProcessedOn among Applied events, or null.
+ Task GetNewestAppliedOnAsync();
+ /// Health: events with the outcome received since .
+ Task CountByOutcomeSinceAsync(int outcome, DateTime sinceUtc);
+ /// Worker: deletes events received before ; returns the count.
+ Task PurgeReceivedBeforeAsync(DateTime receivedBeforeUtc, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/Core/Resgrid.Model/Repositories/IPersonnelCertificationRepository.cs b/Core/Resgrid.Model/Repositories/IPersonnelCertificationRepository.cs
index 2f9c39fe..8d1b0101 100644
--- a/Core/Resgrid.Model/Repositories/IPersonnelCertificationRepository.cs
+++ b/Core/Resgrid.Model/Repositories/IPersonnelCertificationRepository.cs
@@ -1,20 +1,29 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace Resgrid.Model.Repositories
{
///
- /// Interface IPersonnelCertificationRepository
- /// Implements the
+ /// Personnel certification records. The Phase D reads (plan D3) return rows without the file bytes and skip
+ /// soft-deleted rows; GetCertificationsByUserAsync is the legacy full read (bytes included, every row).
///
- ///
- public interface IPersonnelCertificationRepository: IRepository
+ public interface IPersonnelCertificationRepository : IRepository
{
- ///
- /// Gets the certifications by user asynchronous.
- ///
- /// The user identifier.
- /// Task<IEnumerable<PersonnelCertification>>.
Task> GetCertificationsByUserAsync(string userId);
+
+ /// Non-deleted records of the department without bytes; optionally narrowed to some users.
+ Task> GetForDepartmentAsync(int departmentId, IEnumerable userIds = null);
+
+ /// Non-deleted typed records of the given types, without bytes.
+ Task> GetByTypeIdsAsync(int departmentId, IEnumerable departmentCertificationTypeIds);
+
+ /// Non-deleted records with an expiry on or before the date, without bytes.
+ Task> GetExpiringAsync(int departmentId, DateTime onOrBefore);
+
+ Task CountByTypeIdAsync(int departmentCertificationTypeId);
+
+ /// Departments holding at least one non-deleted typed record (worker 34 scope).
+ Task> GetDepartmentIdsWithTypedRecordsAsync();
}
}
diff --git a/Core/Resgrid.Model/Services/ICertificationService.cs b/Core/Resgrid.Model/Services/ICertificationService.cs
index 5dcdac49..08c56191 100644
--- a/Core/Resgrid.Model/Services/ICertificationService.cs
+++ b/Core/Resgrid.Model/Services/ICertificationService.cs
@@ -1,88 +1,130 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Resgrid.Model.Services
{
+ ///
+ /// Certification catalog, personnel and unit records, role qualification and the expiry sweep. Phase D of the
+ /// Workforce & Business Operations plan extends this one service (decision 40); the legacy members keep their
+ /// signatures so the Profile/Types/Reports surfaces and the RMS evidence adapters keep working unchanged.
+ /// Every write audits (decision 27); the plan's error codes are thrown as InvalidOperationException("certifications_*").
+ ///
public interface ICertificationService
{
- ///
- /// Gets all certification types by department asynchronous.
- ///
- /// The department identifier.
- /// Task<List<DepartmentCertificationType>>.
- Task> GetAllCertificationTypesByDepartmentAsync(int departmentId);
+ // ---- Legacy surface (kept) --------------------------------------------------------------------------------
- ///
- /// Gets the certification type by identifier asynchronous.
- ///
- /// The certification type identifier.
- /// Task<DepartmentCertificationType>.
+ /// Every type row of the department, soft-deleted ones included (legacy callers filter by hand).
+ Task> GetAllCertificationTypesByDepartmentAsync(int departmentId);
Task GetCertificationTypeByIdAsync(int certificationTypeId);
-
- ///
- /// Deletes the certification type by identifier asynchronous.
- ///
- /// The certification type identifier.
- /// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// Task<System.Boolean>.
+ /// Soft delete (Phase D); refused with certifications_type_in_use while a requirement or a live record references the type.
Task DeleteCertificationTypeByIdAsync(int certificationTypeId, CancellationToken cancellationToken = default(CancellationToken));
-
- ///
- /// Saves the new certification type asynchronous.
- ///
- /// Type of the certification.
- /// The department identifier.
- /// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// Task<DepartmentCertificationType>.
+ /// Creates a person-scoped type from its display name; Code derives from the name.
Task SaveNewCertificationTypeAsync(string certificationType, int departmentId, CancellationToken cancellationToken = default(CancellationToken));
-
- ///
- /// Gets the certifications by user identifier asynchronous.
- ///
- /// The user identifier.
- /// Task<List<PersonnelCertification>>.
+ /// Full legacy read of a member's records (bytes included). Soft-deleted rows are excluded.
Task> GetCertificationsByUserIdAsync(string userId);
-
- ///
- /// Gets the department certification types asynchronous.
- ///
- /// The department identifier.
- /// Task<List<System.String>>.
Task> GetDepartmentCertificationTypesAsync(int departmentId);
-
- ///
- /// Saves the certification asynchronous.
- ///
- /// The certification.
- /// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// Task<PersonnelCertification>.
+ /// Creates or updates a record (ADP catalog 6 seam). New typed records of a RequiresVerification type start PendingVerification; raises trigger 87 on create.
Task SaveCertificationAsync(PersonnelCertification certification, CancellationToken cancellationToken = default(CancellationToken));
-
- ///
- /// Gets the certification by identifier asynchronous.
- ///
- /// The certification identifier.
- /// Task<PersonnelCertification>.
Task GetCertificationByIdAsync(int certificationId);
-
- ///
- /// Deletes the certification.
- ///
- /// The certification.
- /// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// Task<System.Boolean>.
+ /// Hard delete kept for the legacy Profile page and purge paths; Phase D surfaces use .
Task DeleteCertification(PersonnelCertification certification, CancellationToken cancellationToken = default(CancellationToken));
+ Task DeleteAllCertificationsForUser(string userId, CancellationToken cancellationToken = default(CancellationToken));
+ Task DoesCertificationTypeAlreadyExistAsync(int departmentId, string certificationTypeText);
+
+ // ---- Types (plan D4) -------------------------------------------------------------------------------------
+ /// Active, non-deleted types, optionally narrowed to one scope.
+ Task> GetActiveCertificationTypesAsync(int departmentId, CertificationAppliesTo? appliesTo = null);
+ Task GetCertificationTypeByCodeAsync(int departmentId, string code);
///
- /// Deletes all certifications for user.
+ /// Creates or updates a typed catalog row. Code is unique per department and immutable once a requirement references it;
+ /// AppliesTo is immutable once records of the type exist; unit-scoped types cannot carry credits or verification.
///
- /// The user identifier.
- /// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// Task<System.Boolean>.
- Task DeleteAllCertificationsForUser(string userId, CancellationToken cancellationToken = default(CancellationToken));
+ Task SaveCertificationTypeAsync(DepartmentCertificationType type, string userId, CancellationToken cancellationToken = default);
+ /// Projects a template into a new department type.
+ Task CreateCertificationTypeFromTemplateAsync(int departmentId, string templateId, string userId, CancellationToken cancellationToken = default);
- Task DoesCertificationTypeAlreadyExistAsync(int departmentId, string certificationTypeText);
+ // ---- Personnel records (plan D4) ---------------------------------------------------------------------------
+
+ /// Non-deleted records of the department without file bytes; optionally narrowed to some members.
+ Task> GetCertificationsForDepartmentAsync(int departmentId, IEnumerable userIds = null);
+ /// Suspend / revoke / reinstate / mark trainee with a reason: audits Before/After and raises trigger 91.
+ Task SetCertificationStatusAsync(int certificationId, int departmentId, PersonnelCertificationStatuses status, string reason, string userId, CancellationToken cancellationToken = default);
+ /// Supervisor sign-off: PendingVerification → Active, stamps the verifier; raises trigger 91.
+ Task VerifyCertificationAsync(int certificationId, int departmentId, string userId, CancellationToken cancellationToken = default);
+ /// Extends the expiry (and optionally the number) of a record, reactivating an expired one; raises trigger 88.
+ Task RenewCertificationAsync(int certificationId, int departmentId, DateTime? newExpiresOn, string newNumber, string userId, CancellationToken cancellationToken = default);
+ /// Soft delete with audit; credits stay for history.
+ Task SoftDeleteCertificationAsync(int certificationId, int departmentId, string userId, CancellationToken cancellationToken = default);
+
+ // ---- Credits (plan D1.4) ----------------------------------------------------------------------------------
+
+ Task> GetCertificationCreditsAsync(int certificationId);
+ Task GetCertificationCreditByIdAsync(int creditId, bool includeData = false);
+ Task AddCertificationCreditAsync(PersonnelCertificationCredit credit, string userId, CancellationToken cancellationToken = default);
+ Task DeleteCertificationCreditAsync(int creditId, int departmentId, string userId, CancellationToken cancellationToken = default);
+ Task> GetCertificationCreditTotalsAsync(IEnumerable certificationIds);
+
+ // ---- Unit records (plan D1.8) --------------------------------------------------------------------------------
+
+ Task> GetUnitCertificationsAsync(int unitId);
+ Task> GetUnitCertificationsForDepartmentAsync(int departmentId);
+ Task GetUnitCertificationByIdAsync(int unitCertificationId, bool includeData = false);
+ /// Creates or updates a unit record; the type must be unit-scoped (certifications_type_scope).
+ Task SaveUnitCertificationAsync(UnitCertification certification, string userId, CancellationToken cancellationToken = default);
+ Task SetUnitCertificationStatusAsync(int unitCertificationId, int departmentId, UnitCertificationStatuses status, string reason, string userId, CancellationToken cancellationToken = default);
+ Task DeleteUnitCertificationAsync(int unitCertificationId, int departmentId, string userId, CancellationToken cancellationToken = default);
+
+ // ---- Role requirements and settings (plan D1.5 / D1.6) ------------------------------------------------------------
+
+ Task> GetRoleRequirementsAsync(int roleId);
+ Task> GetAllRoleRequirementsAsync(int departmentId);
+ /// Replaces a role's requirement set; every type must be person-scoped and belong to the department.
+ Task> SaveRoleRequirementsAsync(int departmentId, int roleId, List requirements, string userId, CancellationToken cancellationToken = default);
+ /// The department's settings, or the defaults (EnforcementMode Off) when it never saved any.
+ Task GetCertificationSettingsAsync(int departmentId);
+ Task SaveCertificationSettingsAsync(DepartmentCertificationSettings settings, string userId, CancellationToken cancellationToken = default);
+
+ // ---- Evaluation (plan D1.7 / D4) ------------------------------------------------------------------------------
+
+ /// Every current member of the role, evaluated with the D1.7 rule.
+ Task> EvaluateRoleRequirementsAsync(int departmentId, int roleId, DateTime? onDate = null);
+ /// One member against one role (used before a role add).
+ Task EvaluateUserForRoleAsync(int departmentId, int roleId, string userId, DateTime? onDate = null);
+ /// Members qualified for the role today (all mandatory requirements satisfied), members with no requirements included.
+ Task> GetQualifiedPersonnelForRoleAsync(int departmentId, int roleId);
+ /// Members holding a valid record of every (or any) listed type code.
+ Task> GetUsersWithValidCertificationAsync(int departmentId, IEnumerable typeCodes, bool allOf = true);
+ /// Person × type and unit × type matrices with expiry colouring inputs (plan D9).
+ Task GetExpiryDashboardAsync(int departmentId, DateTime? onDate = null);
+
+ // ---- Worker (plan D5) --------------------------------------------------------------------------------------------
+
+ /// Departments the sweep has anything to do for: typed personnel records, unit records or role requirements.
+ Task> GetDepartmentsForSweepAsync();
+ /// Runs the expire, expiring, unit, enforcement and digest passes for one department for its local date.
+ Task RunExpirySweepAsync(int departmentId, DateTime localToday, CancellationToken cancellationToken = default);
+ }
+}
+
+namespace Resgrid.Model
+{
+ /// Counts from one department's nightly sweep (plan D5).
+ public sealed class CertificationSweepResult
+ {
+ public int DepartmentId { get; set; }
+ public int Expired { get; set; }
+ public int ExpiringNotified { get; set; }
+ public int UnitsExpired { get; set; }
+ public int UnitsExpiringNotified { get; set; }
+ public int InGrace { get; set; }
+ public int Removed { get; set; }
+ public bool DigestSent { get; set; }
+ public List ExpiredNames { get; set; } = new List();
+ public List ExpiringNames { get; set; } = new List();
+ public List InGraceNames { get; set; } = new List();
+ public List RemovedNames { get; set; } = new List();
}
}
diff --git a/Core/Resgrid.Model/Services/IContactsService.cs b/Core/Resgrid.Model/Services/IContactsService.cs
index a02b2102..04ab8763 100644
--- a/Core/Resgrid.Model/Services/IContactsService.cs
+++ b/Core/Resgrid.Model/Services/IContactsService.cs
@@ -18,6 +18,8 @@ public interface IContactsService
Task GetContactCategoryByIdAsync(string contactCategoryId);
Task DeleteContactCategoryAsync(ContactCategory contactCategory, CancellationToken cancellationToken = default(CancellationToken));
Task GetContactByIdAsync(string contactId);
+ /// The department's contacts for the given ids in one query, keyed by id (case-insensitive); deleted contacts are included so historical references still resolve.
+ Task> GetContactsByIdsAsync(int departmentId, IEnumerable contactIds);
Task> GetContactNotesByContactIdAsync(string contactId, int departmentId, bool getDeleted = false);
Task> GetContactNoteTypesByDepartmentIdAsync(int departmentId);
Task SaveContactNoteTypeAsync(ContactNoteType type, CancellationToken cancellationToken = default(CancellationToken));
diff --git a/Core/Resgrid.Model/Services/IInvoicePaymentsService.cs b/Core/Resgrid.Model/Services/IInvoicePaymentsService.cs
index 21018ded..30e35619 100644
--- a/Core/Resgrid.Model/Services/IInvoicePaymentsService.cs
+++ b/Core/Resgrid.Model/Services/IInvoicePaymentsService.cs
@@ -1,19 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
using Resgrid.Model.Invoicing;
namespace Resgrid.Model.Services
{
///
- /// Online payment collection on Resgrid invoices through a department's own Stripe account (Workforce &
- /// Business Operations plan, Phase B2). Scaffolded 2026-09-18 with the health read only; the connect, pay-link,
- /// webhook-apply and reconciliation members arrive with migration M0212 and the Phase B invoicing tables.
+ /// Online payment collection on Resgrid invoices through a department's own provider account (Workforce &
+ /// Business Operations plan, Phase B2, B2.4). Resgrid never holds funds: every request is a direct charge on the
+ /// connected account, and the verified webhook is the only thing that records money. Refusals throw
+ /// InvalidOperationException("payments_*") with a resource-key code, the invoicing convention.
///
public interface IInvoicePaymentsService
{
+ /// PaymentConnectConfig.Enabled and the Payments.StripeConnect operator flag both hold in this cluster (global state only).
+ Task IsAvailableInClusterAsync();
+
+ /// The department's posture: cluster switch, department flag, connection, allowed methods, and the first failing gate.
+ Task GetStatusAsync(int departmentId);
+
+ // ---- Connect (department administrators only; the caller enforces the role) ----------------------------
+
+ /// Signed, single-use, 15-minute state plus the provider's authorize URL. Refuses while the cluster switch is off.
+ Task BeginConnectAsync(int departmentId, int provider, string userId, string ipAddress, string userAgent);
+
+ /// Validates the state, exchanges the code, reads the account, upserts the connection (default when first), audits and e-mails the department administrators.
+ Task CompleteConnectAsync(int departmentId, int provider, IReadOnlyDictionary callbackParameters, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
+
+ /// Deauthorizes at the provider and marks the connection Disconnected; open requests are cancelled.
+ Task DisconnectAsync(string departmentPaymentConnectionId, int departmentId, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
+
+ Task> GetConnectionsAsync(int departmentId);
+
+ // ---- Pay links --------------------------------------------------------------------------------------------
+
///
- /// Health of the Stripe Connect webhook path for the v4 Health endpoint (plan B2.5a). Never throws; every
- /// field falls back to its safe value, and a cluster with payment collection off reports healthy.
+ /// Reuses the invoice's open request while unexpired, else opens a hosted request for the full remaining
+ /// balance. Refuses Draft/Void/Paid invoices, a zero balance, a lapsed entitlement, either flag off, a missing
+ /// or not-ready connection, or a currency the account does not settle.
///
+ Task CreatePaymentRequestAsync(string invoiceId, int departmentId, int source, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
+
+ Task GetOpenRequestAsync(string invoiceId, int departmentId);
+ Task> GetRequestsAsync(string invoiceId, int departmentId);
+
+ /// {PublicBaseUrl}/pay/{token} for the invoice, valid for the department's pay-link expiry; null when online payment is not offered right now.
+ Task BuildPayPageUrlAsync(string invoiceId, int departmentId);
+
+ /// The pay-page token itself (p|department|invoice|expiry, encrypted, URL safe).
+ string CreatePayPageToken(int departmentId, string invoiceId, DateTime expiresOnUtc);
+
+ /// What the anonymous pay page shows; Available is false with a reason for an expired or foreign token.
+ Task GetPayPageModelAsync(string token);
+
+ // ---- Truth ------------------------------------------------------------------------------------------------
+
+ /// Verifies, records and applies one webhook body; never throws. Accepted answers 2xx, otherwise 400.
+ Task ReceiveWebhookAsync(int provider, string signatureHeader, string rawBody, string ipAddress, CancellationToken cancellationToken = default);
+
+ /// Applies a parsed envelope idempotently (event id). The reconciliation pass and the webhook share this path.
+ Task ApplyProviderEventAsync(PaymentProviderEventEnvelope envelope, string rawBody, CancellationToken cancellationToken = default);
+
+ // ---- Worker 29 passes 2–4 ---------------------------------------------------------------------------------
+
+ /// Reads back every open request older than RequestReconcileAfterMinutes and applies its state (a lost webhook lands within one cycle). Returns the number applied.
+ Task ReconcileOpenRequestsAsync(DateTime asOfUtc, CancellationToken cancellationToken = default);
+
+ /// Open requests past ExpiresOn become Expired. Returns the count.
+ Task ExpireStaleRequestsAsync(DateTime asOfUtc, CancellationToken cancellationToken = default);
+
+ /// Re-reads connections not verified in a day; a revoked or charges-disabled account is downgraded. Returns the count checked.
+ Task ReverifyConnectionsAsync(DateTime asOfUtc, CancellationToken cancellationToken = default);
+
+ /// Deletes event bodies older than EventRetentionDays. Returns the count.
+ Task PurgeEventsAsync(DateTime asOfUtc, CancellationToken cancellationToken = default);
+
+ // ---- Health -----------------------------------------------------------------------------------------------
+
+ /// Health of the webhook path for the v4 Health endpoint (plan B2.5a). Never throws; a cluster with payment collection off reports healthy.
Task GetWebhookHealthAsync();
}
}
diff --git a/Core/Resgrid.Model/Services/IInvoicingService.cs b/Core/Resgrid.Model/Services/IInvoicingService.cs
index 77e66d83..730e7306 100644
--- a/Core/Resgrid.Model/Services/IInvoicingService.cs
+++ b/Core/Resgrid.Model/Services/IInvoicingService.cs
@@ -12,7 +12,10 @@ public class InvoiceAgingBucket
{
public string Label { get; set; }
public int Count { get; set; }
+ /// Sum of every balance in the bucket regardless of currency; meaningful only when has one entry.
public decimal Balance { get; set; }
+ /// The bucket's balance per currency code (invoices may be issued in any supported currency), ordered by code.
+ public SortedDictionary BalancesByCurrency { get; set; } = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
public List Invoices { get; set; } = new List();
}
@@ -21,8 +24,18 @@ public class InvoiceAgingReport
{
public DateTime AsOfUtc { get; set; }
public List Buckets { get; set; } = new List();
+ /// Sum of every open balance regardless of currency; meaningful only when has one entry.
public decimal TotalBalance { get; set; }
+ /// Open balance per currency code, ordered by code.
+ public SortedDictionary BalancesByCurrency { get; set; } = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
public int TotalCount { get; set; }
+
+ /// Adds to the entry (a missing code files under an empty key).
+ public static void Accumulate(SortedDictionary balances, string currency, decimal amount)
+ {
+ var key = string.IsNullOrWhiteSpace(currency) ? string.Empty : currency.Trim().ToUpperInvariant();
+ balances[key] = (balances.TryGetValue(key, out var current) ? current : 0m) + amount;
+ }
}
/// The department's Phase B invoicing surface (Workforce & Business Operations plan, B4). Every mutation audits; lifecycle transitions publish their Workflow trigger through the domain outbox.
@@ -57,8 +70,10 @@ public interface IInvoicingService
Task CreateDraftInvoiceAsync(int departmentId, string contactId, string userId, string ipAddress, string userAgent, string currency = null, CancellationToken cancellationToken = default);
/// Draft-only edit of header fields (notes, terms, discount, due date, currency); totals are recomputed.
Task SaveInvoiceAsync(Invoice invoice, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
- /// Replaces a draft's line items with the supplied list and recomputes totals.
+ /// Replaces a draft's line items with the supplied list (re-applying each rate card item's minimum charge) and recomputes totals, in one transaction.
Task SaveInvoiceLineItemsAsync(string invoiceId, int departmentId, List lineItems, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
+ /// Header fields and line items of a draft saved atomically: then in one transaction.
+ Task SaveDraftAsync(Invoice invoice, List lineItems, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
/// Editable draft lines for a call from a rate card: time on scene from unit states (plan decision 9), flat and fixed items. Nothing is saved.
Task> GenerateLineItemsFromCallAsync(int callId, string rateCardId, int departmentId);
/// Appends the generated lines for a call to a draft invoice and recomputes totals.
@@ -72,6 +87,8 @@ public interface IInvoicingService
Task RecordPaymentAsync(InvoicePayment payment, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
/// Applies a refund (or lost dispute) to a recorded payment and re-derives the invoice's status and balance (Phase B2).
Task ApplyPaymentRefundAsync(string invoicePaymentId, int departmentId, decimal refundedAmount, bool disputeLost, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
+ /// Card dispute lifecycle on an online payment (Phase B2): Opened flags the payment and publishes InvoicePaymentDisputed once; Won restores Succeeded; Lost zeroes the payment's effect (a refund in all but name) and re-derives the invoice.
+ Task ApplyPaymentDisputeAsync(string invoicePaymentId, int departmentId, InvoiceDisputeStages stage, string userId, string ipAddress, string userAgent, CancellationToken cancellationToken = default);
/// Sent / PartiallyPaid invoices past DueOn become Overdue (the invoice maintenance worker's pass). Returns the number transitioned. lets the worker skip departments whose entitlement lapsed.
Task MarkOverdueInvoicesAsync(DateTime asOfUtc, Func> departmentEnabled = null, CancellationToken cancellationToken = default);
Task GetAccountsReceivableAgingAsync(int departmentId, DateTime? asOfUtc = null);
diff --git a/Core/Resgrid.Model/Services/IPersonnelRolesService.cs b/Core/Resgrid.Model/Services/IPersonnelRolesService.cs
index 6bf24758..9201944e 100644
--- a/Core/Resgrid.Model/Services/IPersonnelRolesService.cs
+++ b/Core/Resgrid.Model/Services/IPersonnelRolesService.cs
@@ -40,7 +40,7 @@ public interface IPersonnelRolesService
/// The role.
/// The cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// Task<PersonnelRole>.
- Task SaveRoleAsync(PersonnelRole role, CancellationToken cancellationToken = default(CancellationToken));
+ Task SaveRoleAsync(PersonnelRole role, CancellationToken cancellationToken = default(CancellationToken), string actingUserId = null);
///