From af137ca09453ccbf4eb89e06cf7a109033f516b1 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Fri, 14 Aug 2026 17:19:06 -0700 Subject: [PATCH 1/3] RG-T131 Greek Support, Cache Bug fixes, Centerpoint Fix, Alerts --- .gitignore | 1 + Core/Resgrid.Config/MappingConfig.cs | 2 + .../Account/Login.el.resx | 264 ++++ .../Areas/User/Account/DeleteAccount.el.resx | 144 +++ .../User/Account/ForcePasswordChange.el.resx | 45 + .../Areas/User/Calendar/Calendar.el.resx | 414 +++++++ .../CommunicationTest.el.resx | 208 ++++ .../Areas/User/Contacts/Contacts.el.resx | 507 ++++++++ .../Areas/User/CustomMaps/CustomMaps.el.resx | 374 ++++++ .../CustomStatuses/CustomStatuses.el.resx | 321 +++++ .../Areas/User/Department/Department.ar.resx | 108 ++ .../Areas/User/Department/Department.de.resx | 108 ++ .../Areas/User/Department/Department.el.resx | 1083 +++++++++++++++++ .../Areas/User/Department/Department.en.resx | 108 ++ .../Areas/User/Department/Department.es.resx | 108 ++ .../Areas/User/Department/Department.fr.resx | 108 ++ .../Areas/User/Department/Department.it.resx | 108 ++ .../Areas/User/Department/Department.pl.resx | 108 ++ .../Areas/User/Department/Department.resx | 108 ++ .../Areas/User/Department/Department.sv.resx | 108 ++ .../Areas/User/Department/Department.uk.resx | 108 ++ .../User/Department/DepartmentTypes.el.resx | 312 +++++ .../Areas/User/Dispatch/Call.el.resx | 751 ++++++++++++ .../Areas/User/Dispatch/Dashboard.el.resx | 144 +++ .../Areas/User/Documents/Documents.el.resx | 165 +++ .../Areas/User/Forms/Forms.el.resx | 198 +++ .../Areas/User/Groups/Groups.el.resx | 287 +++++ .../Areas/User/Home/EditProfile.el.resx | 456 +++++++ .../Areas/User/Home/HomeDashboard.el.resx | 174 +++ .../Areas/User/IndoorMaps/IndoorMaps.el.resx | 259 ++++ .../Areas/User/Inventory/Inventory.el.resx | 220 ++++ .../Areas/User/Links/Links.el.resx | 166 +++ .../Areas/User/Logs/Logs.el.resx | 357 ++++++ .../Areas/User/Mapping/Mapping.el.resx | 287 +++++ .../Areas/User/Messages/Messages.el.resx | 240 ++++ .../Areas/User/Moderation/Moderation.el.resx | 156 +++ .../Areas/User/Notes/Note.el.resx | 171 +++ .../User/Notifications/Notifications.el.resx | 124 ++ .../Areas/User/Orders/Orders.el.resx | 388 ++++++ .../Areas/User/Personnel/Person.el.resx | 399 ++++++ .../Areas/User/Profile/Profile.el.resx | 303 +++++ .../Areas/User/Protocols/Protocols.el.resx | 253 ++++ .../Areas/User/Reports/FlaggedReport.el.resx | 162 +++ .../Areas/User/Reports/Reports.el.resx | 743 +++++++++++ .../Areas/User/Routes/Routes.el.resx | 527 ++++++++ .../Areas/User/Security/Security.el.resx | 383 ++++++ .../Areas/User/Shifts/Shifts.el.resx | 636 ++++++++++ .../User/Subscription/Subscription.el.resx | 471 +++++++ .../Areas/User/Templates/Templates.el.resx | 174 +++ .../Areas/User/Trainings/Trainings.el.resx | 390 ++++++ .../Areas/User/TwoFactor/TwoFactor.el.resx | 77 ++ .../Areas/User/Units/Units.el.resx | 234 ++++ .../UserDefinedFields.el.resx | 295 +++++ .../Areas/User/Voice/Voice.el.resx | 189 +++ .../User/WeatherAlerts/WeatherAlerts.el.resx | 463 +++++++ .../Areas/User/Workflows/Workflows.el.resx | 618 ++++++++++ Core/Resgrid.Localization/Common.el.resx | 687 +++++++++++ Core/Resgrid.Localization/SupportedLocales.cs | 1 + Core/Resgrid.Model/DepartmentSettingTypes.cs | 12 + .../Helpers/NewCallFieldPolicyValidator.cs | 102 ++ Core/Resgrid.Model/Helpers/PoiIconHelper.cs | 88 ++ Core/Resgrid.Model/NewCallFieldPolicy.cs | 140 +++ Core/Resgrid.Model/Resgrid.Model.csproj | 2 +- .../Services/IDepartmentSettingsService.cs | 46 +- Core/Resgrid.Model/UnitStatusThresholds.cs | 90 ++ Core/Resgrid.Model/VisibilityPayloadUnits.cs | 9 + Core/Resgrid.Model/VisibilityPayloadUsers.cs | 9 + Core/Resgrid.Services/AuthorizationService.cs | 70 +- .../DepartmentGroupsService.cs | 32 +- .../DepartmentSettingsService.cs | 175 +++ Core/Resgrid.Services/DepartmentsService.cs | 18 + .../Resgrid.Services/PersonnelRolesService.cs | 34 +- Core/Resgrid.Services/Resgrid.Services.csproj | 2 +- Core/Resgrid.Services/UnitsService.cs | 24 + Directory.Build.targets | 27 + .../Resgrid.Providers.MigrationsPg.csproj | 2 +- .../Resgrid.Providers.Workflow.csproj | 4 +- ...esgrid.Repositories.NoSqlRepository.csproj | 2 +- .../Models/NewCallFieldPolicyTests.cs | 186 +++ .../Models/PoiIconHelperTests.cs | 58 + .../Models/UnitStatusThresholdsTests.cs | 100 ++ Tests/Resgrid.Tests/Resgrid.Tests.csproj | 2 +- .../Services/AuthorizationServiceTests.cs | 7 +- .../Services/CalendarServiceCheckInTests.cs | 5 +- ...DepartmentSettingsServiceMapCenterTests.cs | 150 +++ Web/Resgrid.Web.Eventing/Dockerfile | 4 + .../Resgrid.Web.Eventing.csproj | 2 +- Web/Resgrid.Web.Mcp/Dockerfile | 4 + Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj | 2 - .../Controllers/v4/CallsController.cs | 65 + .../Controllers/v4/ChatController.cs | 39 +- .../Controllers/v4/ConfigController.cs | 88 +- .../Controllers/v4/GeocodingController.cs | 62 + .../Controllers/v4/MappingController.cs | 8 +- .../Controllers/v4/StatusesController.cs | 6 +- .../Controllers/v4/UnitsController.cs | 8 +- Web/Resgrid.Web.Services/Dockerfile | 4 + .../Helpers/UtcDateTimeConverter.cs | 40 + .../v4/Calendar/GetAllCalendarItemResult.cs | 5 + .../Models/v4/CallNotes/CallNotesResult.cs | 4 + .../v4/CallVideoFeeds/CallVideoFeedsResult.cs | 6 +- .../Models/v4/Calls/CallHistoryResult.cs | 4 + .../Models/v4/Calls/CallResult.cs | 18 + .../v4/Calls/NewCallFieldPolicyResult.cs | 40 + .../Models/v4/Configs/GetConfigResult.cs | 36 + .../v4/Contacts/ContactCategoryResult.cs | 5 + .../Models/v4/Contacts/ContactNotesResult.cs | 7 +- .../Models/v4/Contacts/ContactResult.cs | 5 + .../Models/v4/Geocoding/GeocodingResults.cs | 18 + .../Models/v4/Messages/GetMessagesResult.cs | 4 + .../GetCurrentStaffingResult.cs | 4 + .../GetCurrentStatusResult.cs | 4 + .../Models/v4/Statuses/StatusResult.cs | 9 +- .../Models/v4/UnitStatus/UnitStatusResult.cs | 4 + .../Models/v4/Units/UnitsInfoResult.cs | 19 +- .../Resgrid.Web.Services.csproj | 4 +- .../Resgrid.Web.Services.xml | 180 ++- Web/Resgrid.Web.Tts/Dockerfile | 3 + .../User/Controllers/DepartmentController.cs | 105 +- .../User/Controllers/DispatchController.cs | 41 +- .../User/Controllers/DocumentsController.cs | 1 - .../User/Models/DepartmentSettingsModel.cs | 36 + .../User/Views/Department/Settings.cshtml | 69 ++ .../Areas/User/Views/Shared/_TopNavbar.cshtml | 3 + Web/Resgrid.Web/Dockerfile | 4 + Web/Resgrid.Web/Resgrid.Web.csproj | 4 +- Web/Resgrid.Web/Views/Account/LogOn.cshtml | 6 +- .../resgrid.dispatch.addArchivedCall.js | 9 +- .../dispatch/resgrid.dispatch.newcall.js | 9 +- .../wwwroot/js/app/internal/resgrid.user.js | 3 +- Workers/Resgrid.TrackerGateway/Dockerfile | 4 + Workers/Resgrid.Workers.Console/Dockerfile | 4 + .../Logic/SecurityLogic.cs | 23 +- .../Quidjibo.Postgres.csproj | 2 +- .../Quidjibo.SqlServer.csproj | 2 +- 135 files changed, 18663 insertions(+), 72 deletions(-) create mode 100644 Core/Resgrid.Localization/Account/Login.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Account/DeleteAccount.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Account/ForcePasswordChange.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Calendar/Calendar.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/CommunicationTest/CommunicationTest.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/CustomStatuses/CustomStatuses.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Department/Department.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Department/DepartmentTypes.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Dispatch/Call.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Dispatch/Dashboard.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Documents/Documents.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Forms/Forms.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Groups/Groups.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Home/EditProfile.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Home/HomeDashboard.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Inventory/Inventory.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Links/Links.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Logs/Logs.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Mapping/Mapping.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Messages/Messages.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Moderation/Moderation.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Notes/Note.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Notifications/Notifications.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Orders/Orders.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Personnel/Person.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Profile/Profile.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Protocols/Protocols.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Reports/FlaggedReport.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Reports/Reports.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Routes/Routes.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Security/Security.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Shifts/Shifts.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Subscription/Subscription.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Templates/Templates.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Trainings/Trainings.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/TwoFactor/TwoFactor.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Units/Units.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/UserDefinedFields/UserDefinedFields.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Voice/Voice.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.el.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Workflows/Workflows.el.resx create mode 100644 Core/Resgrid.Localization/Common.el.resx create mode 100644 Core/Resgrid.Model/Helpers/NewCallFieldPolicyValidator.cs create mode 100644 Core/Resgrid.Model/Helpers/PoiIconHelper.cs create mode 100644 Core/Resgrid.Model/NewCallFieldPolicy.cs create mode 100644 Core/Resgrid.Model/UnitStatusThresholds.cs create mode 100644 Directory.Build.targets create mode 100644 Tests/Resgrid.Tests/Models/NewCallFieldPolicyTests.cs create mode 100644 Tests/Resgrid.Tests/Models/PoiIconHelperTests.cs create mode 100644 Tests/Resgrid.Tests/Models/UnitStatusThresholdsTests.cs create mode 100644 Tests/Resgrid.Tests/Services/DepartmentSettingsServiceMapCenterTests.cs create mode 100644 Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs create mode 100644 Web/Resgrid.Web.Services/Models/v4/Calls/NewCallFieldPolicyResult.cs diff --git a/.gitignore b/.gitignore index bd5cf3810..0c83e6a66 100644 --- a/.gitignore +++ b/.gitignore @@ -279,3 +279,4 @@ opencode.json /.codex /.claude /Web/Resgrid.Web/wwwroot/js/ng +/tmp diff --git a/Core/Resgrid.Config/MappingConfig.cs b/Core/Resgrid.Config/MappingConfig.cs index d33f0bfef..f4e8dcc61 100644 --- a/Core/Resgrid.Config/MappingConfig.cs +++ b/Core/Resgrid.Config/MappingConfig.cs @@ -28,6 +28,7 @@ public static class MappingConfig ***********************************/ public static string UnitAppGoogleMapsKey = ""; public static string ResponderAppGoogleMapsKey = ""; + public static string DispatchAppGoogleMapsKey = ""; /*********************************** * OpenWeatherApi Keys @@ -45,6 +46,7 @@ public static class MappingConfig ***********************************/ public static string UnitAppWhat3WordsKey = ""; public static string ResponderAppWhat3WordsKey = ""; + public static string DispatchAppWhat3WordsKey = ""; /*********************************** * Leaflet OSM Keys (used for Mapping and Map display) diff --git a/Core/Resgrid.Localization/Account/Login.el.resx b/Core/Resgrid.Localization/Account/Login.el.resx new file mode 100644 index 000000000..f1f6b8b06 --- /dev/null +++ b/Core/Resgrid.Localization/Account/Login.el.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ολοκληρωμένη Πρόσκληση + + + Ολοκλήρωση Πρόσκλησης + + + Συμπληρώστε τις πληροφορίες για να δημιουργήσετε τον δικό σας λογαριασμό Resgrid και να συνδεθείτε με + + + Οι πληροφορίες που παρέχετε χρησιμοποιούνται για τη δημιουργία του λογαριασμού σας στο Resgrid. Αν έχετε ήδη λογαριασμό, ο υπάρχων λογαριασμός σας θα συνδεθεί με το τμήμα ως μέρος αυτής της πρόσκλησης. + + + Αν αντιμετωπίζετε δυσκολίες στην ολοκλήρωση της πρόσκλησης ή προβλήματα, επικοινωνήστε με τον Διαχειριστή του τμήματος της πρόσκλησης που προσπαθείτε να ολοκληρώσετε για βοήθεια. Μπορεί να προσθέσει τον λογαριασμό σας χειροκίνητα αν χρειαστεί. + + + Επιβεβαίωση Κωδικού Πρόσβασης + + + Δημιουργία λογαριασμού + + + Όνομα Τμήματος + + + Δεν έχετε λογαριασμό; + + + Διεύθυνση Email + + + Όνομα + + + Ξεχάσατε το όνομα χρήστη ή τον κωδικό πρόσβασής σας; + + + Επιστροφή + + + Αρχική + + + Φαίνεται ότι αυτή η πρόσκληση έχει ήδη ολοκληρωθεί. Αν πρόκειται για σφάλμα, επικοινωνήστε με το τμήμα ή τον οργανισμό σας για νέα πρόσκληση, διαφορετικά μπορείτε να συνδεθείτε με το όνομα χρήστη και τον κωδικό πρόσβασής σας. + + + Η πρόσκληση Resgrid έχει ήδη ολοκληρωθεί + + + Επώνυμο + + + Κλειδωμένος λογαριασμός + + + Αυτός ο λογαριασμός έχει κλειδωθεί· δοκιμάστε να συνδεθείτε ξανά σε 30 λεπτά. Σημειώστε ότι η υποστήριξη δεν μπορεί να ξεκλειδώσει έναν λογαριασμό για εσάς. + + + Σύνδεση + + + Εισαγάγετε το όνομα χρήστη και τον κωδικό πρόσβασής σας για να συνδεθείτε. + + + Αν το τμήμα σας έχει ήδη εγγραφεί, ζητήστε από τον διαχειριστή του τμήματος μια πρόσκληση. + + + Αν το τμήμα σας δεν έχει εγγραφεί ακόμη στο Resgrid, μπορείτε να δημιουργήσετε νέο λογαριασμό, ο οποίος θα δημιουργήσει το τμήμα για εσάς. + + + Η συντήρηση πραγματοποιείται εβδομαδιαία, το Σάββατο, με έναρξη στις 2000 ώρα Ειρηνικού. + + + Σύνδεση + + + Λυπούμαστε, αλλά δεν μπορέσαμε να εντοπίσουμε την πρόσκλησή σας στο Resgrid. Ζητήστε από το τμήμα ή τον οργανισμό σας να στείλει άλλη. Αν έχετε ήδη λογαριασμό Resgrid, μπορείτε να συνδεθείτε παρακάτω. + + + Λείπει η Πρόσκληση + + + Δεν ήταν δυνατή η εύρεση της πρόσκλησής σας στο Resgrid + + + Ειδοποίηση + + + Κωδικός Πρόσβασης + + + Οι κωδικοί πρόσβασης πρέπει να έχουν 8 χαρακτήρες ή περισσότερους και να περιλαμβάνουν ένα ψηφίο (αριθμό), ένα κεφαλαίο και ένα πεζό γράμμα + + + πολιτική απορρήτου + + + Αν η διεύθυνση email σας είναι έγκυρη, έχουμε επαναφέρει τον κωδικό πρόσβασής σας και στείλαμε το όνομα χρήστη και τον νέο κωδικό πρόσβασης στη διεύθυνση email που δώσατε. Η παράδοση του email μπορεί να καθυστερήσει έως και 15 λεπτά· <b>ελέγξτε επίσης τους φακέλους ανεπιθύμητης αλληλογραφίας στο πρόγραμμα email σας αν το μήνυμα δεν φτάσει</b> μέχρι τότε. + + + Δώστε τη διεύθυνση email που χρησιμοποιήσατε με τον λογαριασμό σας Resgrid. Θα σας στείλουμε στη συνέχεια email σε αυτή τη διεύθυνση με το όνομα χρήστη και τον νέο κωδικό πρόσβασής σας για να συνδεθείτε στο σύστημα. + + + Ανάκτηση Λογαριασμού + + + Εγγραφή + + + Η εγγραφή στο Resgrid είναι τόσο εύκολη όσο το 1, 2, 3! + + + Ένα άτομο από τον οργανισμό σας συμπληρώνει αυτή τη φόρμα + + + Μόλις εγγραφείτε, μπορείτε να προσκαλέσετε ή να προσθέσετε άτομα από τη σελίδα Προσωπικού εντός της εφαρμογής + + + Αυτό είναι όλο! Μόνο ένα άτομο χρειάζεται να εγγράψει τον οργανισμό + + + Αν κάποιος στον οργανισμό σας <b>έχει ήδη λογαριασμό στο Resgrid</b>, ζητήστε του να σας προσκαλέσει στο τμήμα του αντί να δημιουργήσετε νέο, αν θέλετε να χρησιμοποιείτε το Resgrid μαζί. + + + Εγγραφή + + + Με την εγγραφή σας, αποδέχεστε + + + τους όρους χρήσης + + + Όνομα Χρήστη + + + Κωδικός Έκπτωσης + + + Κωδικός Συνεργάτη + + + Περιοχή + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Account/DeleteAccount.el.resx b/Core/Resgrid.Localization/Areas/User/Account/DeleteAccount.el.resx new file mode 100644 index 000000000..9dfec29da --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Account/DeleteAccount.el.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Επιλέξτε το παρακάτω πλαίσιο για να επιβεβαιώσετε ότι κατανοείτε τα παραπάνω και επιθυμείτε να διαγράψετε τον λογαριασμό σας. + + + Επιλέξτε το Πλαίσιο για Επιβεβαίωση + + + Διαγραφή του Λογαριασμού Σας + + + Εδώ μπορείτε να διαγράψετε τον προσωπικό σας λογαριασμό στο σύστημα Resgrid. Αν είστε Κάτοχος Λογαριασμού σε οποιοδήποτε τμήμα στο σύστημα Resgrid, πρέπει να διαγράψετε τον λογαριασμό σας διαγράφοντας εκείνο το τμήμα ή να μεταβιβάσετε την κυριότητα του Τμήματος σε κάποιον άλλον μέσω της επεξεργασίας των Ρυθμίσεων Τμήματος. + + + Αν επιθυμείτε να αποχωρήσετε από ένα τμήμα, συνιστάται οι διαχειριστές του Τμήματος να διαγράψουν τον λογαριασμό για εσάς μέσω της λίστας Προσωπικού. Αν όμως αυτό δεν είναι εφικτό, μπορείτε να διαγράψετε τον δικό σας λογαριασμό εδώ. + + + Λάβετε υπόψη ότι τα δεδομένα που παρέχονται στο Resgrid για ένα τμήμα ανήκουν σε εκείνο το τμήμα και ενδέχεται να υπόκεινται σε πολιτικές διατήρησης δεδομένων και στους νόμους που διέπουν τη δικαιοδοσία του τμήματος. Η διαγραφή του λογαριασμού σας εδώ θα διαγράψει τα προσωπικά σας αναγνωριστικά στοιχεία (PII) και τη σύνδεσή σας, αλλά δεν θα διαγράψει όλα τα δεδομένα. Θα χρειαστεί να υποβάλετε αίτημα στο τμήμα στο οποίο ανήκατε για επιπλέον ενέργειες, αν θέλετε να διαγραφούν όλα τα δεδομένα. + + + Η διαγραφή του λογαριασμού σας θα τον απενεργοποιήσει σε ΚΑΘΕ τμήμα στο οποίο είστε μέλος, όχι μόνο στο τρέχον. Όλες οι προγραμματισμένες αυτοματοποιήσεις σας (παραδόσεις αναφορών, προγραμματισμένες αλλαγές κατάστασης και προγραμματισμένες αλλαγές στελέχωσης) θα αφαιρεθούν και θα σταματήσετε να λαμβάνετε ειδοποιήσεις και επικοινωνίες από όλα τα τμήματα. + + + Δεν μπορείτε να διαγράψετε τον λογαριασμό σας επειδή είστε Κάτοχος Τμήματος σε ένα τμήμα. Αν επιθυμείτε να διαγράψετε το τμήμα, θα χρειαστεί να το κάνετε μέσω της σελίδας Ρυθμίσεων Τμήματος. Αν θέλετε να διατηρήσετε το τμήμα, επιλέξτε άλλο άτομο ως Κάτοχο Τμήματος και δοκιμάστε ξανά να διαγράψετε τον λογαριασμό σας. + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Account/ForcePasswordChange.el.resx b/Core/Resgrid.Localization/Areas/User/Account/ForcePasswordChange.el.resx new file mode 100644 index 000000000..9f8877c3b --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Account/ForcePasswordChange.el.resx @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + Ο Κωδικός Πρόσβασης Έληξε — Απαιτείται Αλλαγή + Ο Κωδικός Πρόσβασης Έληξε + Ο κωδικός πρόσβασής σας έληξε και πρέπει να αλλάξει προτού συνεχίσετε. + Η πολιτική ασφαλείας του τμήματός σας απαιτεί την περιοδική αλλαγή των κωδικών πρόσβασης. Επιλέξτε νέο κωδικό πρόσβασης για να συνεχίσετε. + Απαιτήσεις Κωδικού Πρόσβασης + Τουλάχιστον {0} χαρακτήρες + Τουλάχιστον ένα ψηφίο (αριθμός) + Τουλάχιστον ένα κεφαλαίο γράμμα + Τουλάχιστον ένα πεζό γράμμα + Τρέχων Κωδικός Πρόσβασης + Νέος Κωδικός Πρόσβασης + Επιβεβαίωση Νέου Κωδικού Πρόσβασης + Αλλαγή Κωδικού Πρόσβασης και Συνέχεια + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Calendar/Calendar.el.resx b/Core/Resgrid.Localization/Areas/User/Calendar/Calendar.el.resx new file mode 100644 index 000000000..4d8059262 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Calendar/Calendar.el.resx @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ολοήμερο + + + Ημερολόγιο + + + Λεπτομέρειες Συμβάντος + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον τύπο. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο + + + Τύποι Ημερολογίου + + + Ημέρες της Εβδομάδας + + + Διαγραφή Συμβάντος + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το συμβάν; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. + + + Διαγραφή Συμβάντος και Επαναλήψεων + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το συμβάν και όλες τις επαναλήψεις του; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. + + + Επεξεργασία Τύπου Ημερολογίου + + + Επεξεργασία Καταχώρισης Ημερολογίου + + + Αυτό είναι το γονικό στοιχείο επανάληψης. Αν επιλέξετε το πλαίσιο «Εφαρμογή σε όλα», θα ενημερωθούν όλα τα στοιχεία επανάληψης με τα δεδομένα αυτού του στοιχείου. Αν διαγράψετε αυτό το στοιχείο, θα διαγραφούν και όλα τα θυγατρικά στοιχεία επανάληψης. + + + Επεξεργασία Συμβάντος + + + Επεξεργασία Γονικού + + + Αφήστε το κενό για να μην λήγει ποτέ + + + Λήξη Στις + + + Οντότητες προς Ειδοποίηση + + + πρώτη + + + τέταρτη + + + τελευταία + + + Διαχείριση Τύπων + + + Διαχείριση Τύπων Ημερολογίου + + + Νέα Καταχώριση Ημερολογίου + + + Νέος Τύπος Ημερολογίου + + + Νέα Καταχώριση + + + Προσθήκη νέας καταχώρισης ημερολογίου + + + Χωρίς Προσαρμοσμένους Τύπους + + + Κανένα + + + Χωρίς Σημείωση + + + Δεν Υπάρχουν Προσεχή Στοιχεία + + + Αυτό είναι θυγατρικό στοιχείο επανάληψης. Δεν μπορείτε να αλλάξετε τις ρυθμίσεις επανάληψης για αυτό το στοιχείο. Αν χρειάζεται να αλλάξετε το μοτίβο επανάληψης για αυτή την καταχώριση, θα πρέπει να την αφαιρέσετε και να την προσθέσετε ξανά από τη γονική καταχώριση. + + + Αυτό είναι το γονικό στοιχείο επανάληψης. Δεν μπορείτε να αλλάξετε τις ρυθμίσεις επανάληψης για αυτό το στοιχείο. Αν χρειάζεται να αλλάξετε μοτίβο επανάληψης, πρέπει πρώτα να το αφαιρέσετε και μετά να δημιουργήσετε νέα καταχώριση επανάληψης. Μπορείτε να αλλάξετε οποιαδήποτε άλλη λεπτομέρεια της καταχώρισης. + + + Υπενθύμιση + + + Αφαίρεση από αυτό το συμβάν + + + Αφαίρεσέ με + + + Επανάληψη Την + + + Απάντηση + + + δεύτερη + + + Δήλωση Συμμετοχής (Απάντηση) + + + τρίτη + + + Τίτλος της καταχώρισης ημερολογίου + + + Τίτλος της καταχώρισης ημερολογίου + + + Χρώμα Τύπου + + + Όνομα Τύπου + + + Προσεχή + + + Ολοήμερο Συμβάν + + + {0} — {1} + + + Λήψη .ics + + + Εγγραφή στο Ημερολόγιο + + + Συγχρονισμός Ημερολογίου + + + Εγγραφείτε στο ημερολόγιο του τμήματός σας στο Google Calendar, το Microsoft Outlook, το Apple Calendar ή οποιαδήποτε εφαρμογή υποστηρίζει ροές iCal. + + + Ενεργοποίηση Συγχρονισμού Ημερολογίου + + + Επαναδημιουργία Κλειδιού Συγχρονισμού + + + Για να συγχρονίσετε το ημερολόγιο του τμήματός σας με εξωτερική εφαρμογή ημερολογίου, πρέπει πρώτα να ενεργοποιήσετε τον συγχρονισμό ημερολογίου. Αυτό δημιουργεί μια μοναδική διεύθυνση URL συνδρομής. Αν η διεύθυνση URL παραβιαστεί, μπορείτε να την επαναδημιουργήσετε ώστε να ακυρωθεί η παλιά. + + + Αντιγραφή στο Πρόχειρο + + + Διεύθυνση URL Συνδρομής + + + Άνοιγμα σε Εφαρμογή Ημερολογίου + + + Ο συγχρονισμός ημερολογίου είναι ενεργός. Χρησιμοποιήστε την παρακάτω διεύθυνση URL για εγγραφή. + + + Ο συγχρονισμός ημερολογίου δεν έχει ενεργοποιηθεί ακόμη. Κάντε κλικ στο παρακάτω κουμπί για να δημιουργήσετε τη διεύθυνση URL συνδρομής σας. + + + Τύποι + + + Δήλωση Άφιξης + + + Δήλωση Αναχώρησης + + + Ώρα Άφιξης + + + Ώρα Αναχώρησης + + + Διάρκεια + + + Η δήλωση άφιξης ολοκληρώθηκε + + + Η δήλωση αναχώρησης ολοκληρώθηκε + + + Επεξεργασία Ωρών Άφιξης + + + Χειροκίνητη Παράκαμψη + + + Δήλωση Άφιξης από Διαχειριστή + + + Δήλωση Άφιξης για Λογαριασμό Άλλου + + + Αναφορά Παρουσιών Συμβάντος + + + Σύνολο Συμβάντων που Παρακολουθήθηκαν + + + Συνολικές Ώρες + + + Δεν βρέθηκε εγγραφή άφιξης + + + Έχει ήδη δηλωθεί άφιξη + + + Δεν έχει δηλωθεί άφιξη + + + Δεν έχει δηλωθεί αναχώρηση + + + Καθυστερημένη Απάντηση + + + Προσαρμοσμένες Ώρες + + + Μη εξουσιοδοτημένο + + + Η δήλωση άφιξης διαγράφηκε + + + Σημείωση Άφιξης + + + Σημείωση Αναχώρησης + + + Δήλωση Άφιξης Από + + + Δήλωση Αναχώρησης Από + + + Απενεργοποιημένη + + + Αυτόματη Δήλωση Άφιξης/Αναχώρησης + + + Μόνο Δημιουργός και Διαχειριστές + + + Η δήλωση άφιξης ανοίγει 15 λεπτά πριν την έναρξη του συμβάντος. + + + Συμμετέχοντες + + + Οι επιλεγμένες ομάδες/άτομα θα προστεθούν ως συμμετέχοντες και θα ειδοποιηθούν απευθείας. + + + Οι επιλεγμένες ομάδες/άτομα θα ειδοποιηθούν για αυτό το συμβάν. + + + Εκτύπωση Φύλλου Δηλώσεων + + + Η δήλωση άφιξης δεν είναι πλέον διαθέσιμη για αυτό το συμβάν. + + + Δήλωση άφιξης από {0} + + + Δήλωση αναχώρησης από {0} + + diff --git a/Core/Resgrid.Localization/Areas/User/CommunicationTest/CommunicationTest.el.resx b/Core/Resgrid.Localization/Areas/User/CommunicationTest/CommunicationTest.el.resx new file mode 100644 index 000000000..1ae3a59e5 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/CommunicationTest/CommunicationTest.el.resx @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Δοκιμές Επικοινωνίας + + + Δοκιμές Επικοινωνίας + + + Νέα Δοκιμή + + + Επεξεργασία Δοκιμής + + + Διαγραφή Δοκιμής + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη δοκιμή; + + + Ορισμοί Δοκιμών + + + Όνομα + + + Περιγραφή + + + Τύπος Προγραμματισμού + + + Κατά Παραγγελία + + + Εβδομαδιαία + + + Μηνιαία + + + Ημέρες της Εβδομάδας + + + Ημέρα του Μήνα + + + Ώρα + + + Κανάλια προς Δοκιμή + + + SMS + + + Email + + + Φωνή + + + Push + + + Ενεργή + + + Ανενεργή + + + Παράθυρο Απόκρισης + + + Παράθυρο Απόκρισης (λεπτά) + + + Δημιουργία Δοκιμής + + + Αποθήκευση Αλλαγών + + + Ακύρωση + + + Πρόσφατες Εκτελέσεις Δοκιμών + + + Εκτέλεση Τώρα + + + Κωδικός Εκτέλεσης + + + Ξεκίνησε + + + Κατάσταση + + + Χρήστες που Δοκιμάστηκαν + + + Απαντήσεις + + + Προβολή Αναφοράς + + + Αναφορά Δοκιμής Επικοινωνίας + + + Λεπτομέρειες Εκτέλεσης + + + Αποτελέσματα ανά Χρήστη + + + Χρήστης + + + Επαφή + + + Πάροχος + + + Κατάσταση Επαλήθευσης + + + Απάντησε + + + Καμία Απάντηση + + + Δεν Στάλθηκε + + + Ποσοστό Απόκρισης + + + Η απάντησή σας στη δοκιμή επικοινωνίας καταγράφηκε. Ευχαριστούμε. + + + Το Resgrid έλαβε την απάντησή σας στη δοκιμή επικοινωνίας. Ευχαριστούμε. + + diff --git a/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx new file mode 100644 index 000000000..06d2a7ddb --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Κατηγορίας + + + Προσθήκη Επαφής + + + Διεύθυνση + + + Φυσική Διεύθυνση Οδού + + + Bluesky + + + Διεύθυνση URL Bluesky + + + Κατηγορίες + + + Κατηγορίες + + + Περιγραφή Κατηγορίας + + + Όνομα Κατηγορίας + + + Κινητό Τηλέφωνο + + + Αριθμός Κινητού Τηλεφώνου + + + Πόλη + + + Φυσική Πόλη + + + Τοποθεσία ή Εταιρεία + + + Όνομα Εταιρείας\Τοποθεσίας + + + Όνομα Οργανισμού ή Τοποθεσίας + + + Λεπτομέρειες Επαφής + + + Στοιχεία Επικοινωνίας + + + Επαφές + + + Επαφές στην Κατηγορία + + + Τύπος Επαφής + + + Χώρα + + + Αν αλλάζετε τον κωδικό πρόσβασής σας, πρέπει να εισαγάγετε εδώ τον τρέχοντα κωδικό (όχι τον νέο) + + + Άλλα Δεδομένα + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε την επαφή με το ακόλουθο όνομα; + + + Περιγραφή + + + Εμφάνιση στον Χάρτη + + + Επεξεργασία Κατηγορίας + + + Διεύθυνση email της ίδιας της Επαφής ή του Ατόμου Επικοινωνίας (ή κοινόχρηστου γραμματοκιβωτίου) της Εταιρείας + + + Διεύθυνση Email + + + Διεύθυνση Email του Ατόμου ή της Εταιρείας + + + Τοποθεσία GPS Εισόδου + + + Γεωγραφικό Πλάτος GPS της Εισόδου + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος GPS της Εισόδου + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Τοποθεσία GPS Εξόδου + + + Γεωγραφικό Πλάτος GPS της Εξόδου + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος GPS της Εξόδου + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Facebook + + + Όνομα ή διεύθυνση URL Facebook + + + Αριθμός Φαξ + + + Αριθμός Τηλεφώνου Φαξ + + + Όνομα + + + Όνομα της επαφής + + + Τηλέφωνο Οικίας + + + Αριθμός Τηλεφώνου Οικίας + + + Instagram + + + Όνομα Instagram + + + Επώνυμο + + + Επώνυμο της επαφής + + + Τελευταία Ενημέρωση + + + LinkedIn + + + Όνομα ή διεύθυνση URL LinkedIn + + + Τοποθεσία GPS + + + Γεωγραφικό Πλάτος Τοποθεσίας GPS + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος Τοποθεσίας GPS + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Πληροφορίες Τοποθεσίας + + + Διεύθυνση + + + Ταχυδρομική Διεύθυνση Οδού + + + Πόλη + + + Ταχυδρομική Πόλη + + + Χώρα + + + Ταχυδρομικός Κώδικας + + + Ταχυδρομικός Κώδικας Αλληλογραφίας + + + Νομός + + + Εικονίδιο Χάρτη + + + Mastodon + + + Όνομα Χρήστη και Διακομιστής Mastodon + + + Πατρώνυμο + + + Πατρώνυμο της επαφής + + + Νέα Επαφή + + + Δεν έχετε προσθέσει καμία Επαφή στο σύστημα. + + + Δεν έχετε Επαφές συσχετισμένες με αυτή την Κατηγορία. + + + Τηλέφωνο Γραφείου + + + Αριθμός Τηλεφώνου Γραφείου + + + Στοιχεία Επικοινωνίας + + + Άλλες Πληροφορίες + + + Άλλο Όνομα + + + Άλλο όνομα ή ψευδώνυμο + + + Φυσική Διεύθυνση + + + Ταχυδρομικός Κώδικας + + + Φυσικός Ταχυδρομικός Κώδικας + + + Προσπαθείτε να διαγράψετε μια Κατηγορία Επαφών. Είστε βέβαιοι ότι θέλετε να διαγράψετε την κατηγορία με το όνομα + + + Ίδια με τη Φυσική; + + + Νομός + + + Φυσικός Νομός\Επαρχία + + + Threads + + + Όνομα χρήστη Threads + + + Twitter + + + @OnomaXristiTwitter + + + Προβολή Κατηγορίας + + + Προβολή + + + Προβολή Επαφής + + + Διεύθυνση URL Ιστότοπου + + + Διεύθυνση URL ιστότοπου επαφής + + + Πρόσθετες Πληροφορίες + + + Δημιουργήθηκε Στις + + + Δημιουργήθηκε Από + + + Τύπος Επαφής + + + Ταχυδρομική Διεύθυνση + + + Πληροφορίες Τοποθεσίας + + + Κοινωνικά Δίκτυα + + + Δεν έχουν καταχωριστεί σημειώσεις για αυτή την Επαφή + + + Προσθήκη Σημείωσης + + + Τίτλος Σημείωσης + + + Περιεχόμενο Σημείωσης + + + Τύπος Σημείωσης + + + Λήγει Στις + + + Αφήστε το πεδίο Λήγει Στις κενό για να μην λήγει ποτέ. + + + Να Ειδοποιεί η Σημείωση + + + Αν επιλεγεί, αυτή η σημείωση θα εμφανίζεται όταν δημιουργείται νέα κλήση για αυτή την επαφή. + + + Προσθήκη Σημείωσης + + + Φιλτράρισμα Κατά + + + Φόρτωση Σημειώσεων + + + Αναζήτηση Σημειώσεων + + + Επεξεργασία Επαφής + + + Διαδρομές + + + Διαδρομή + + + Όνομα Στάσης + + + Κανονική + + + Υψηλή + + + Κρίσιμη + + + Προαιρετική + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.el.resx b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.el.resx new file mode 100644 index 000000000..7c7264e59 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.el.resx @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Ακύρωση + + + Αποθήκευση + + + Όνομα + + + Περιγραφή + + + Επεξεργασία + + + Διαγραφή + + + Εισαγωγή + + + Τύπος + + + Δημιουργήθηκε + + + Σειρά + + + Ναι + + + Όχι + + + + Προσαρμοσμένοι Χάρτες + + + Προσαρμοσμένοι Χάρτες + + + Νέος Προσαρμοσμένος Χάρτης + + + Όλοι + + + Εσωτερικοί Χώροι + + + Εξωτερικοί Χώροι + + + Συμβάν + + + Προσαρμοσμένος + + + Επίπεδα + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χάρτη; + + + + Νέος Προσαρμοσμένος Χάρτης + + + Νέος Προσαρμοσμένος Χάρτης + + + Νέος + + + Τύπος Χάρτη + + + Εσωτερικός Χώρος + + + Εξωτερικός Χώρος + + + Συμβάν + + + Προσαρμοσμένος + + + π.χ. Κεντρικό Νοσοκομείο, Χώρος Συναυλίας + + + Προαιρετική περιγραφή + + + Σχεδίαση Ορίων + + + Σχεδιάστε ένα ορθογώνιο στον παρακάτω χάρτη για να ορίσετε τα όρια. Κάντε κλικ στον χάρτη για να ορίσετε το κεντρικό σημείο. + + + Σχεδιάστε ένα ορθογώνιο στον παρακάτω χάρτη για να ενημερώσετε τα όρια. Κάντε κλικ στον χάρτη για να ορίσετε το κεντρικό σημείο. + + + + Επεξεργασία Προσαρμοσμένου Χάρτη + + + Επεξεργασία Προσαρμοσμένου Χάρτη + + + Επεξεργασία + + + + Επίπεδα + + + Επίπεδα + + + Επίπεδα + + + Προσθήκη Επιπέδου + + + Όνομα Επιπέδου + + + π.χ. Όροφος 1, Βασικός Χάρτης, Υποδομές + + + Σειρά + + + Τύπος + + + Κάτοψη + + + Επικάλυψη + + + Επίπεδο Δεδομένων + + + Υποδομές + + + Εικόνα Επιπέδου + + + Εικόνες μεγαλύτερες από 2048px θα χωριστούν αυτόματα σε πλακίδια. + + + Προσθήκη Επιπέδου + + + Έχει Εικόνα + + + Σε Πλακίδια + + + Επεξεργαστής Περιοχών + + + Διαγραφή αυτού του επιπέδου; + + + + Επεξεργαστής Περιοχών + + + Επεξεργαστής Περιοχών + + + Περιοχές + + + Ιδιότητες Περιοχής + + + Όνομα + + + Τύπος + + + Δωμάτιο + + + Πτέρυγα + + + Διάδρομος + + + Κλιμακοστάσιο + + + Ανελκυστήρας + + + Ζώνη Κινδύνου + + + Σημείο Συγκέντρωσης + + + Χώρος Αναμονής + + + Σημείο Πρόσβασης + + + Δίκτυα Κοινής Ωφέλειας + + + Πλέγμα Έρευνας + + + Σκηνή + + + Πύλη + + + Είσοδος + + + Χώρος Στάθμευσης + + + Περιοχή Προμηθευτών + + + Λωρίδα Έκτακτης Ανάγκης + + + Περιφέρεια + + + Προσαρμοσμένη + + + Χρώμα + + + Περιγραφή + + + Αναζητήσιμη στη διαβίβαση + + + Επιτρέπει Αποστολή + + + Αποθήκευση Περιοχής + + + Επιτρέπει Αποστολή + + + + Εισαγωγή + + + Εισαγωγή + + + Εισαγωγή + + + Εισαγωγή Γεωχωρικών Δεδομένων + + + Επίπεδο Προορισμού + + + Αρχείο + + + Υποστηριζόμενες μορφές: GeoJSON (.geojson, .json), KML (.kml), KMZ (.kmz) + + + Εισαγωγή + + + Ιστορικό Εισαγωγών + + + Κατάσταση + + + Ημερομηνία + + + Σφάλμα + + + Δεν υπάρχουν διαθέσιμα επίπεδα. Δημιουργήστε πρώτα ένα επίπεδο πριν από την εισαγωγή. + + + Επιλέξτε επίπεδο προορισμού πριν από την εισαγωγή. Δημιουργήστε πρώτα ένα επίπεδο αν δεν υπάρχει. + + diff --git a/Core/Resgrid.Localization/Areas/User/CustomStatuses/CustomStatuses.el.resx b/Core/Resgrid.Localization/Areas/User/CustomStatuses/CustomStatuses.el.resx new file mode 100644 index 000000000..7e4031122 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/CustomStatuses/CustomStatuses.el.resx @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Επιλογής (Κουμπιού) + + + Διαθέσιμος + + + Βασικός Τύπος + + + Χρώμα Φόντου Κουμπιού + + + Χρώμα Κουμπιού + + + Προεπισκόπηση Κουμπιού + + + Αυτά είναι τα κουμπιά που θα βλέπουν οι χρήστες σας στις εφαρμογές για κινητά και στον ιστότοπο. + + + Κείμενο Κουμπιού + + + Χρώμα Κειμένου Κουμπιού + + + Κείμενο Κουμπιού + + + Κλήσεις + + + Κλήσεις και Σταθμοί + + + Οι αλλαγές στις προσαρμοσμένες καταστάσεις μπορεί να χρειαστούν έως και 30 λεπτά για να διαδοθούν πλήρως στο δίκτυο Resgrid. Οι χρήστες εφαρμογών για κινητά ενθαρρύνονται να κάνουν χειροκίνητο επανασυγχρονισμό (Ρυθμίσεις->Σύνθετες Ρυθμίσεις->Επανασυγχρονισμός). + + + Απαλλάχθηκε + + + Προσαρμοσμένες Καταστάσεις Προσωπικού (Ενέργειες) + + + Τρέχουσες Καταστάσεις Μονάδων + + + Προσαρμοσμένα Επίπεδα Στελέχωσης Προσωπικού + + + Μια περιγραφή των προσαρμοσμένων καταστάσεων. + + + Προσαρμοσμένες Καταστάσεις + + + Όνομα Συνόλου Προσαρμοσμένων Καταστάσεων (π.χ. «Οι Καταστάσεις Μου») + + + Προεπιλεγμένες Ενέργειες/Καταστάσεις Προσωπικού + + + Προεπιλεγμένη Στελέχωση Προσωπικού + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτό το σύνολο καταστάσεων και τα κουμπιά σας θα επανέλθουν στις προεπιλογές για αυτόν τον τύπο μονάδας. Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το σύνολο καταστάσεων; + + + Τύπος Λεπτομέρειας + + + Απεστάλη + + + Επεξεργασία Λεπτομέρειας Προσαρμοσμένης Κατάστασης + + + Επεξεργασία Προσαρμοσμένων Καταστάσεων + + + Επεξεργασία Συνόλου + + + Επεξεργασία Καταστάσεων Προσωπικού + + + Επεξεργασία Επιπέδων Στελέχωσης + + + Διερεύνηση + + + Έγινε Επαφή + + + Νέες Προσαρμοσμένες Καταστάσεις + + + Νέα Επιλογή + + + Χωρίς Λεπτομέρεια + + + Καμία + + + Χωρίς Σημείωση + + + Τύπος Σημείωσης + + + Δεν Ανταποκρίνεται + + + Στο Σημείο + + + Ενέργειες Προσωπικού + + + Στελέχωση Προσωπικού + + + Καταστάσεις Προσωπικού + + + Προεπισκόπηση Κουμπιού + + + Απαίτηση GPS + + + Ανταποκρίνεται + + + Επιστρέφει + + + Ορισμός Προσαρμοσμένων Επιπέδων Στελέχωσης + + + Ορισμός Προσαρμοσμένων Καταστάσεων + + + Επίπεδα Στελέχωσης + + + Χώρος Αναμονής + + + Σταθμοί + + + Χρώμα Κειμένου + + + Μη Διαθέσιμος + + + Κατάσταση Μονάδας + + + Καθ' Οδόν + + + Μεταφέρει + + + Παραδίδει + + + Στον Ασθενή + + + Στο Νοσοκομείο + + + Έρευνα + + + Φόρτωση + + + Αναμονή + + + Σε Περιπολία + + + Συντήρηση + + + Σε Διάλειμμα + + + Ολοκληρώθηκε + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx index 26ab9aba3..413216115 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx @@ -942,4 +942,112 @@ بطاقات الإرسال + + تفتح جميع الخرائط في Resgrid والتطبيقات عند هذه النقطة. اترك الحقلين فارغين وسنستنتجهما من عنوان القسم عند الحفظ التالي؛ املأهما لتحديد موقع دقيق لن يتم استبداله أبدًا. + + + حقول نموذج البلاغ الجديد + + + اختر الحقول المدمجة التي تظهر في النموذج وأيها يجب إكماله قبل إنشاء البلاغ وإرساله إلى الميدان. الاسم والطبيعة والأولوية والنوع تظهر دائمًا وهي مطلوبة دائمًا. + + + الحقل + + + مرئي + + + مطلوب + + + العنوان + + + إحداثيات GPS + + + what3words + + + Plus Code + + + الوجهة + + + موقع داخلي + + + ملاحظة + + + جهة الاتصال الرئيسية + + + معلومات الاتصال بالمبلّغ + + + معرّف خارجي + + + معرّف الحادث + + + المعرّف المرجعي + + + البروتوكولات + + + بلاغ مرتبط + + + إرسال مجدول + + + مستلمو الإرسال + + + مؤقتات حالة الوحدة + + + يُبرز وحدة على لوحة Big Board عندما تبقى في حالة ما فترة طويلة — مثلاً مُرسلة لأكثر من أربع دقائق دون الإبلاغ عن المغادرة. اترك العمودين عند 0 لعدم المراقبة. + + + الحالة + + + تحذير بعد (دقائق) + + + إنذار بعد (دقائق) + + + مُرسلة + + + مستجيبة + + + في الطريق + + + في الموقع + + + في الانتظار + + + عند المريض + + + نقل + + + في المستشفى + + + عائدة + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.de.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.de.resx index 09cedecf6..678564de6 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.de.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.de.resx @@ -893,4 +893,112 @@ Ausrückeordnungen + + Jede Karte in Resgrid und in den Apps öffnet sich hier. Beide Felder leer lassen, dann ermitteln wir die Position beim nächsten Speichern aus der oben angegebenen Abteilungsadresse; ausgefüllte Werte legen einen genauen Punkt fest und werden nie überschrieben. + + + Felder im Einsatzformular + + + Wählen Sie, welche Standardfelder im Formular für neue Einsätze erscheinen und welche ausgefüllt sein müssen, bevor ein Einsatz angelegt und an die Kräfte gesendet werden kann. Name, Art, Priorität und Typ werden immer angezeigt und sind immer erforderlich. + + + Feld + + + Sichtbar + + + Erforderlich + + + Adresse + + + GPS-Koordinaten + + + what3words + + + Plus Code + + + Ziel + + + Innenraum-Standort + + + Notiz + + + Hauptkontakt + + + Kontaktdaten des Melders + + + Externe Einsatz-ID + + + Vorgangs-ID + + + Referenz-ID + + + Protokolle + + + Verknüpfter Einsatz + + + Geplante Alarmierung + + + Alarmierungsempfänger + + + Zeitgeber für Einheitenstatus + + + Hebt eine Einheit auf dem Big Board hervor, wenn sie zu lange in einem Status verharrt — zum Beispiel länger als vier Minuten alarmiert, ohne die Ausrückung zu melden. Beide Spalten auf 0 lassen, um nichts zu überwachen. + + + Status + + + Warnen nach (Minuten) + + + Alarmieren nach (Minuten) + + + Alarmiert + + + Auf Anfahrt + + + Unterwegs + + + Vor Ort + + + Bereitstellung + + + Beim Patienten + + + Transport + + + Im Krankenhaus + + + Rückfahrt + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.el.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.el.resx new file mode 100644 index 000000000..8ae244736 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.el.resx @@ -0,0 +1,1083 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ζητήστε τη διαγραφή του τμήματός σας. Πρόκειται για μόνιμη και μη αναστρέψιμη ενέργεια που χρειάζεται 25 ημέρες για να ολοκληρωθεί και εκτελείται εκτός ωραρίου. Κατά την περίοδο αναμονής μπορείτε να ακυρώσετε το αίτημα αν το επιθυμείτε. + + + Αίτημα Διαγραφής Τμήματος + + + Το τμήμα σας έχει εκκρεμές αίτημα διαγραφής. Αν θέλετε να προχωρήσει αυτό το αίτημα, δεν χρειάζεται να κάνετε τίποτα· μετά την παρακάτω χρονική σήμανση (κατά τη νυχτερινή μας διαδικασία) το τμήμα σας και όλα του τα δεδομένα θα αφαιρεθούν. Αν θέλετε να ακυρώσετε το αίτημα και να μην διαγραφεί το τμήμα, πατήστε το κουμπί «Ακύρωση Διαγραφής Τμήματος» παρακάτω. Σημειώστε ότι οποιεσδήποτε αλλαγές σε διαχειριστές και κατόχους θα εμποδίσουν την ολοκλήρωση της διαδικασίας· συνιστάται να χρησιμοποιείτε το σύστημα μόνο σε λειτουργία ανάγνωσης για να κατεβάσετε δεδομένα που θέλετε να διατηρήσετε. + + + Ρυθμίσεις Χαρτογράφησης + + + Οι Ρυθμίσεις Χαρτογράφησης αποθηκεύτηκαν με επιτυχία. + + + Ρυθμίσεις Χαρτογράφησης Προσωπικού + + + Ρυθμίσεις Χαρτογράφησης Μονάδων + + + Να επιτρέπεται σε κατάσταση χωρίς δεδομένα τοποθεσίας να αποκρύπτει την προηγούμενη τοποθεσία + + + TTL σε Λεπτά για Τοποθεσίες Προσωπικού + + + Να επιτρέπεται σε κατάσταση μονάδας χωρίς δεδομένα τοποθεσίας να αποκρύπτει την προηγούμενη τοποθεσία + + + TTL σε Λεπτά για Τοποθεσίες Μονάδων + + + Αν είναι ενεργοποιημένο και έρθει κατάσταση για τον χρήστη (π.χ. Σε Αναμονή) χωρίς γεωτοποθεσία, θα αποκρύψει την τοποθεσία του χρήστη στον χάρτη. Διαφορετικά θα εμφανίζεται η τελευταία τοποθεσία του χρήστη, ακόμη και αν είναι παλαιότερη. + + + Αν είναι ενεργοποιημένο και έρθει κατάσταση για τη μονάδα (π.χ. Στη Βάση) χωρίς γεωτοποθεσία, θα αποκρύψει την τοποθεσία της μονάδας στον χάρτη. Διαφορετικά θα εμφανίζεται η τελευταία τοποθεσία, ακόμη και αν είναι παλαιότερη. + + + Σε Λεπτά, για πόσο ισχύουν οι τοποθεσίες ώστε να εμφανίζονται στον χάρτη. Δηλαδή η τιμή 60 σημαίνει ότι ο δείκτης ενός ατόμου θα εμφανίζεται στους χάρτες έως και 60 λεπτά, μετά δεν θα εμφανίζεται. Η τιμή 0 απενεργοποιεί το TTL και μια τοποθεσία, ανεξάρτητα από την παλαιότητά της, θα εμφανίζεται στον χάρτη. + + + Σε Λεπτά, για πόσο ισχύουν οι τοποθεσίες ώστε να εμφανίζονται στον χάρτη. Δηλαδή η τιμή 60 σημαίνει ότι ο δείκτης μιας μονάδας θα εμφανίζεται στους χάρτες έως και 60 λεπτά, μετά δεν θα εμφανίζεται. Η τιμή 0 απενεργοποιεί το TTL και μια τοποθεσία, ανεξάρτητα από την παλαιότητά της, θα εμφανίζεται στον χάρτη. + + + Ρυθμίσεις Αποστολής Ομάδων + + + Ρυθμίσεις Αποστολής Μονάδων + + + Αποστολή Επίσης στο Ανατεθειμένο Προσωπικό + + + Αποστολή Επίσης σε Ολόκληρη την Ομάδα + + + Ρυθμίσεις Βαρδιών + + + Οι Ρυθμίσεις Βαρδιών αποθηκεύτηκαν με επιτυχία. + + + Ρυθμίσεις Δήλωσης Συμμετοχής σε Βάρδιες + + + Να Επιτρέπονται Δηλώσεις για Πολλαπλές Ομάδες + + + Το Προσωπικό σε Μονάδα Ορίζει την Κατάσταση Μονάδας + + + Κάτοχος Λογαριασμού + + + Διεύθυνση + + + Δεν μπορέσαμε να επαληθεύσουμε τη διεύθυνση που δώσατε. Ελέγξτε ότι είναι σωστή πριν συνεχίσετε. + + + Αυτή είναι η διεύθυνση του τμήματός σας· μπορεί να είναι η διεύθυνση ενός μεμονωμένου σταθμού ή του γραφείου της περιφέρειάς σας. Αυτή η διεύθυνση χρησιμοποιείται για τον εντοπισμό της περιφέρειας και της περιοχής σας και ως εφεδρική για ορισμένες λειτουργίες (π.χ. κεντράρισμα του big board). + + + Ρυθμίσεις API και RSS + + + Ρυθμίσεις API + + + Ρυθμίσεις Εισαγωγής Κλήσεων + + + Ταξινόμηση Κλήσεων + + + Πόλη + + + Πόλη + + + Ολοκληρώθηκε Στις + + + Χώρα + + + Χώρα + + + Προεπιλεγμένες Συντεταγμένες GPS Κέντρου Χάρτη + + + Διαγραφή Πρόσκλησης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την πρόσκληση και ο σύνδεσμος που έχει ο χρήστης στο email θα οδηγεί σε σφάλμα. Είστε βέβαιοι ότι θέλετε να διαγράψετε την πρόσκληση; + + + Διεύθυνση Τμήματος + + + Όνομα Τμήματος + + + Ρυθμίσεις Τμήματος + + + Απενεργοποίηση Αυτόματης Διαθεσιμότητας + + + Απενεργοποίηση της αυτόματης ρύθμισης της κατάστασης του προσωπικού σε Διαθέσιμος μετά από μία ώρα + + + Email Εισαγωγής Αποστολών + + + Αυτή είναι η διεύθυνση email γενικής κλήσης· όλοι οι χρήστες του τμήματός σας (που έχουν ενεργοποιημένες τις ειδοποιήσεις κλήσεων) θα λαμβάνουν την ειδοποίηση κλήσης + + + Ρυθμίσεις Αποστολής + + + Ρυθμίσεις Αποστολής + + + Διεύθυνση Email + + + Διευθύνσεις Email + + + Εισαγάγετε τις διευθύνσεις email χωρισμένες με κόμμα (π.χ. "test1@test.com, test2@test.com") + + + Τύπος Email + + + Εξαναγκασμός Ενημέρωσης Τμήματος + + + Το Resgrid αποθηκεύει προσωρινά ορισμένα δεδομένα τμήματος για βελτίωση της απόδοσης. Παραδείγματα δεδομένων που αποθηκεύονται προσωρινά είναι τα επίπεδα στελέχωσης, οι ρυθμίσεις τμήματος και οι ρόλοι\ομάδες\ονόματα προσωπικού. Αν αντιμετωπίζετε προβλήματα όπου ορισμένα δεδομένα τμήματος είναι λανθασμένα, ίσως θέλετε να εκκαθαρίσετε την προσωρινή μνήμη του τμήματός σας. + + + Προσκλήσεις + + + Γεωγραφικό Πλάτος Κέντρου Χάρτη + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος Κέντρου Χάρτη + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Λεπτά διατήρησης της κλήσης ανοιχτής + + + Η παραπάνω τιμή θα κρατά τις κλήσεις που εισάγονται μέσω email ανοιχτές για τόσο χρόνο και μετά θα κλείνουν. Πρέπει επίσης να είναι επιλεγμένο το πλαίσιο Περικοπής για να γίνεται αυτόματο κλείσιμο. + + + Εκκρεμής + + + Κατάσταση Αποστολής Ατόμου σε Κλήση + + + Κατάσταση Αποδέσμευσης Ατόμου από Κλήση + + + Κατάσταση Αποστολής Μονάδας σε Κλήση + + + Κατάσταση Αποδέσμευσης Μονάδας από Κλήση + + + Αυτές οι προεπιλεγμένες καταστάσεις μονάδων χρησιμοποιούν μόνο τους ενσωματωμένους τύπους κατάστασης μονάδας. Χρησιμοποιήστε τον παρακάτω πίνακα παρακάμψεων τύπου μονάδας όταν ένας τύπος μονάδας χρειάζεται δικές του προσαρμοσμένες καταστάσεις. + + + Παρακάμψεις Κατάστασης ανά Τύπο Μονάδας + + + Εδώ εμφανίζονται μόνο οι τύποι μονάδων με προσαρμοσμένες καταστάσεις. Αφήστε μια τιμή στην Προεπιλογή για να χρησιμοποιηθεί η παραπάνω ενσωματωμένη κατάσταση σε επίπεδο τμήματος. + + + Ταξινόμηση Προσωπικού + + + Επαναφορά Στελέχωσης Προσωπικού + + + Αν χρειάζεται να επαναφέρετε καθημερινά το επίπεδο στελέχωσης όλου του προσωπικού σας, μπορείτε να το διαμορφώσετε εδώ. Ένα παράδειγμα θα ήταν η επαναφορά της στελέχωσης σε μη διαθέσιμη κάθε ημέρα, ώστε τις ημέρες υψηλού κινδύνου να είστε βέβαιοι ότι το προσωπικό που έχει σημανθεί ως διαθέσιμο δήλωσε διαθεσιμότητα μετά την επαναφορά. + + + Επαναφορά Κατάστασης Προσωπικού + + + Αν χρειάζεται να επαναφέρετε καθημερινά τις καταστάσεις όλου του προσωπικού σας, μπορείτε να το διαμορφώσετε εδώ. Ένα παράδειγμα θα ήταν η επαναφορά της κατάστασης σε «Σε Αναμονή» κάθε ημέρα, ώστε να γνωρίζετε ότι η κατάσταση του προσωπικού έχει μηδενιστεί. + + + Ταχυδρομικός Κώδικας + + + Δημιουργία Διευθύνσεων URL Rss\Atom + + + Το Resgrid δεν μπόρεσε να δεσμεύσει τον αριθμό που ζητήσατε· δοκιμάστε ξανά με άλλον αριθμό. Αν δεν μπορείτε να δεσμεύσετε αριθμό, επικοινωνήστε μαζί μας για να μας ενημερώσετε. + + + Αποτυχία Δέσμευσης + + + Περικοπή (Αυτόματο Κλείσιμο) Κλήσεων + + + Αυτό θα κλείνει αυτόματα τις κλήσεις που δημιουργούνται αυτόματα (π.χ. μέσω διαδικασίας εισαγωγής email, μηνύματος ή ήχου) + + + Επαναδημιουργία Διευθύνσεων URL RSS + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η επαναδημιουργία των διευθύνσεων URL Rss\Atom θα απαιτήσει από όλους τους αναγνώστες\καταναλωτές να ενημερωθούν σε νέα διεύθυνση URL. Είστε βέβαιοι ότι θέλετε να τις επαναδημιουργήσετε; + + + Επαναποστολή Πρόσκλησης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα στείλει ξανά αυτή την πρόσκληση στη διεύθυνση email της πρόσκλησης και θα επαναφέρει την ημερομηνία αποστολής. Είστε βέβαιοι ότι θέλετε να στείλετε ξανά αυτή την πρόσκληση; + + + Καθημερινή Επαναφορά Στελέχωσης; + + + Επαναφορά Επιπέδου Στελέχωσης Σε + + + Καθημερινή Επαναφορά Κατάστασης; + + + Ώρα Επαναφοράς Κατάστασης + + + Επαναφορά Κατάστασης Σε + + + Ροή RSS Ενεργών Κλήσεων + + + Το Resgrid επιτρέπει την πρόσβαση στις ενεργές κλήσεις του τμήματός σας μέσω ροής RSS. Αυτό επιτρέπει σε οποιονδήποτε αναγνώστη ροών (όπως αναγνώστη ιστολογίων ή ακόμη και το Microsoft Outlook) να εμφανίζει ενεργά και να ειδοποιεί για νέες κλήσεις. Για μέγιστη συμβατότητα, το Resgrid θα δημιουργήσει μοναδική διεύθυνση URL για αναγνώστες RSS 2.0. Αυτή η διεύθυνση URL δεν έχει άλλη προστασία (όνομα χρήστη\κωδικό κ.λπ.), οπότε είναι σημαντικό να μην κοινοποιείται δημόσια αν δεν θέλετε τα δεδομένα ενεργών κλήσεων να είναι δημόσια. + + + Το τμήμα σας δεν έχει δεσμευμένες διευθύνσεις URL ροής RSS. Κάντε κλικ στο παρακάτω κουμπί για να δεσμεύσετε τις διευθύνσεις URL RSS σας. Δεσμεύστε διευθύνσεις URL RSS\Atom μόνο όταν τις χρειάζεται το τμήμα σας. + + + Οι Ρυθμίσεις Αποστολής αποθηκεύτηκαν με επιτυχία. + + + Αποστολή Προσκλήσεων + + + Στάλθηκε Στις + + + Ορισμός Κατάστασης για Προσωπικό Βάρδιας κατά την Αποστολή + + + Ρυθμίσεις Βαρδιών + + + Νομός/Επαρχία + + + Νομός/Επαρχία + + + Οδός + + + Οδός + + + Ώρα Επαναφοράς Στελέχωσης + + + Ζώνη Ώρας + + + Ταξινόμηση Μονάδων + + + Χρήση 24ωρης Μορφής Ώρας + + + Χρήση Βάρδιας για Αποστολή Ομάδας + + + Ταχυδρομικός Κώδικας + + + Διαγραφή Τμήματος + + + Εδώ μπορείτε να ζητήσετε τη διαγραφή του τμήματός σας. Μόνο ο Κάτοχος Λογαριασμού μπορεί να ζητήσει τη διαγραφή του τμήματος. Όλοι οι χρήστες και τα δεδομένα που σχετίζονται με το τμήμα σας θα αφαιρεθούν από το σύστημα Resgrid μετά την ολοκλήρωση της διαδικασίας. Αυτή η διαδικασία δεν είναι αναστρέψιμη! Οποιαδήποτε στιγμή πριν ολοκληρωθεί, μπορείτε να ακυρώσετε το αίτημα και να διατηρήσετε τα δεδομένα σας. Το τμήμα σας θα διαγραφεί 25 ημέρες μετά την υποβολή του αιτήματος. + + + Αίτημα Διαγραφής + + + Επιλέξτε το παρακάτω πλαίσιο για να επιβεβαιώσετε το αίτημα διαγραφής σας + + + Κατανοώ και συμφωνώ με τη διαδικασία διαγραφής + + + Ακύρωση Διαγραφής Τμήματος + + + Διαγραφή του Τμήματός Σας + + + Ενεργοποίηση Καταστολής Στελέχωσης + + + Καταστολή (Σίγαση) για Αυτά τα Επίπεδα Στελέχωσης + + + Επιλέξτε παρακάτω τα Επίπεδα Στελέχωσης Προσωπικού για τα οποία δεν θα λαμβάνονται ειδοποιήσεις, αποστολές, ειδοποιήσεις συναγερμού, μηνύματα ή οποιαδήποτε άλλη επικοινωνία. Συνήθως πρόκειται για τα επίπεδα «Εκτός Υπηρεσίας» ή «Σε Άδεια». + + + Προσθήκη Επαφής + + + Τύπος Επαφής + + + Εδώ μπορείτε να ελέγξετε πληροφορίες υψηλού επιπέδου για κάθε Ενότητα του συστήματος Resgrid. Νέες επιλογές ενοτήτων υψηλού επιπέδου θα προστίθενται εδώ. Αυτές οι ρυθμίσεις βρίσκονται σε πρώιμο στάδιο και ενδέχεται να μην έχουν ακόμη εφαρμοστεί σε όλα τα μέρη του συστήματος Resgrid. Για παράδειγμα, αν απενεργοποιήσετε την ενότητα Εκπαίδευσης, μπορεί να εμφανίζεται ακόμη σε κάποια οθόνη βαθιά μέσα σε άλλη λειτουργία. Θα εργαστούμε στο μέλλον ώστε να διασφαλιστεί η συνέπεια. + + + Ημερολόγιο + + + Απόθεμα + + + Καταγραφές + + + Συντήρηση + + + Σημειώσεις + + + Αναφορές + + + Εκπαίδευση + + + Χαρτογράφηση + + + Μηνύματα + + + Βάρδιες + + + Ενότητες Τμήματος + + + Απενεργοποίηση Εγγράφων + + + Οι Ρυθμίσεις Ενοτήτων Τμήματος Αποθηκεύτηκαν + + + Όταν μια μονάδα υποβάλλει κατάσταση και υπάρχουν χρήστες σε ρόλους, η κατάσταση των χρηστών θα οριστεί σε Επί Μονάδας. + + + Ρυθμίσεις Κλήσεων και Αποστολής + + + Ρυθμίσεις Κλήσεων και Αποστολής + + + Οι Ρυθμίσεις Κλήσεων και Αποστολής αποθηκεύτηκαν με επιτυχία. + + + Ρυθμίσεις Χρονομέτρων Παρουσίας + + + Γενικές Ρυθμίσεις + + + Αυτόματη Ενεργοποίηση Χρονομέτρων Παρουσίας για Νέες Κλήσεις + + + Όταν είναι ενεργοποιημένο, όλες οι νέες κλήσεις θα έχουν αυτόματα ενεργά χρονόμετρα παρουσίας. + + + Προεπιλεγμένες Διαμορφώσεις Χρονομέτρων + + + Αυτές ορίζουν τα προεπιλεγμένα διαστήματα παρουσίας για κάθε τύπο στόχου. Ισχύουν για όλες τις κλήσεις εκτός αν παρακαμφθούν από τους παρακάτω κανόνες τύπου/προτεραιότητας κλήσης. + + + Παρακάμψεις Τύπου / Προτεραιότητας Κλήσης + + + Οι παρακάμψεις υπερισχύουν των προεπιλεγμένων χρονομέτρων όταν μια κλήση ταιριάζει με τον καθορισμένο τύπο ή/και προτεραιότητα. + + + Τύπος Στόχου + + + Τύπος Μονάδας + + + Διάρκεια (λεπτά) + + + Όριο Προειδοποίησης (λεπτά) + + + Ενεργοποιημένο + + + Αναγνωριστικό Τύπου Κλήσης + + + Προτεραιότητα Κλήσης + + + Προσθήκη Διαμόρφωσης Χρονομέτρου + + + Προσθήκη Παράκαμψης + + + Προσθήκη Νέου Προεπιλεγμένου Χρονομέτρου + + + Προσθήκη Νέας Παράκαμψης + + + -- Κανένα -- + + + -- Οποιοδήποτε -- + + + Προσωπικό + + + Τύπος Μονάδας + + + Διοικητής Συμβάντος + + + PAR (Λογοδοσία Προσωπικού) + + + Έκθεση σε Επικίνδυνα Υλικά + + + Εναλλαγή Τομέα + + + Αποκατάσταση + + + αφήστε το κενό για οποιοδήποτε + + + Ναι + + + Όχι + + + Διαγραφή αυτής της διαμόρφωσης χρονομέτρου; + + + Διαγραφή αυτής της παράκαμψης; + + + Προεπιλογή + + + Παράκαμψη + + + Χαμηλή + + + Μεσαία + + + Υψηλή + + + Έκτακτης Ανάγκης + + + Αποθήκευση Γενικών Ρυθμίσεων + + + Ενεργές Καταστάσεις + + + Αφήστε το κενό για «Όλες τις Καταστάσεις». + + + Όλες + + + Γλώσσα TTS + + + Επιλέξτε τη γλώσσα ή διάλεκτο eSpeak-NG που θα χρησιμοποιηθεί για τα φωνητικά μηνύματα αυτού του τμήματος. + + + Ρυθμίσεις Βοηθού + + + Βοηθός + + + Διαμόρφωση Βοηθού + + + Ενεργοποίηση Βοηθού + + + Επιτρέψτε στα μέλη αυτού του τμήματος να αλληλεπιδρούν με τον βοηθό. + + + Επιτρεπόμενες Πλατφόρμες + + + Ονόματα πλατφορμών χωρισμένα με κόμμα που επιτρέπει αυτό το τμήμα ή * για όλες. Έγκυρα ονόματα: SmsTwilio, SmsSignalWire, Discord, Slack, Telegram, WhatsApp, Teams, Signal, WebChat. + + + Να Επιτρέπεται Αποστολή μέσω Βοηθού + + + Επιτρέψτε σε εξουσιοδοτημένους χρήστες να δημιουργούν ή να στέλνουν κλήσεις μέσω του βοηθού. + + + Επιβεβαίωση Αλλαγών Κατάστασης + + + Απαίτηση ρητής επιβεβαίωσης πριν από επικίνδυνες ενέργειες ή ενέργειες αλλαγής κατάστασης. + + + Επιβεβαίωση Σύνδεσης Τηλεφώνου + + + Απαίτηση εφάπαξ επιβεβαίωσης «Απαντήστε YES» πριν από τη σύνδεση αριθμού SMS με αντίστοιχο λογαριασμό. + + + Προληπτικές Ειδοποιήσεις + + + Επιτρέψτε στον βοηθό να στέλνει προληπτικές ειδοποιήσεις (κλήσεις, υπενθυμίσεις) σε συνδεδεμένους χρήστες. + + + Μηνύματα / Χρήστη / Λεπτό + + + Όριο ρυθμού ανά χρήστη. Αφήστε το κενό για χρήση της προεπιλογής του συστήματος. + + + Μηνύματα / Τμήμα / Λεπτό + + + Όριο ρυθμού ανά τμήμα. Αφήστε το κενό για χρήση της προεπιλογής του συστήματος. + + + Πάροχος AI / LLM Τμήματος (προαιρετικό) + + + Αφήστε τα κενά για χρήση του παρόχου AI του συστήματος Resgrid. Για να διατηρήσετε την επεξεργασία του βοηθού στον δικό σας πάροχο, εισαγάγετε τελικό σημείο συμβατό με OpenAI, μοντέλο και κλειδί API. + + + Τελικό Σημείο LLM + + + Μοντέλο LLM + + + Κλειδί API LLM + + + Υπάρχει αποθηκευμένο κλειδί. Αφήστε το κενό για να το διατηρήσετε ή εισαγάγετε νέα τιμή για να το αντικαταστήσετε. + + + Δεν έχει αποθηκευτεί κλειδί. Το κλειδί κρυπτογραφείται κατά την αποθήκευση και δεν εμφανίζεται ποτέ. + + + Αποθήκευση + + + Ακύρωση + + + Οι ρυθμίσεις βοηθού αποθηκεύτηκαν. + + + Παρουσιάστηκε σφάλμα κατά την αποθήκευση των ρυθμίσεων βοηθού. + + + Ενεργοποίηση Σύγχρονων Ειδοποιήσεων + + + Όταν είναι ενεργοποιημένο, οι ειδοποιήσεις push θα χρησιμοποιούν τους σύγχρονους ήχους ειδοποίησης αντί για τους παλαιούς για όλους τους χρήστες αυτού του τμήματος. + + + Απαίτηση PIN Ασφαλείας + + + Απαιτήστε από κάθε μέλος να επιβεβαιώνει επικίνδυνες ενέργειες ή ενέργειες σε επίπεδο τμήματος μέσω βοηθού/μηνυμάτων με το προσωπικό του 4ψήφιο PIN ασφαλείας. Τα μέλη χωρίς PIN λαμβάνουν ένα τυχαία δημιουργημένο (ορατό στη σελίδα προφίλ τους). + + + Κάρτες Εξόδου και Αυτόματη Αποστολή + + + Λειτουργία αυτόματης επιλογής πόρων + + + Πώς επιλέγει το σύστημα μονάδες και προσωπικό για να καλύψει μια κάρτα εξόδου: βάσει της περιοχής ανταπόκρισης του σταθμού που περιέχει την κλήση (με μετάβαση στον επόμενο πλησιέστερο σταθμό σε περίπτωση έλλειψης) ή βάσει των πλησιέστερων διαθέσιμων πόρων. + + + Αυτόματη αποστολή προτεινόμενων πόρων + + + Όταν είναι ενεργό, οι κάρτες εξόδου που ταιριάζουν στέλνουν πόρους αμέσως με τη δημιουργία της κλήσης. Όταν είναι ανενεργό, οι προτάσεις προσυμπληρώνουν τη σελίδα Νέας Κλήσης για έλεγχο από τον διαβιβαστή. + + + Περίοδος ανάπαυσης (λεπτά) + + + Μετά την αποστολή μιας μονάδας ή ενός ατόμου, υποβαθμίστε την προτεραιότητά τους για τόσα λεπτά ώστε να μην αποστέλλονται οι ίδιοι πόροι διαδοχικά. Το 0 απενεργοποιεί τη λειτουργία. + + + Ελάχιστη στελέχωση μονάδας για αποστολή + + + Μονάδες με στελέχωση κάτω από αυτό το επίπεδο δεν επιλέγονται ακόμη και αν η κατάστασή τους ταιριάζει. Μονάδες χωρίς καθορισμένους ρόλους περνούν πάντα. + + + Ενεργοποίηση προτάσεων μετακίνησης / αναπλήρωσης + + + Μετά την επιλογή αποστολής, ελέγξτε τα ελάχιστα όρια κάλυψης σταθμών και προτείνετε μετακίνηση πόρων σε σταθμούς με έλλειψη. + + + Ρύθμιση Ανταπόκρισης Πλησιέστερης Μονάδας + + + Μέγιστη παλαιότητα τοποθεσίας μονάδας (δευτερόλεπτα) + + + Μέγιστη παλαιότητα τοποθεσίας προσωπικού (δευτερόλεπτα) + + + Μέγιστη ακτίνα ανταπόκρισης (μέτρα) + + + Συμπερίληψη παλαιών τοποθεσιών + + + Ταξινόμηση κατά εκτιμώμενο χρόνο οδήγησης + + + Επανακατάταξη των πλησιέστερων υποψηφίων με βάση τον χρόνο οδήγησης αντί για την ευθεία απόσταση. Χρησιμοποιεί τον πάροχο χαρτογράφησης και ενδέχεται να προσθέσει κόστος και καθυστέρηση. + + + Μέγεθος βραχείας λίστας εκτιμώμενου χρόνου άφιξης + + + Ελάχιστα Όρια Κάλυψης Σταθμών + + + Ορίστε τον ελάχιστο αριθμό κάθε τύπου μονάδας ή ρόλου προσωπικού που πρέπει να παραμένει διαθέσιμος σε έναν σταθμό. Όταν μια αποστολή θα μείωνε την κάλυψη κάτω από το ελάχιστο, το σύστημα προτείνει μετακινήσεις. + + + Σταθμός + + + Τύπος Μονάδας / Ρόλος + + + Ελάχιστοι Διαθέσιμοι + + + Ακτίνα (μέτρα, λειτουργία πλησιέστερης μονάδας) + + + Ενεργοποιημένο + + + Κάρτες Εξόδου + + + Προσχεδιασμένα πακέτα ανταπόκρισης που αντιστοιχίζονται σε κλήσεις βάσει προτεραιότητας και τύπου. Κάθε κάρτα ορίζει τους τύπους μονάδων και τους ρόλους προσωπικού που θα αποσταλούν ανά επίπεδο συναγερμού και ποιες καταστάσεις θεωρούνται διαθέσιμες. + + + Νέα Κάρτα Εξόδου + + + Επεξεργασία + + + Διαγραφή αυτής της κάρτας εξόδου; Οι κλήσεις που τη χρησιμοποιούν ήδη διατηρούν το ιστορικό αποστολής τους. + + + Όνομα + + + Περιγραφή + + + Απενεργοποιημένη + + + Γενικά + + + Προεπιλογή τμήματος + + + Μόνο χειροκίνητα (χωρίς αυτόματη επιλογή) + + + Προσυμπλήρωση για έλεγχο από τον διαβιβαστή + + + Αυτόματη αποστολή + + + Χωρίς όριο στελέχωσης + + + Σταθμός βάσης (χρησιμοποιείται όταν μια κλήση δεν έχει τοποθεσία) + + + Κανένας + + + Κανόνες Ενεργοποίησης + + + Μια κάρτα ταιριάζει με μια κλήση όταν ταιριάζει οποιοσδήποτε κανόνας ενεργοποίησης. Υπερισχύει η πιο συγκεκριμένη αντιστοίχιση (προτεραιότητα + τύπος υπερισχύει του τύπου, ο οποίος υπερισχύει της προτεραιότητας). + + + Τύπος κανόνα + + + Προτεραιότητα Κλήσης + + + Τύπος Κλήσης + + + Προτεραιότητα και Τύπος + + + Προσθήκη Κανόνα Ενεργοποίησης + + + Επίπεδα Συναγερμού + + + Οι απαιτήσεις κάθε επιπέδου συναγερμού είναι αθροιστικές: η κλιμάκωση στο επίπεδο 2 στέλνει τους πόρους του επιπέδου 2 επιπλέον όσων έχουν ήδη ανατεθεί. + + + Συναγερμός + + + Προαιρετικό όνομα, π.χ. Ενεργή Πυρκαγιά + + + Προσθήκη Επιπέδου Συναγερμού + + + Απαιτήσεις τύπων μονάδων + + + Απαιτήσεις ρόλων προσωπικού + + + Προσθήκη Τύπου Μονάδας + + + Προσθήκη Ρόλου + + + Καταστάσεις Επιλέξιμες για Αποστολή + + + Ποιες καταστάσεις μονάδων, καταστάσεις προσωπικού και επίπεδα στελέχωσης θεωρούνται διαθέσιμα για αυτή την κάρτα. Αφήστε μια ενότητα κενή για να χρησιμοποιηθούν οι τυπικοί κανόνες διαθεσιμότητας. + + + Επίπεδα στελέχωσης + + + Δοκιμή / Προσομοίωση + + + Δείτε τι θα έστελναν οι κάρτες εξόδου αυτού του τμήματος για μια προτεραιότητα, τύπο και τοποθεσία αυτή τη στιγμή — δεν αποστέλλεται τίποτα. + + + Εκτέλεση Δοκιμής + + + Η κάρτα εξόδου αποθηκεύτηκε. + + + Προτεραιότητα Κλήσης + + + Τύπος Κλήσης + + + Κάρτες Εξόδου + + + Όλοι οι χάρτες στο Resgrid και στις εφαρμογές ανοίγουν εδώ. Αφήστε και τα δύο πεδία κενά και θα τα υπολογίσουμε από τη διεύθυνση του τμήματος στην επόμενη αποθήκευση· συμπληρώστε τα για να ορίσετε ακριβές σημείο, το οποίο δεν αντικαθίσταται ποτέ. + + + Πεδία φόρμας νέας κλήσης + + + Επιλέξτε ποια ενσωματωμένα πεδία εμφανίζονται στη φόρμα και ποια πρέπει να συμπληρωθούν πριν η κλήση δημιουργηθεί και σταλεί. Όνομα, φύση, προτεραιότητα και τύπος εμφανίζονται πάντα και είναι πάντα υποχρεωτικά. + + + Πεδίο + + + Ορατό + + + Απαιτείται + + + Διεύθυνση + + + Συντεταγμένες GPS + + + what3words + + + Plus Code + + + Προορισμός + + + Εσωτερική θέση + + + Σημείωση + + + Κύρια επαφή + + + Στοιχεία επικοινωνίας αναφέροντος + + + Εξωτερικό αναγνωριστικό + + + Αναγνωριστικό συμβάντος + + + Αναφορά + + + Πρωτόκολλα + + + Συνδεδεμένη κλήση + + + Προγραμματισμένη αποστολή + + + Παραλήπτες αποστολής + + + Χρονόμετρα κατάστασης μονάδας + + + Επισημαίνει μια μονάδα στο Big Board όταν παραμένει πολύ ώρα σε μία κατάσταση — για παράδειγμα, απεσταλμένη για πάνω από τέσσερα λεπτά χωρίς να δηλώσει αναχώρηση. Αφήστε και τις δύο στήλες στο 0 για να μην παρακολουθείται τίποτα. + + + Κατάσταση + + + Προειδοποίηση μετά (λεπτά) + + + Συναγερμός μετά (λεπτά) + + + Απεσταλμένη + + + Μεταβαίνει + + + Καθοδόν + + + Στο σημείο + + + Αναμονή + + + Στον ασθενή + + + Μεταφορά + + + Στο νοσοκομείο + + + Επιστροφή + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.en.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.en.resx index e92b94e40..66e1ae550 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.en.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.en.resx @@ -972,4 +972,112 @@ Run Cards + + Every map in Resgrid and the mobile apps opens here. Leave both blank and we will work it out from the department address above the next time you save; fill them in to pin an exact spot, which is never overwritten. + + + New Call Form Fields + + + Choose which built-in fields appear on the new call form and which must be completed before a call can be created and sent to the field. Name, nature, priority and type are always shown and always required. + + + Field + + + Visible + + + Required + + + Address + + + GPS Coordinates + + + what3words + + + Plus Code + + + Destination + + + Indoor Location + + + Note + + + Primary Contact + + + Reporter Contact Info + + + External Call ID + + + Incident ID + + + Reference ID + + + Protocols + + + Linked Call + + + Scheduled Dispatch + + + Dispatch Recipients + + + Unit Status Timers + + + Highlight a unit on the Big Board when it has been sitting in a status too long — for example, dispatched for more than four minutes without reporting that it has departed. Leave both columns at 0 to time nothing. + + + Status + + + Warn after (minutes) + + + Alert after (minutes) + + + Dispatched + + + Responding + + + En Route + + + On Scene + + + Staging + + + At Patient + + + Transporting + + + At Hospital + + + Returning + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.es.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.es.resx index a457f975e..f9529cade 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.es.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.es.resx @@ -828,4 +828,112 @@ Tarjetas de despacho + + Todos los mapas de Resgrid y de las aplicaciones se abren aquí. Deja ambos campos vacíos y lo calcularemos a partir de la dirección del departamento al guardar; rellénalos para fijar un punto exacto, que nunca se sobrescribe. + + + Campos del formulario de aviso + + + Elige qué campos integrados aparecen en el formulario de nuevo aviso y cuáles deben completarse antes de crear el aviso y enviarlo al terreno. Nombre, naturaleza, prioridad y tipo se muestran siempre y son siempre obligatorios. + + + Campo + + + Visible + + + Obligatorio + + + Dirección + + + Coordenadas GPS + + + what3words + + + Código Plus + + + Destino + + + Ubicación interior + + + Nota + + + Contacto principal + + + Datos de contacto del informante + + + ID externo del aviso + + + ID del incidente + + + ID de referencia + + + Protocolos + + + Aviso vinculado + + + Despacho programado + + + Destinatarios del despacho + + + Temporizadores de estado de unidad + + + Resalta una unidad en el Big Board cuando lleva demasiado tiempo en un estado — por ejemplo, despachada más de cuatro minutos sin informar de que ha salido. Deja ambas columnas en 0 para no controlar nada. + + + Estado + + + Avisar tras (minutos) + + + Alertar tras (minutos) + + + Despachada + + + Respondiendo + + + En camino + + + En el lugar + + + En espera + + + Con el paciente + + + Transportando + + + En el hospital + + + Regresando + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.fr.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.fr.resx index 66e8b517e..2837d903e 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.fr.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.fr.resx @@ -893,4 +893,112 @@ Plans de départ + + Toutes les cartes de Resgrid et des applications s'ouvrent ici. Laissez les deux champs vides et nous les déduirons de l'adresse du service ci-dessus au prochain enregistrement ; remplissez-les pour fixer un point précis, qui ne sera jamais écrasé. + + + Champs du formulaire d'intervention + + + Choisissez les champs intégrés qui apparaissent sur le formulaire et ceux qui doivent être remplis avant qu'une intervention puisse être créée et envoyée sur le terrain. Nom, nature, priorité et type sont toujours affichés et toujours requis. + + + Champ + + + Visible + + + Requis + + + Adresse + + + Coordonnées GPS + + + what3words + + + Plus Code + + + Destination + + + Localisation intérieure + + + Note + + + Contact principal + + + Coordonnées du déclarant + + + ID externe de l'intervention + + + ID de l'incident + + + ID de référence + + + Protocoles + + + Intervention liée + + + Envoi programmé + + + Destinataires + + + Minuteurs de statut des unités + + + Met en évidence une unité sur le Big Board lorsqu'elle reste trop longtemps dans un statut — par exemple engagée depuis plus de quatre minutes sans avoir signalé son départ. Laissez les deux colonnes à 0 pour ne rien surveiller. + + + Statut + + + Avertir après (minutes) + + + Alerter après (minutes) + + + Engagée + + + En route + + + En chemin + + + Sur les lieux + + + En attente + + + Auprès du patient + + + Transport + + + À l'hôpital + + + Retour + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.it.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.it.resx index 3bd850612..07e2deb15 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.it.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.it.resx @@ -893,4 +893,112 @@ Schede di intervento + + Tutte le mappe di Resgrid e delle app si aprono qui. Lascia entrambi i campi vuoti e li ricaveremo dall'indirizzo del dipartimento al prossimo salvataggio; compilali per fissare un punto preciso, che non viene mai sovrascritto. + + + Campi del modulo di chiamata + + + Scegli quali campi predefiniti compaiono nel modulo di nuova chiamata e quali devono essere compilati prima che la chiamata possa essere creata e inviata sul campo. Nome, natura, priorità e tipo sono sempre visibili e sempre obbligatori. + + + Campo + + + Visibile + + + Obbligatorio + + + Indirizzo + + + Coordinate GPS + + + what3words + + + Plus Code + + + Destinazione + + + Posizione interna + + + Nota + + + Contatto principale + + + Contatti del segnalante + + + ID chiamata esterno + + + ID incidente + + + ID di riferimento + + + Protocolli + + + Chiamata collegata + + + Invio programmato + + + Destinatari invio + + + Timer di stato delle unità + + + Evidenzia un'unità sul Big Board quando resta troppo a lungo in uno stato — per esempio inviata da più di quattro minuti senza aver segnalato la partenza. Lascia entrambe le colonne a 0 per non monitorare nulla. + + + Stato + + + Avvisa dopo (minuti) + + + Allarme dopo (minuti) + + + Inviata + + + In risposta + + + In viaggio + + + Sul posto + + + In attesa + + + Dal paziente + + + In trasporto + + + All'ospedale + + + In rientro + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.pl.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.pl.resx index de702032e..941b65ac3 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.pl.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.pl.resx @@ -893,4 +893,112 @@ Karty dysponowania + + Wszystkie mapy w Resgrid i w aplikacjach otwierają się w tym miejscu. Pozostaw oba pola puste, a ustalimy je z adresu jednostki przy najbliższym zapisie; wypełnij je, aby wskazać dokładny punkt, który nigdy nie zostanie nadpisany. + + + Pola formularza nowego zdarzenia + + + Wybierz, które wbudowane pola pojawiają się w formularzu i które muszą zostać wypełnione, zanim zdarzenie będzie można utworzyć i wysłać w teren. Nazwa, charakter, priorytet i typ są zawsze widoczne i zawsze wymagane. + + + Pole + + + Widoczne + + + Wymagane + + + Adres + + + Współrzędne GPS + + + what3words + + + Plus Code + + + Cel + + + Lokalizacja wewnętrzna + + + Notatka + + + Główny kontakt + + + Dane kontaktowe zgłaszającego + + + Zewnętrzny identyfikator + + + Identyfikator zdarzenia + + + Identyfikator referencyjny + + + Protokoły + + + Powiązane zdarzenie + + + Zaplanowane zadysponowanie + + + Odbiorcy zadysponowania + + + Liczniki czasu statusu jednostki + + + Wyróżnia jednostkę na Big Board, gdy zbyt długo pozostaje w danym statusie — na przykład zadysponowana od ponad czterech minut bez zgłoszenia wyjazdu. Pozostaw obie kolumny na 0, aby niczego nie monitorować. + + + Status + + + Ostrzeż po (minutach) + + + Alarm po (minutach) + + + Zadysponowana + + + W drodze + + + W trasie + + + Na miejscu + + + W gotowości + + + Przy pacjencie + + + Transport + + + W szpitalu + + + Powrót + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.resx index 94fc699d2..b40abeb07 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.resx @@ -567,4 +567,112 @@ + + Every map in Resgrid and the mobile apps opens here. Leave both blank and we will work it out from the department address above the next time you save; fill them in to pin an exact spot, which is never overwritten. + + + New Call Form Fields + + + Choose which built-in fields appear on the new call form and which must be completed before a call can be created and sent to the field. Name, nature, priority and type are always shown and always required. + + + Field + + + Visible + + + Required + + + Address + + + GPS Coordinates + + + what3words + + + Plus Code + + + Destination + + + Indoor Location + + + Note + + + Primary Contact + + + Reporter Contact Info + + + External Call ID + + + Incident ID + + + Reference ID + + + Protocols + + + Linked Call + + + Scheduled Dispatch + + + Dispatch Recipients + + + Unit Status Timers + + + Highlight a unit on the Big Board when it has been sitting in a status too long — for example, dispatched for more than four minutes without reporting that it has departed. Leave both columns at 0 to time nothing. + + + Status + + + Warn after (minutes) + + + Alert after (minutes) + + + Dispatched + + + Responding + + + En Route + + + On Scene + + + Staging + + + At Patient + + + Transporting + + + At Hospital + + + Returning + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.sv.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.sv.resx index 9c6e8aad3..c76db586e 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.sv.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.sv.resx @@ -893,4 +893,112 @@ Larmplaner + + Alla kartor i Resgrid och apparna öppnas här. Lämna båda fälten tomma så räknar vi ut det från avdelningens adress nästa gång du sparar; fyll i dem för att låsa en exakt punkt, som aldrig skrivs över. + + + Fält i ärendeformuläret + + + Välj vilka inbyggda fält som visas i formuläret för nya ärenden och vilka som måste fyllas i innan ett ärende kan skapas och skickas ut. Namn, art, prioritet och typ visas alltid och krävs alltid. + + + Fält + + + Synligt + + + Obligatoriskt + + + Adress + + + GPS-koordinater + + + what3words + + + Plus Code + + + Destination + + + Inomhusplats + + + Anteckning + + + Primär kontakt + + + Anmälarens kontaktuppgifter + + + Externt ärende-ID + + + Händelse-ID + + + Referens-ID + + + Protokoll + + + Länkat ärende + + + Schemalagt larm + + + Larmmottagare + + + Tidsgränser för enhetsstatus + + + Markerar en enhet på Big Board när den stått för länge i en status — till exempel larmad i mer än fyra minuter utan att ha rapporterat avfärd. Lämna båda kolumnerna på 0 för att inte bevaka något. + + + Status + + + Varna efter (minuter) + + + Larma efter (minuter) + + + Larmad + + + Under utryckning + + + På väg + + + På plats + + + Uppställning + + + Hos patient + + + Transporterar + + + På sjukhus + + + Återvänder + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/Department.uk.resx b/Core/Resgrid.Localization/Areas/User/Department/Department.uk.resx index 8fb2361ef..8da57f8b3 100644 --- a/Core/Resgrid.Localization/Areas/User/Department/Department.uk.resx +++ b/Core/Resgrid.Localization/Areas/User/Department/Department.uk.resx @@ -893,4 +893,112 @@ Картки виїзду + + Усі карти в Resgrid та додатках відкриваються тут. Залиште обидва поля порожніми — і ми визначимо їх із адреси відділу під час наступного збереження; заповніть їх, щоб задати точну точку, яка ніколи не перезаписується. + + + Поля форми нового виклику + + + Оберіть, які вбудовані поля відображаються у формі та які мають бути заповнені, перш ніж виклик можна створити та надіслати. Назва, характер, пріоритет і тип завжди видимі та обов’язкові. + + + Поле + + + Видиме + + + Обов’язкове + + + Адреса + + + GPS-координати + + + what3words + + + Plus Code + + + Пункт призначення + + + Внутрішнє розташування + + + Примітка + + + Основний контакт + + + Контакти заявника + + + Зовнішній ідентифікатор + + + Ідентифікатор інциденту + + + Референсний ідентифікатор + + + Протоколи + + + Пов'язаний виклик + + + Заплановане висилання + + + Отримувачі висилання + + + Таймери статусу підрозділу + + + Підсвічує підрозділ на Big Board, коли він занадто довго залишається в одному статусі — наприклад, направлений понад чотири хвилини без повідомлення про виїзд. Залиште обидва стовпці на 0, щоб нічого не відстежувати. + + + Статус + + + Попередити через (хв) + + + Тривога через (хв) + + + Направлений + + + Реагує + + + У дорозі + + + На місці + + + Очікування + + + Біля пацієнта + + + Транспортування + + + У лікарні + + + Повернення + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Department/DepartmentTypes.el.resx b/Core/Resgrid.Localization/Areas/User/Department/DepartmentTypes.el.resx new file mode 100644 index 000000000..d55ea48e0 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Department/DepartmentTypes.el.resx @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη νέου τύπου κλήσης + + + Προτεραιότητες Κλήσεων + + + Ήχος Ειδοποίησης + + + Είναι Προεπιλογή + + + Έχετε ενεργοποιήσει προσαρμοσμένες Προτεραιότητες Κλήσεων. Για να επιστρέψετε στις προεπιλεγμένες, θα χρειαστεί να διαγράψετε όλες τις προσαρμοσμένες. Η επεξεργασία μιας υπάρχουσας προτεραιότητας κλήσης θα την ενημερώσει για όλες τις κλήσεις που τη χρησιμοποίησαν. + + + Χρησιμοποιείτε αυτή τη στιγμή τις Προεπιλεγμένες προτεραιότητες κλήσεων. Αν προσθέσετε οποιαδήποτε προσαρμοσμένη προτεραιότητα κλήσης, δεν θα μπορείτε πλέον να χρησιμοποιείτε τις προεπιλεγμένες προτεραιότητες κλήσεων (συμπεριλαμβανομένων των ήχων τους). Αν δεν ορίσετε προσαρμοσμένο ήχο για τις νέες σας προτεραιότητες κλήσεων, θα χρησιμοποιηθεί ο ήχος push και ο ήχος ειδοποίησης Υψηλής προτεραιότητας. Οι χρήστες με έκδοση v5 ή παλαιότερη της εφαρμογής Responder και v2 ή παλαιότερη της εφαρμογής Unit δεν υποστηρίζουν προσαρμοσμένες προτεραιότητες κλήσεων. Αν έχετε χρήστες σε αυτές τις εκδόσεις, συνιστούμε να μην δημιουργήσετε προσαρμοσμένες προτεραιότητες κλήσεων. + + + Εικονίδιο Χάρτη Τύπου Κλήσης + + + Εικονίδιο Χάρτη + + + Όνομα Τύπου Κλήσης + + + Όνομα Τύπου Κλήσης + + + Όνομα του Τύπου + + + Τύποι Κλήσεων + + + Όνομα Πιστοποίησης + + + Όνομα της Πιστοποίησης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον Τύπο Πιστοποίησης. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο + + + Τύποι Πιστοποιήσεων + + + Όνομα Τύπου + + + Προεπιλεγμένες Ενέργειες Μονάδας + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον τύπο κλήσης. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο κλήσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την Κατηγορία Εγγράφων. Είστε βέβαιοι ότι θέλετε να διαγράψετε την κατηγορία + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον τύπο σημείωσης. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο σημείωσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον τύπο μονάδας. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο μονάδας + + + Τύποι Τμήματος + + + Όνομα Κατηγορίας + + + Όνομα της νέας Κατηγορίας Εγγράφων (Τύπου) + + + Κατηγορίες Εγγράφων + + + Επεξεργασία Προτεραιότητας Κλήσης + + + Επεξεργασία Τύπου Κλήσης + + + Επεξεργασία Τύπου Μονάδας + + + Διαχείριση Σειράς Λίστας + + + Προσθήκη νέας προτεραιότητας κλήσης + + + Νέα Προτεραιότητα Κλήσης + + + Νέος Τύπος Κλήσης + + + Νέος Τύπος Πιστοποίησης + + + Νέα Κατηγορία Εγγράφων + + + Νέα Κατηγορία Εγγράφων + + + Νέος Τύπος Σημείωσης + + + Νέος Τύπος Μονάδας + + + Όνομα του Τύπου Σημείωσης (Κατηγορίας) + + + Τύποι Σημειώσεων + + + Τύπος Σημείωσης + + + Χρώμα Προτεραιότητας + + + Είναι Προεπιλογή + + + Όνομα Προτεραιότητας + + + Όνομα της Προτεραιότητας + + + Αποστολή σε Προσωπικό + + + Σειρά Ταξινόμησης + + + Ήχος Ειδοποίησης + + + Αποστολή σε Μονάδες + + + Ενέργειες (Προσαρμοσμένες Καταστάσεις Μονάδας) + + + Εικονίδιο Χάρτη + + + Όνομα Τύπου Μονάδας + + + Όνομα του Τύπου Μονάδας + + + Τύποι Μονάδων + + + Τύποι Σημειώσεων Επαφών + + + Προσθήκη Νέου Τύπου Σημείωσης Επαφής + + + Νέος Τύπος Σημείωσης Επαφής + + + Όνομα Τύπου Σημείωσης Επαφής + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε τον τύπο σημείωσης επαφής με το όνομα; + + + Όνομα Τύπου Σημείωσης Επαφής + + + Το Όνομα του Τύπου Σημείωσης Επαφής + + + Χρώμα Τύπου Σημείωσης Επαφής + + + Επεξεργασία Τύπου Σημείωσης Επαφής + + + Νέος Τύπος Σημείωσης Επαφής + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Dispatch/Call.el.resx b/Core/Resgrid.Localization/Areas/User/Dispatch/Call.el.resx new file mode 100644 index 000000000..bb9914bd6 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Dispatch/Call.el.resx @@ -0,0 +1,751 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ενεργές Κλήσεις + + + Προσθήκη ενεργής κλήσης + + + Προσθήκη Αρχειοθετημένης Κλήσης + + + Προσθήκη Αρχειοθετημένης Κλήσης + + + Προσθήκη αρχειοθετημένης κλήσης + + + Προστέθηκε Από + + + Προσθήκη Αρχείου + + + Εισαγάγετε όνομα συνημμένου + + + Προσθήκη Εικόνας + + + Πρόσθετες Επαφές + + + Προσθήκη Συνδεδεμένης Κλήσης + + + Προσθήκη Σημείωσης + + + Εισαγάγετε σημείωση εδώ... + + + Σημειώσεις Ειδοποίησης για την Επαφή + + + Αρχειοθετημένες Κλήσεις + + + Συνημμένες Εικόνες + + + Τύπος Επαφής Κλήσης + + + Έγγραφα Κλήσης + + + Εξαγωγή Κλήσης Resgrid + + + Φόρμα Κλήσης + + + Φόρμα Κλήσης + + + Αναγνωριστικό Κλήσης + + + Αναγνωριστικό CAD ή Αναγνωριστικό Συστήματος Εισερχόμενης Κλήσης + + + Αναγνωριστικό + + + Σύνδεσμοι Κλήσης + + + Φύση της Κλήσης + + + Μια περιγραφή της φύσης της κλήσης + + + Σημείωση Κλήσης Προστέθηκε Από + + + Σημείωση Κλήσης Προστέθηκε Στις + + + Σημειώσεις Κλήσης + + + Κείμενο Σημείωσης Κλήσης + + + Αριθμός Κλήσης + + + Αριθμός + + + Κλήσεις Συνδεδεμένες Από (Γονικές Κλήσεις) + + + Κλήσεις Συνδεδεμένες Προς (Θυγατρικές Κλήσεις) + + + Πρότυπα Κλήσεων + + + Κλείσιμο Κλήσης + + + Κλείσιμο Κλήσης + + + Έκλεισε Από + + + Έκλεισε Στις + + + Σημειώσεις Ολοκλήρωσης + + + Σημειώσεις σχετικά με το κλείσιμο της κλήσης ή τον λόγο κλεισίματος + + + Τύπος Ολοκλήρωσης + + + Επαφές + + + Δημιουργία και Αποστολή Κλήσης + + + Διαγραφή Κλήσης + + + Διαγραφή Κλήσης + + + Λόγος Διαγραφής + + + Ο λόγος διαγραφής της κλήσης + + + Ήχος Αποστολής + + + Απεσταλμένο Προσωπικό + + + Απεσταλμένες Μονάδες + + + Αποστολή + + + Οδηγίες Οδήγησης από + + + Αυτή είναι μια μη πιστοποιημένη σελίδα για την προβολή αυτής της κλήσης μόνο. Δεν είστε συνδεδεμένοι στο Resgrid. Μην μοιραστείτε αυτή τη διεύθυνση URL. + + + Όνομα Αρχείου + + + Τύπος Αρχείου + + + Εύρεση Διεύθυνσης + + + Εύρεση Τοποθεσίας w3w + + + Αναφορά Σημείωσης Κλήσης για Εποπτεία + + + Αναφορά Εικόνας Κλήσης για Εποπτεία + + + Επισήμανση Αρχείου Κλήσης + + + Εικόνα Προστέθηκε Από + + + Χρονική Σήμανση Εικόνας + + + Αρχείο Προστέθηκε Από + + + Χρονική Σήμανση Αρχείου + + + Αναφορά Εικόνας + + + Επισήμανση Αρχείου + + + Λόγος Αναφοράς + + + Λόγος αναφοράς + + + GPS + + + Αναγνωριστικό Περιστατικού + + + Κύριο ή Γονικό Εξωτερικό Αναγνωριστικό Περιστατικού + + + Έχει Επισημανθεί + + + Μάθετε Περισσότερα. + + + Συνδεδεμένες Κλήσεις + + + Τοποθεσία + + + Πλήρης Διεύθυνση της κλήσης (Οδός, Πόλη, Νομός, Ταχυδρομικός Κώδικας) + + + Καταγράφηκε Από + + + Καταγράφηκε Στις + + + Σελίδα Χάρτη + + + Σελίδα Χαρτοβιβλίου + + + Όνομα + + + Όνομα της Κλήσης + + + Φόρμα Νέας Κλήσης + + + Νέα Κλήση + + + Χωρίς Συνημμένα + + + Δεν έχουν επισυναφθεί αρχεία σε αυτή την Κλήση + + + Δεν Έκλεισε + + + Σημείωση + + + Σημειώσεις + + + Σημειώσεις για την κλήση + + + Δεν Δόθηκε + + + Χωρίς Τύπο + + + Δραστηριότητα Προσωπικού + + + Προσωπικό + + + Συμβάντα Προσωπικού + + + Κύρια Επαφή + + + Προτεραιότητα + + + Απαντήστε στις Ερωτήσεις Πρωτοκόλλου + + + Πρωτόκολλα + + + Κείμενο Πρωτοκόλλου + + + Αν επιλεγεί, οι οντότητες που αφαιρέθηκαν από τη λίστα αποστολής θα λάβουν ειδοποίηση ακύρωσης για αυτή την κλήση. + + + Ειδοποίηση Ακυρωμένων + + + Αν θέλετε να αναμεταδώσετε αυτή την κλήση, επιλέξτε αυτό το πλαίσιο. Αυτό θα στείλει την κλήση σε όλους τους επιλεγμένους χρήστες, ανεξάρτητα από το αν συμπεριλήφθηκαν στην πρώτη αποστολή. + + + Επαναποστολή + + + Αναγνωριστικό Αναφοράς + + + Αριθμός Συνεργάτη ή Αναφοράς + + + Επαναδημιουργία Αριθμών Κλήσεων + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτή η διαδικασία θα ενημερώσει ΟΛΟΥΣ τους αριθμούς κλήσεων Resgrid (π.χ. 16-55) βάσει ημερομηνίας. Αυτή η διαδικασία δεν είναι αναστρέψιμη. + + + Επανάνοιγμα Κλήσης + + + Στοιχεία Επικοινωνίας Αναφέροντος + + + Τηλέφωνο ή διεύθυνση email του Αναφέροντος + + + Όνομα Αναφέροντος + + + Όνομα του Ατόμου που Αναφέρει την κλήση + + + Επιλογή Κλήσης + + + Σημείωση για τον Σύνδεσμο (Προαιρετικό) + + + Επιλογή Προτύπου Κλήσης + + + Επιλογή Κλήσης προς Σύνδεση + + + Αποστολή Ειδοποίησης + + + Ορισμός Προτύπου + + + Κατάσταση + + + Πρότυπο + + + Χρονική Σήμανση + + + Τύπος + + + Δραστηριότητα Μονάδας + + + Συμβάντα Μονάδας + + + Μονάδες + + + Ενημέρωση Κλήσης + + + Προβολή Κλήσης + + + Προβολή Κειμένου + + + Αυτή είναι μια διεύθυνση what3words. + + + Χρονόμετρα Παρουσίας + + + Ενεργοποίηση Χρονομέτρων Παρουσίας + + + Χρονόμετρα Παρουσίας + + + Στόχος + + + Τύπος + + + Τελευταία Παρουσία + + + Υπολειπόμενος Χρόνος + + + Κατάσταση + + + Ενέργειες + + + Δήλωση Παρουσίας + + + Εμφάνιση Ιστορικού Παρουσιών + + + Χρονική Σήμανση + + + Ποιος + + + Τύπος + + + Μονάδα + + + Τοποθεσία + + + Σημείωση + + + Διαμόρφωση Χρονομέτρων Παρουσίας + + + Αρχείο Παρουσιών + + + Διάρκεια (λεπ.) + + + Όριο Προειδοποίησης (λεπ.) + + + Πηγή + + + Ροές Βίντεο + + + Προσθήκη Ροής Βίντεο + + + Επεξεργασία Ροής Βίντεο + + + Διαγραφή Ροής Βίντεο + + + Όνομα + + + Διεύθυνση URL + + + Τύπος Ροής + + + Μορφή Ροής + + + Περιγραφή + + + Κατάσταση + + + Τοποθεσία + + + Προστέθηκε Από + + + Προστέθηκε Στις + + + Σειρά Ταξινόμησης + + + Ενεργή + + + Ανενεργή + + + Σφάλμα + + + Drone + + + Σταθερή Κάμερα + + + Κάμερα Σώματος + + + Κάμερα Κυκλοφορίας + + + Κάμερα Καιρού + + + Δορυφορική Ροή + + + Διαδικτυακή Κάμερα + + + Άλλη + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη ροή βίντεο; + + + Δεν έχουν προστεθεί ροές βίντεο σε αυτή την κλήση. + + Πρόσθετα + Όλη η ομάδα {0} Απεστάλη + Είστε βέβαιοι; + Η Κλήση Ενημερώθηκε + Αυτή η κλήση ενημερώθηκε· ανανεώστε τη σελίδα για να δείτε τις τελευταίες πληροφορίες. + Κλήσεις για το Έτος + Αποτυχία δήλωσης παρουσίας. Δοκιμάστε ξανά. + ΕΝΤΑΞΕΙ + ΕΚΠΡΟΘΕΣΜΟ + Προσαρμοσμένα Πεδία + Προορισμός + Επιλέξτε σημείο ενδιαφέροντος προορισμού, όπως νοσοκομείο, καταφύγιο, κέντρο μεταφοράς ή χώρο αναμονής. + Συνομιλία Αποστολής + Ώρα Αποστολής + Εισαγάγετε μήνυμα εδώ... + Εκτιμώμενη Ώρα Άφιξης + Μετάβαση στην Αποστολή + Μετάβαση στην Αρχική + Ομάδα + (Επιλέχθηκε Ομάδα) + Τοποθεσία Εσωτερικού Χώρου + Εισαγάγετε έγκυρες αριθμητικές τιμές γεωγραφικού πλάτους και μήκους. + Γεωγραφικό Πλάτος + Πλάτος + Γεωγραφικό Μήκος + Μήκος + Ειδοποιήσεις Σημειώσεων + Συνδεδεμένοι Χρήστες + Πλήθος Προσωπικού + Προβολή Εκτύπωσης (Εξαγωγής) + Κύρια + Κείμενο Πρωτοκόλλου για {0} + Ερωτήσεις για {0} + Αφαίρεση αυτού του συνδέσμου κλήσης + Το Αίτημα Στάλθηκε! + Είστε βέβαιοι ότι θέλετε να ανοίξετε ξανά αυτή την κλήση; Αυτό θα διαγράψει όλα τα σχετικά δεδομένα κλεισίματος, συμπεριλαμβανομένου του ποιος έκλεισε την κλήση, πότε έκλεισε, της κατάστασης κλεισίματος και τυχόν σημειώσεων κλεισίματος. + Το αίτημά σας για επανάνοιγμα της κλήσης βρίσκεται σε επεξεργασία. Ελέγξτε τη σελίδα πίνακα Ανοιχτών Κλήσεων για να δείτε την κλήση. + Διαδρομή από {0} + Αναζήτηση σημείων ενδιαφέροντος προορισμού... + Αναζήτηση τοποθεσιών εσωτερικού χώρου... + Ορισμός Πινέζας στον Χάρτη + Στελέχωση + Χρήστες/Μονάδες Εκτός Ομάδας + Το What3Words δεν μπόρεσε να βρει τοποθεσία για αυτές τις λέξεις. Βεβαιωθείτε ότι είναι 3 λέξεις χωρισμένες με τελείες. + what.three.words + Απάντηση σε Ερωτήσεις + Χαμηλή + Μεσαία + Υψηλή + Έκτακτης Ανάγκης + Ακυρώθηκε + Τύπος Μονάδας + Έκθεση σε Επικίνδυνα Υλικά + Εναλλαγή Τομέα + Αποκατάσταση + Αρχείο + Ανενεργή + Επιλέξτε έγκυρο σημείο ενδιαφέροντος προορισμού. + αποσυνδέθηκε. + Πρέπει να επιλέξετε αρχείο για επισύναψη. + Ο τύπος εγγράφου ({0}) δεν μπορεί να εισαχθεί. + Το αρχείο είναι πολύ μεγάλο· πρέπει να είναι μικρότερο από 10MB. + Χωρίς Τοποθεσία + Γενική Κλήση + Επιλογή Επαφής + Χωρίς Προορισμό + Σημεία Ενδιαφέροντος + Παράκαμψη + Απόσταση + Διάρκεια + Βήμα + χλμ + λεπ. + + Προγραμματισμός Αποστολής + + + Επιλέξτε μελλοντική ημερομηνία και ώρα... + + + Προαιρετικά ορίστε μελλοντική ημερομηνία/ώρα για καθυστέρηση της αποστολής. Πρέπει να είναι τουλάχιστον 15 λεπτά στο μέλλον. + + + Η προγραμματισμένη αποστολή πρέπει να είναι τουλάχιστον 15 λεπτά στο μέλλον. + + + Προγραμματισμένη Ημερομηνία Αποστολής + + diff --git a/Core/Resgrid.Localization/Areas/User/Dispatch/Dashboard.el.resx b/Core/Resgrid.Localization/Areas/User/Dispatch/Dashboard.el.resx new file mode 100644 index 000000000..de9ee9c10 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Dispatch/Dashboard.el.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ενεργές Κλήσεις + + + Αρχειοθετημένες Κλήσεις + + + Νέα Κλήση + + + Προσθήκη νέας κλήσης + + + Καταστάσεις Μονάδων + + + Διαδρομές + + + Προγραμματισμένες Κλήσεις + + + Προγραμματισμένες Κλήσεις + + diff --git a/Core/Resgrid.Localization/Areas/User/Documents/Documents.el.resx b/Core/Resgrid.Localization/Areas/User/Documents/Documents.el.resx new file mode 100644 index 000000000..154b55b3c --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Documents/Documents.el.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Μόνο Διαχειριστές + + + Κατηγορία + + + Διαγραφή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτό το έγγραφο. Είστε βέβαιοι ότι θέλετε να διαγράψετε το έγγραφο + + + Επεξεργασία Εγγράφου + + + Όλοι + + + Εικόνες + + + Νέο Έγγραφο + + + Όνομα του Εγγράφου + + + Παρουσιάσεις + + + Υπολογιστικά Φύλλα + + + Μεταφόρτωση + + + Δεν επιτρέπονται όλοι οι τύποι αρχείων! Το αρχείο πρέπει να είναι μικρότερο από 10MB και να έχει μία από αυτές τις επεκτάσεις (.png, .jpg, .jpeg, .gif, .pdf, .doc, .docx, .txt, .ppt, .pptx, .pps, .ppsx, .odt, .xls, .xlsx, .mp4, .mp3) + + + Ορατό Από + + + Προβολή Εγγράφου + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Forms/Forms.el.resx b/Core/Resgrid.Localization/Areas/User/Forms/Forms.el.resx new file mode 100644 index 000000000..f65685a18 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Forms/Forms.el.resx @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Αυτοματισμού + + + Προσθήκη Φόρμας + + + Ενεργή + + + Θα χρειαστεί να αντιστοιχίσετε τα ονόματα πεδίων που ορίστηκαν παραπάνω με τα πεδία στα οποία θέλετε να ενεργήσετε. Αφήστε την τιμή του πεδίου κενή για οποιαδήποτε τιμή. + + + Αυτοματισμοί Κλήσεων + + + Τιμή Κλήσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή τη φόρμα. Είστε βέβαιοι ότι θέλετε να διαγράψετε τη φόρμα + + + Απενεργοποίηση + + + Ενεργοποίηση + + + Όνομα Πεδίου + + + Τιμή Πεδίου + + + Σχεδιαστής Φόρμας + + + Φόρμα + + + Όνομα Φόρμας + + + Φόρμες + + + Τύπος Φόρμας + + + Φόρμα Νέας Κλήσης + + + Νέα Φόρμα + + + Νέα Φόρμα + + + Νέα Φόρμα + + + Μπορείτε να έχετε μόνο 1 ενεργή φόρμα τύπου Νέας Κλήσης κάθε φορά. + + + Ενέργεια + + + Φόρμες + + + Προβολή + + + Προβολή Φόρμας + + + Προβολή Φόρμας + + diff --git a/Core/Resgrid.Localization/Areas/User/Groups/Groups.el.resx b/Core/Resgrid.Localization/Areas/User/Groups/Groups.el.resx new file mode 100644 index 000000000..3efc5fac1 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Groups/Groups.el.resx @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + Ομάδες Τμήματος + + + Ομάδες + + + Προσθήκη Νέας Ομάδας + + + Προσθήκη νέας Ομάδας + + + Όνομα Ομάδας + + + Γονική + + + Email Αποστολής + + + Email Μηνυμάτων + + + Μέλη + + + Οργανωτική + + + Σταθμός + + + Καμία + + + Γεωφράχτης + + + + + Διαγραφή Ομάδας + + + Διαγραφή Ομάδας + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την ομάδα; + + + Αν επιλέξετε να διαγράψετε αυτή την ομάδα, οι πληροφορίες της ομάδας, για παράδειγμα οι διευθύνσεις αποστολής και μηνυμάτων, θα αφαιρεθούν μαζί με την ομάδα και δεν θα μπορούν να χρησιμοποιηθούν ξανά. + + + Αυτή η ομάδα έχει ενεργούς χρήστες. Πρέπει να μετακινήσετε ή να διαγράψετε τους χρήστες για να μπορέσετε να διαγράψετε την ομάδα. + + + Αυτή η ομάδα έχει θυγατρικές ομάδες. Θα χρειαστεί να μετακινήσετε ή να διαγράψετε τη θυγατρική ομάδα πριν διαγράψετε την ομάδα. + + + Αυτή η ομάδα έχει ανατεθειμένες μονάδες. Θα χρειαστεί να μετακινήσετε τη μονάδα σε άλλη Ομάδα Σταθμού ή να τη διαγράψετε για να μπορέσετε να διαγράψετε την ομάδα. + + + Αυτή η ομάδα έχει ανατεθειμένες ομάδες βαρδιών. Θα χρειαστεί να αφαιρέσετε αυτές τις ομάδες βαρδιών, δηλαδή τις Βάρδιες που έχουν ανατεθεί σε Σταθμό\Ομάδα, για να μπορέσετε να διαγράψετε την ομάδα. + + + Κάντε κλικ στο παρακάτω πλαίσιο για να διαγράψετε αυτή την ομάδα. + + + Αν επιλέξετε να διαγράψετε αυτή την ομάδα, οι παρακάτω πληροφορίες θα διαγραφούν οριστικά. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. + + + Δεδομένα που Διαγράφονται + + + Δεδομένα Ομάδας + + + Συμμετοχές Προσωπικού σε Ομάδα + + + Συμμετοχές Μονάδων σε Ομάδα + + + Απόθεμα Ομάδας + + + Δεδομένα Βαρδιών Ομάδας + + + Επιβεβαίωση Διαγραφής; + + + Διαγραφή Ομάδας + + + + + Νέα Ομάδα + + + Νέα Ομάδα + + + Τύπος ομάδας + + + Οργανωτική Ομάδα + + + Ομάδα Σταθμού + + + Γονική Ομάδα + + + Διεύθυνση Σταθμού + + + Οι ομάδες σταθμού απαιτούν φυσική τοποθεσία (καθώς το προσωπικό και οι μονάδες μπορούν να ανταποκρίνονται σε αυτές). Εισαγάγετε Διεύθυνση ή Συντεταγμένες GPS ή διεύθυνση what3words. + + + Οδός + + + Πόλη + + + Νομός/Επαρχία + + + Ταχυδρομικός Κώδικας + + + Χώρα + + + Ή + + + Συντεταγμένες GPS + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Διεύθυνση what3words + + + Διεύθυνση w3w (π.χ. humble.echo.sticky) + + + Εκτυπωτής PrintNode + + + Το PrintNode είναι μια υπηρεσία που μας επιτρέπει να στέλνουμε εργασίες εκτύπωσης σε τοπικό υπολογιστή ή διακομιστή στο δίκτυό σας, σε συγκεκριμένο εκτυπωτή. Πρέπει να έχετε ενεργό λογαριασμό PrintNode και το λογισμικό του ρυθμισμένο με τον εκτυπωτή ενεργό. + + + Επιλογή Εκτυπωτή + + + Οι χρήστες μπορούν να ανήκουν μόνο σε μία ομάδα κάθε φορά. Αν δεν βλέπετε τον χρήστη σας στη λίστα, ανήκει ήδη σε ομάδα. + + + Διαχειριστές Ομάδας + + + Χρήστες Ομάδας + + + Αποθήκευση Ομάδας + + + Κλειδί API: + + + Λήψη Εκτυπωτών + + + Εκτυπωτής + + + Κλείσιμο + + + + + Επεξεργασία Ομάδας + + + Επεξεργασία Ομάδας + + + Εκτύπωση Κλήσεων σε Εκτυπωτή + + + Η ενεργοποίηση αυτής της επιλογής θα εκτυπώνει μια κλήση στον επιλεγμένο Εκτυπωτή όταν μια μονάδα ή άτομο από αυτή την ομάδα συνδέεται (αποστέλλεται) σε μια κλήση + + + + + Γεωφράχτης Σταθμού + + + Γεωφράχτης Σταθμού + + + Γεωφράχτης Σταθμού + + + Παρακάτω μπορείτε να δημιουργήσετε γεωφράχτη για τον σταθμό σας. Τα πεδία με μπλε πλάγια γράμματα είναι υποχρεωτικά. Οι γεωφράχτες σάς επιτρέπουν να δημιουργήσετε ένα όριο στον χάρτη που αποτελεί την περιοχή ανταπόκρισης αυτού του σταθμού. Θα χρησιμοποιηθεί για την αυτόματη δρομολόγηση κλήσεων και τον καθορισμό ανταποκρίσεων. Για να δημιουργήσετε τον γεωφράχτη, κάντε πρώτα κλικ στον χάρτη για να ορίσετε το σημείο έναρξης και μετά ξανά κλικ για να σχεδιάσετε γραμμή· συνεχίστε να σχεδιάζετε γραμμές μέχρι να περικλείσετε την περιοχή ανταπόκρισης. + + + Χρώμα Περιοχής + + + Αποθήκευση Γεωφράχτη + + + Εκκαθάριση Γεωφράχτη + + diff --git a/Core/Resgrid.Localization/Areas/User/Home/EditProfile.el.resx b/Core/Resgrid.Localization/Areas/User/Home/EditProfile.el.resx new file mode 100644 index 000000000..643339a20 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Home/EditProfile.el.resx @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Πληροφορίες Λογαριασμού + + + Διεύθυνση + + + Φυσική Διεύθυνση Οδού + + + Email + + + Επιλογές Κλήσεων + + + Push + + + SMS + + + Πιστοποιήσεις + + + Αλλαγή Κωδικού Πρόσβασης Χρήστη + + + Πόλη + + + Φυσική Πόλη + + + Αν αλλάζετε τον κωδικό πρόσβασής σας, πρέπει να εισαγάγετε ξανά τον ΝΕΟ κωδικό εδώ + + + Επιβεβαίωση Κωδικού Πρόσβασης + + + Χώρα + + + Αν αλλάζετε τον κωδικό πρόσβασής σας, πρέπει να εισαγάγετε εδώ τον τρέχοντα κωδικό (όχι τον νέο) + + + Τρέχων Κωδικός Πρόσβασης + + + Διαγραφή του Λογαριασμού Σας + + + Διαγράψτε τον δικό σας λογαριασμό Resgrid σε όλα τα τμήματα στα οποία είστε μέλος + + + Διαχειριστής Τμήματος + + + Ρυθμίσεις Τμήματος + + + Απενεργοποίηση Χρήστη + + + Επεξεργασία + + + Οι διευθύνσεις email πρέπει να είναι μοναδικές + + + Διεύθυνση Email + + + Διεύθυνση Email + + + Υπήρξαν σφάλματα στο παρακάτω προφίλ και δεν ήταν δυνατή η αποθήκευσή του. Διορθώστε τα παρακάτω σφάλματα και δοκιμάστε να αποθηκεύσετε ξανά. + + + Όνομα + + + Όνομα + + + Ομάδα + + + Απόκρυψη Χρήστη + + + Αριθμός Οικίας + + + Τηλέφωνο Οικίας + + + Αριθμός Ταυτότητας + + + Προσαρμοσμένος Αριθμός Ταυτοποίησης + + + Είναι Διαχειριστής Ομάδας; + + + Σημείωση: Η πολυγλωσσική υποστήριξη στο Resgrid δεν είναι πλήρης. Ορισμένα τμήματα του ιστότοπου θα εξακολουθούν να είναι στα Αγγλικά και εργαζόμαστε για τη μετάφραση όλων. Οι υπηρεσίες υποστήριξής μας (Helpdesk, Github, Discord κ.λπ.) και η τεκμηρίωση είναι διαθέσιμες μόνο στα Αγγλικά. + + + Επώνυμο + + + Επώνυμο + + + Διεύθυνση + + + Ταχυδρομική Διεύθυνση Οδού + + + Πόλη + + + Ταχυδρομική Πόλη + + + Χώρα + + + Ταχυδρομικός Κώδικας + + + Ταχυδρομικός Κώδικας Αλληλογραφίας + + + Νομός + + + Νομός\Επαρχία Αλληλογραφίας + + + Email + + + Επιλογές Μηνυμάτων + + + Push + + + SMS + + + Πάροχος Κινητής + + + + + + Αριθμός Κινητού + + + Αριθμός Κινητού Τηλεφώνου + + + Αφήστε το κενό αν δεν θέλετε να αλλάξετε τον κωδικό πρόσβασής σας + + + Νέος Κωδικός Πρόσβασης + + + Να Μην Λαμβάνω Ενημερωτικά Δελτία + + + Αφήστε το κενό αν δεν θέλετε να αλλάξετε το Όνομα Χρήστη σας (δηλαδή να κρατήσετε το τρέχον όνομα με το οποίο συνδέεστε) + + + Νέο Όνομα Χρήστη + + + Κανονικός Χρήστης + + + Email + + + Επιλογές Ειδοποιήσεων + + + Ενεργοποίηση Σύγχρονων Ήχων Εφαρμογής + + + Χρησιμοποιήστε σύγχρονους ήχους ειδοποιήσεων αντί για τους παλαιούς. Αν το τμήμα σας ενεργοποιήσει τους σύγχρονους ήχους, χρησιμοποιούνται ανεξάρτητα από αυτή τη ρύθμιση. + + + Άλλες Επιλογές Ειδοποιήσεων + + + Push + + + Φυσική Διεύθυνση + + + Ταχυδρομικός Κώδικας + + + Φυσικός Ταχυδρομικός Κώδικας + + + Προφίλ + + + Παράδοση Αναφορών + + + Ρόλοι + + + Ίδια με τη Φυσική; + + + οι αλλαγές φωτογραφίας μπορεί να χρειαστούν έως και 24 ώρες + + + κάντε κλικ εδώ για να ορίσετε φωτογραφία + + + Προγράμματα Στελέχωσης + + + Νομός + + + Φυσικός Νομός\Επαρχία + + + Κλήση Οικίας + + + Κλήση Κινητού + + + Ενεργοποιημένο + + + Επιλογές Τηλεφώνου (Φωνής) + + + Λανθασμένο Πακέτο + + + Το τρέχον πακέτο του τμήματός σας δεν υποστηρίζει Τηλεφωνική Ειδοποίηση με φωνή. Αναβαθμίστε το πακέτο σας σε Ultimate ή ανώτερο για να αποκτήσετε αυτή τη δυνατότητα. + + + Ζώνη Ώρας + + + Επεξεργασία Προφίλ + + + Τύπος Χρήστη + + + Η διεύθυνση email σας δεν έχει επαληθευτεί. Επαληθεύστε την για να διασφαλίσετε ότι λαμβάνετε αποστολές και ειδοποιήσεις. + + + Ο αριθμός κινητού σας δεν έχει επαληθευτεί. Επαληθεύστε τον για να διασφαλίσετε ότι λαμβάνετε αποστολές και ειδοποιήσεις SMS. + + + Ο αριθμός οικίας σας δεν έχει επαληθευτεί. Επαληθεύστε τον για να διασφαλίσετε ότι λαμβάνετε αποστολές με φωνητική κλήση. + + + Συνιστούμε να επαληθεύσετε τη διεύθυνση email σας για να διασφαλιστεί η αδιάλειπτη παράδοση αποστολών και ειδοποιήσεων. + + + Συνιστούμε να επαληθεύσετε τον αριθμό κινητού σας για να διασφαλιστεί η αδιάλειπτη παράδοση αποστολών και ειδοποιήσεων SMS. + + + Συνιστούμε να επαληθεύσετε τον αριθμό οικίας σας για να διασφαλιστεί η αδιάλειπτη παράδοση αποστολών με φωνητική κλήση. + + + Επαλήθευση + + + Εισαγάγετε κωδικό επαλήθευσης + + + Στάλθηκε κωδικός επαλήθευσης. + + + Πραγματοποιείται κλήση επαλήθευσης. Απαντήστε και σημειώστε τον κωδικό που θα σας υπαγορευτεί. + + + Η επαλήθευση ολοκληρώθηκε με επιτυχία. + + + Η επαλήθευση απέτυχε. Ελέγξτε τον κωδικό και δοκιμάστε ξανά. + + + Πάρα πολλές προσπάθειες επαλήθευσης. Δοκιμάστε ξανά αργότερα. + + + Η επιλεγμένη μέθοδος επικοινωνίας δεν έχει διαμορφωθεί. + + + Η επιλεγμένη μέθοδος επικοινωνίας δεν είναι έγκυρη για παράδοση επαλήθευσης. + + + Δεν ήταν δυνατή η παράδοση του κωδικού επαλήθευσης. Δοκιμάστε ξανά. + + + + Απαιτήσεις Κωδικού Πρόσβασης + Τουλάχιστον {0} χαρακτήρες + Τουλάχιστον ένα ψηφίο (αριθμός) + Τουλάχιστον ένα κεφαλαίο και ένα πεζό γράμμα + + Όταν είναι ενεργοποιημένο, οι επικίνδυνες ενέργειες μέσω βοηθού/μηνυμάτων ζητούν το PIN σας ως επιπλέον βήμα ασφαλείας. Αν το ενεργοποιήσετε χωρίς PIN, δημιουργείται τυχαίο PIN για εσάς. + + + Απαίτηση PIN Ασφαλείας + + + Το τμήμα σας απαιτεί PIN ασφαλείας για επικίνδυνες ενέργειες μέσω βοηθού/μηνυμάτων, οπότε αυτή η επιλογή είναι πάντα ενεργή. + + + Το 4ψήφιο PIN ασφαλείας σας επιβεβαιώνει επικίνδυνες ενέργειες ή ενέργειες σε επίπεδο τμήματος που εκτελούνται μέσω μηνύματος ή βοηθού. Αποφύγετε επαναλαμβανόμενα ψηφία (0000) και ακολουθίες (1234, 4321). Αφήστε το κενό για να διατηρήσετε το τρέχον PIN σας. + + + PIN Ασφαλείας + + + 4ψήφιο PIN + + diff --git a/Core/Resgrid.Localization/Areas/User/Home/HomeDashboard.el.resx b/Core/Resgrid.Localization/Areas/User/Home/HomeDashboard.el.resx new file mode 100644 index 000000000..ba1aa206c --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Home/HomeDashboard.el.resx @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Σημείωση Ενέργειας (Προαιρετικό) + + + Ενέργειες + + + Είστε βέβαιοι ότι θέλετε να επαναφέρετε την κατάσταση όλου του προσωπικού σε Διαθέσιμο; + + + Σύμπτυξη Ομάδων (Μία φορά) + + + ΕΠΙΒΕΒΑΙΩΣΗ: Είστε βέβαιοι ότι θέλετε να επαναφέρετε την ομάδα όλων των χρηστών στην κατάσταση Αναμονής; Ομάδα: + + + Επιβεβαίωση Επαναφοράς Κατάστασης + + + Κωδικός Τμήματος: + + + Αναγνωριστικό Τμήματος: + + + Πληροφορίες Τμήματος + + + Επαναφορά όλων σε Διαθέσιμο + + + Επαναφορά ομάδας σε Αναμονή + + + Επαναφορά Κατάστασης + + + Ορισμός Τρέχουσας Ενέργειας + + + Ορισμός Επιπέδου Στελέχωσης + + + Επίπεδο Στελέχωσης + + + Ορισμός Επιπέδου Στελέχωσης + + + Αριθμός Μηνυμάτων: + + + Εναλλαγή Σύμπτυξης Ομάδων (Αποθηκεύεται) + + diff --git a/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.el.resx b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.el.resx new file mode 100644 index 000000000..a4c2af274 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.el.resx @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Ακύρωση + + + Αποθήκευση + + + Όνομα + + + Περιγραφή + + + Επεξεργασία + + + Διαγραφή + + + Δημιουργήθηκε + + + Ναι + + + Όχι + + + + Χάρτες Εσωτερικών Χώρων + + + Χάρτες Εσωτερικών Χώρων + + + Νέος Χάρτης Εσωτερικού Χώρου + + + Όροφοι + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χάρτη εσωτερικού χώρου; + + + + Νέος Χάρτης Εσωτερικού Χώρου + + + Νέος Χάρτης Εσωτερικού Χώρου + + + Νέος + + + π.χ. Κεντρικό Νοσοκομείο + + + Προαιρετική περιγραφή + + + Γεωγραφικό Πλάτος Κέντρου + + + Γεωγραφικό Μήκος Κέντρου + + + π.χ. 39.7392 + + + π.χ. -104.9903 + + + Πλάτος ΒΑ Ορίου + + + Μήκος ΒΑ Ορίου + + + Πλάτος ΝΔ Ορίου + + + Μήκος ΝΔ Ορίου + + + + Επεξεργασία Χάρτη Εσωτερικού Χώρου + + + Επεξεργασία Χάρτη Εσωτερικού Χώρου + + + Επεξεργασία + + + + Όροφοι + + + Όροφοι + + + Προσθήκη Ορόφου + + + Όνομα Ορόφου + + + π.χ. Όροφος 1, Υπόγειο, Ταράτσα + + + Σειρά + + + Εικόνα Κάτοψης + + + Προσθήκη Ορόφου + + + Όροφοι + + + Έχει Εικόνα + + + Επεξεργαστής Ζωνών + + + Διαγραφή αυτού του ορόφου; + + + + Επεξεργαστής Ζωνών + + + Επεξεργαστής Ζωνών + + + Ζώνες + + + Ιδιότητες Ζώνης + + + Όνομα + + + Τύπος + + + Δωμάτιο + + + Πτέρυγα + + + Διάδρομος + + + Κλιμακοστάσιο + + + Ανελκυστήρας + + + Ζώνη Κινδύνου + + + Σημείο Συγκέντρωσης + + + Χώρος Αναμονής + + + Σημείο Πρόσβασης + + + Δίκτυα Κοινής Ωφέλειας + + + Πλέγμα Έρευνας + + + Προσαρμοσμένη + + + Χρώμα + + + Περιγραφή + + + Αναζητήσιμη στη διαβίβαση + + + Αποθήκευση Ζώνης + + diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.el.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.el.resx new file mode 100644 index 000000000..81eb14fd7 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.el.resx @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ενεργό + + + Προστέθηκε Από + + + Προστέθηκε Στις + + + Προσθήκη Τύπου Αποθέματος + + + Προσθήκη Τύπου Αποθέματος + + + Προσαρμογή Αποθέματος + + + Προσαρμογή Αποθέματος + + + Πρέπει να δώσετε μη μηδενική προσαρμογή αποθέματος (πλήθος/ποσότητα). + + + Ποσότητα + + + Παρτίδα + + + Αριθμός Παρτίδας\Σειριακός Αριθμός + + + Παρτίδα\Σειριακός + + + Προσθήκη Τύπου + + + Δημιουργήθηκε από + + + Επεξεργασία + + + Επεξεργασία Τύπου Αποθέματος + + + Επεξεργασία Τύπου Αποθέματος + + + Για είδη που δεν λήγουν εισαγάγετε 0 + + + Ομάδα + + + Απόθεμα ανά Μονάδα + + + Ιστορικό Αποθέματος + + + Τύποι Αποθέματος + + + Ημέρες Λήξης Είδους + + + Διαχείριση Τύπων + + + Χωρίς Ομάδα + + + Χωρίς Μονάδα + + + Σταθμός + + + Όνομα Τύπου + + + Τύποι + + + Μονάδα + + + Μονάδα Μέτρησης + + + Προβολή Ιστορικού + + + Προβολή Καταχώρισης Αποθέματος + + + diff --git a/Core/Resgrid.Localization/Areas/User/Links/Links.el.resx b/Core/Resgrid.Localization/Areas/User/Links/Links.el.resx new file mode 100644 index 000000000..a59aa80d8 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Links/Links.el.resx @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Σύνδεσμοι Τμημάτων + + + Σύνδεσμοι Τμημάτων + + + Νέοι Σύνδεσμοι Τμημάτων + + + Από Τμήμα + + + Προς Τμήμα + + + Ενεργοποιημένος + + + Κοινόχρηστα Δεδομένα + + + Ο Σύνδεσμος Ενεργοποιήθηκε + + + Ο Σύνδεσμος Απενεργοποιήθηκε + + + Κλήσεις + + + Προσωπικό + + + Μονάδες + + + Παραγγελίες + + + Δεν Μοιράζονται Δεδομένα + + + Ενεργοποίηση + + + Απενεργοποίηση + + + Διαγραφή + + + Προβολή Δεδομένων + + + Ο Κωδικός Συνδέσμου του Τμήματός Σας + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον σύνδεσμο. Είστε βέβαιοι ότι θέλετε να τον διαγράψετε; + + + Ενεργοποίηση Συνδέσμου Τμήματος + + + Οι Σύνδεσμοι Τμημάτων μοιράζονται δεδομένα προς μία κατεύθυνση. Παρακάτω μπορείτε να ενεργοποιήσετε έναν σύνδεσμο και να ορίσετε το χρώμα του. Το χρώμα του συνδέσμου θα χρησιμοποιηθεί για τον χρωματισμό κλήσεων, προσωπικού και μονάδων ώστε να αναγνωρίζονται ως προερχόμενα από αυτόν τον σύνδεσμο. + + + Χρώμα Συνδέσμου + + + Αποθήκευση και Ενεργοποίηση Συνδέσμου + + + Νέος Σύνδεσμος Τμήματος + + + Οι Σύνδεσμοι Τμημάτων μοιράζονται δεδομένα προς μία κατεύθυνση. Παρακάτω μπορείτε να ρυθμίσετε και να μοιραστείτε δεδομένα με άλλο τμήμα και μόλις εκείνο ενεργοποιήσει τον σύνδεσμο θα μπορεί να δει τις πληροφορίες που μοιραστήκατε. Για να δει το τμήμα σας τα δεδομένα εκείνου του τμήματος, εκείνο πρέπει να δημιουργήσει σύνδεσμο προς το τμήμα σας και εσείς να τον ενεργοποιήσετε. + + + Κωδικός Συνδέσμου + + + Χρώμα Τύπου + + + Αυτό είναι το χρώμα που θα εφαρμοστεί για την αναγνώριση Κλήσεων\Μονάδων\Προσωπικού για αυτόν τον σύνδεσμο τμήματος + + + Κοινή Χρήση Κλήσεων + + + Κοινή Χρήση Μονάδων + + + Κοινή Χρήση Προσωπικού + + + Κοινή Χρήση Παραγγελιών + + + Αποθήκευση Συνδέσμου + + + Προβολή Συνδέσμου Τμήματος + + diff --git a/Core/Resgrid.Localization/Areas/User/Logs/Logs.el.resx b/Core/Resgrid.Localization/Areas/User/Logs/Logs.el.resx new file mode 100644 index 000000000..948defbac --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Logs/Logs.el.resx @@ -0,0 +1,357 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Μονάδας στην Καταγραφή + + + Συνημμένα + + + Τοποθεσία Σορού + + + Η Τοποθεσία Σορού είναι υποχρεωτική. + + + Διεύθυνση Κλήσης (Τοποθεσία) + + + Όνομα Κλήσης + + + Προτεραιότητα Κλήσης + + + Χρονική Σήμανση Κλήσης + + + Αρ. Υπόθεσης + + + Αιτία + + + Κατάσταση + + + Ιατροδικαστής + + + Ημερομηνία + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την καταγραφή. Είστε βέβαιοι ότι θέλετε να διαγράψετε την καταγραφή; + + + Τοποθεσία Προορισμού + + + Απεστάλη + + + Ερευνητής + + + Τοποθεσία + + + Πληροφορίες Καταγραφής + + + Καταγραφές για το Έτος + + + Καταγραφές + + + Σύσκεψη + + + Η ώρα λήξης της σύσκεψης είναι υποχρεωτική. + + + Η τοποθεσία της σύσκεψης είναι υποχρεωτική. + + + Η ώρα έναρξης της σύσκεψης είναι υποχρεωτική. + + + Τύπος Σύσκεψης + + + Ο τύπος σύσκεψης είναι υποχρεωτικός. + + + Αφήγηση + + + Φύση της Κλήσης + + + Νέα Κλήση + + + Νέα Καταγραφή + + + Νέα Καταγραφή + + + Παρακάτω μπορείτε να προσθέσετε νέα καταγραφή στο σύστημα (π.χ. αναφορά εξόδου, εργασία σταθμού, εκπαίδευση, συμβάν κ.λπ.). Οι καταγραφές μπορούν να χρησιμοποιηθούν για την καταγραφή πληροφοριών σχετικά με εξόδους, συσκέψεις και εκπαιδεύσεις. Τα πεδία με μπλε πλάγια γράμματα είναι υποχρεωτικά. + + + Άλλοι Συμμετέχοντες + + + Άλλοι που Είχαν Επαφή με τη Σορό + + + Προσωπικό + + + απαιτούμενο προσωπικό + + + Προεδρεύον/τα Άτομο/α + + + Διαπίστωση Θανάτου Από + + + Η Διαπίστωση Θανάτου Από είναι υποχρεωτική. + + + Η ημερομηνία είναι υποχρεωτική. + + + Επιλογή Κλήσης + + + Αρχαιότερος (Επικεφαλής) + + + Η ώρα λήξης της εκπαίδευσης είναι υποχρεωτική. + + + Η ώρα έναρξης της εκπαίδευσης είναι υποχρεωτική. + + + Προσωπικό Μονάδας + + + Μονάδες + + + Μονάδες και Προσωπικό + + + Η ώρα λήξης εργασίας είναι υποχρεωτική. + + + Η ώρα έναρξης εργασίας είναι υποχρεωτική. + + + Προβολή Καταγραφής + + + Επιστροφή στις Καταγραφές + + + Γενικές Πληροφορίες + + + Σύνοψη Καταγραφής + + + Αναγνωριστικό Καταγραφής + + + Τύπος Καταγραφής + + + Καταγράφηκε Στις + + + Καταγράφηκε Από + + + Διάρκεια + + + Πληροφορίες Κλήσης + + + Αριθμός Κλήσης + + + Προβολή Πλήρους Κλήσης + + + Πληροφορίες Εκπαίδευσης + + + Πληροφορίες Σύσκεψης + + + Πληροφορίες Ιατροδικαστή + + + Λεπτομέρειες Καταγραφής + + + Κατάσταση / Αρχική Αναφορά + + + Χωρίς Ανάθεση σε Μονάδα + + + Εκτύπωση / Εξαγωγή + + + Προβολή Εκτύπωσης (Εξαγωγής) + + + Διαγραφή Καταγραφής + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την καταγραφή; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. + + + Αναφορά Καταγραφής + + + Δημιουργήθηκε + + + Εξωτερικό Αναγνωριστικό + + + Χωρίς Τύπο + + + Δεν Δόθηκε + + + Προεδρεύων + + + Πληροφορίες Καταγραφής Εργασίας + + diff --git a/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.el.resx b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.el.resx new file mode 100644 index 000000000..a444ff3f8 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.el.resx @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Χαρτογράφηση + + + Διαχείριση Επιπέδων + + + Προσαρμοσμένοι Χάρτες + + + Επιλογές Χάρτη + + + Εμφάνιση Κλήσεων + + + Εμφάνιση Προσωπικού + + + Εμφάνιση Μονάδων + + + Εμφάνιση Σταθμών + + + Εμφάνιση Γεωφραχτών + + + Εμφάνιση Σημείων Ενδιαφέροντος + + + Κλείσιμο + + + Αποθήκευση + + + Ακύρωση + + + + Προσθήκη Σημείου Ενδιαφέροντος + + + Προσθήκη Σημείου Ενδιαφέροντος + + + Γεωγραφικό Πλάτος + + + Γεωγραφικό Πλάτος (Δεκαδική Σημειογραφία: π.χ. 39.1517) + + + Γεωγραφικό Μήκος + + + Γεωγραφικό Μήκος (Δεκαδική Σημειογραφία: π.χ. -119.4571) + + + Σημείωση + + + + Προσθήκη Τύπου Σημείου Ενδιαφέροντος + + + Προσθήκη Τύπου Σημείου Ενδιαφέροντος + + + Όνομα + + + Είναι Προορισμός + + + Για να δηλωθεί πιθανός προορισμός για μονάδα/άτομο. Για παράδειγμα γραφείο, σημείο συγκέντρωσης, νοσοκομείο, σημείο πρόσβασης κ.λπ. + + + Χρώμα + + + Δείκτης + + + Εικονίδιο + + + Κανένα + + + + Επεξεργασία Επιπέδου + + + Επεξεργασία Επιπέδου + + + Νέο Επίπεδο + + + Νέο Επίπεδο + + + Είναι Ορατό από Προεπιλογή + + + Θέλετε αυτό το επίπεδο να είναι ορατό κάθε φορά που φορτώνει ένας χάρτης; Σημείωση: Πάρα πολλά επίπεδα ορατά από προεπιλογή περιπλέκουν τον χάρτη. Οι χρήστες μπορούν να ενεργοποιούν επίπεδα αν θέλουν να τα δουν στο στοιχείο ελέγχου χάρτη. + + + Είναι Προορισμός + + + Αυτό θα επιτρέψει στην αναζήτηση διευθύνσεων να χρησιμοποιεί συντεταγμένες από αυτό το επίπεδο. Σημείωση: αν δεν έχετε αναζητήσιμα σημεία (πεδία κειμένου), μην το επισημάνετε ως αναζητήσιμο, καθώς μπορεί να επιβραδύνει τη διαβίβαση. + + + Στοιχεία Επιπέδου + + + Επεξεργασία Επιπέδου + + + Προσθήκη Επιπέδου + + + + Εισαγωγή Σημείων Ενδιαφέροντος + + + Εισαγωγή Σημείων Ενδιαφέροντος + + + Μεταφόρτωση + + + Εισαγωγή Σημείων Ενδιαφέροντος + + + + Επίπεδα + + + Επίπεδα + + + Νέο Επίπεδο + + + Πλήθος Στοιχείων + + + Επεξεργασία + + + Διαγραφή + + + + Ζωντανή Πλοήγηση + + + Ζωντανή Δρομολόγηση + + + + Σημεία Ενδιαφέροντος + + + Σημεία Ενδιαφέροντος + + + Προσθήκη Τύπου Σημείου Ενδιαφέροντος + + + Πλήθος Σημείων + + + Προβολή + + + Προσθήκη + + + Εισαγωγή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον Τύπο Σημείου Ενδιαφέροντος και όλες τις θέσεις του. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον + + + Τύπο Σημείου Ενδιαφέροντος; + + + + Πλοήγηση Σταθμού + + + Δρομολόγηση Σταθμού + + + Απόσταση: + + + Διάρκεια: + + + + Προβολή Τύπου + + + Προβολή Τύπου + + + Χάρτης + + + Πληροφορίες + + + Λίστα + + + Ναι + + + Όχι + + diff --git a/Core/Resgrid.Localization/Areas/User/Messages/Messages.el.resx b/Core/Resgrid.Localization/Areas/User/Messages/Messages.el.resx new file mode 100644 index 000000000..cd6ee43fd --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Messages/Messages.el.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Συνημμένα + + + Επανάκληση + + + Σύνταξη Μηνύματος + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτό το μήνυμα. Είστε βέβαιοι ότι θέλετε να διαγράψετε το μήνυμα + + + Διαγραφή Επιλεγμένων + + + Εξαίρεση Βαρδιών + + + Λήγει + + + Λήγει + + + Φάκελοι + + + Από + + + Εισερχόμενα + + + Σήμανση Επιλεγμένων ως Αναγνωσμένων + + + Μήνυμα + + + Εισερχόμενα Μηνύματα + + + Εξερχόμενα Μηνύματα + + + Ποτέ + + + Χωρίς Ομάδα + + + Συμπληρώστε μια σημείωση στο παραπάνω πλαίσιο κειμένου πριν πατήσετε ένα κουμπί απάντησης παρακάτω, αν θέλετε να στείλετε σημείωση μαζί με την απάντησή σας. + + + Εξερχόμενα + + + Ψηφοφορία + + + Αναγνώστηκε Στις + + + Παραλήπτες + + + Παραλήπτες + + + Απάντηση + + + Αποστολή σε όλο το Προσωπικό + + + Αποστολή σε Προσωπικό μόνο στους επιλεγμένους Ρόλους/Ομάδες + + + Στάλθηκε Από + + + Απεσταλμένα Μηνύματα + + + Απεσταλμένα Μηνύματα + + + Στάλθηκε Στις + + + Θέμα + + + Θέμα + + + Το θέμα/τίτλος του μηνύματος + + + Προς + + + Τύπος + + + Μη αναγνωσμένα + + + Προβολή Μηνύματος + + + Απάντηση Ημερολογίου + + + Θα συμμετάσχω + + + Δεν θα συμμετάσχω + + diff --git a/Core/Resgrid.Localization/Areas/User/Moderation/Moderation.el.resx b/Core/Resgrid.Localization/Areas/User/Moderation/Moderation.el.resx new file mode 100644 index 000000000..a54d443c0 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Moderation/Moderation.el.resx @@ -0,0 +1,156 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms + System.Resources.ResXResourceWriter, System.Windows.Forms + Απαιτούνται τμήμα, αναφέρων και στοιχείο. + Το αίτημα άνοιξε ξανά μετά από νέα αναφορά. + Αυτό το αίτημα βρίσκεται εκτός του πεδίου εποπτείας σας. + Μόνο εκκρεμή αιτήματα εποπτείας μπορούν να ολοκληρωθούν. + Δεν ήταν δυνατή η αφαίρεση του αναφερθέντος περιεχομένου. + Αυτό το αποδεικτικό στοιχείο βρίσκεται εκτός του πεδίου εποπτείας σας. + Λήφθηκε το πρωτότυπο αποδεικτικό στοιχείο. + Το μήνυμα συνομιλίας δεν είναι διαθέσιμο ή δεν μπορεί να αναφερθεί. + Το μήνυμα δεν είναι διαθέσιμο ή δεν μπορεί να αναφερθεί. + Το αναγνωριστικό σημείωσης κλήσης δεν είναι έγκυρο. + Η σημείωση κλήσης δεν είναι διαθέσιμη ή δεν μπορεί να αναφερθεί. + Το αναγνωριστικό εικόνας κλήσης δεν είναι έγκυρο. + Η εικόνα κλήσης δεν είναι διαθέσιμη ή δεν μπορεί να αναφερθεί. + Το περιεχόμενο αφαιρέθηκε από την εποπτεία + Αυτό το μήνυμα αφαιρέθηκε από την εποπτεία. + Αυτή η σημείωση κλήσης αφαιρέθηκε από την εποπτεία. + Αυτό το μήνυμα αφαιρέθηκε από την εποπτεία. + Το αναφερθέν περιεχόμενο αφαιρέθηκε. + Το αίτημα εξετάστηκε και δεν αφαιρέθηκε περιεχόμενο. + <br/><br/><strong>Σημείωση διαχειριστή:</strong> {0} + Το Αίτημα Εποπτείας Ολοκληρώθηκε + Η αναφορά εποπτείας σας για {0} {1} ολοκληρώθηκε. {2}{3} + Σύστημα + Μήνυμα συνομιλίας + Μήνυμα + Σημείωση κλήσης + Εικόνα κλήσης + Αιτήματα + Αναφορές + Έλεγχοι συνομιλίας + Ρυθμίσεις συνομιλίας + Εξαγωγές συνομιλίας + Αναζητήστε νέα και ολοκληρωμένα αιτήματα εποπτείας βάσει του χρήστη που πρόσθεσε το περιεχόμενο ή του χρήστη που το ανέφερε. + Κατάσταση + Όλα + Εκκρεμή + Ολοκληρωμένα + Τύπος περιεχομένου + Αναγνωριστικό χρήστη που πρόσθεσε + Αναγνωριστικό χρήστη που ανέφερε + Από + Έως + Ανανέωση + Φόρτωση αιτημάτων εποπτείας… + Κανένα αίτημα εποπτείας δεν ταιριάζει με αυτά τα φίλτρα. + Στοιχείο + Πρωτότυπο αποδεικτικό στοιχείο + Προστέθηκε από + Αναφορές + Ενέργεια + Το περιεχόμενο αφαιρέθηκε + Καμία ενέργεια + Άγνωστος τύπος περιεχομένου + Αναγνωριστικό {0} + Κλήση {0} + Λήψη διατηρημένων αποδεικτικών στοιχείων + Σύστημα ή άγνωστο + ομάδα {0} + Σημείωση ολοκλήρωσης που αποστέλλεται στους χρήστες που ανέφεραν (προαιρετικό) + Αφαίρεση περιεχομένου + Ολοκλήρωση—καμία ενέργεια + Ολοκληρώθηκε από + Χωρίς σημείωση ολοκλήρωσης. + Απόκρυψη ίχνους ελέγχου + Ίχνος ελέγχου ({0}) + από + Χωρίς σημείωση. + Αναφέρων + Διαχειριστής τμήματος + Διαχειριστής ομάδας + Παλαιά εισαγωγή + Άγνωστος ρόλος + Χωρίς διεύθυνση IP + ίχνος {0} + μη διαθέσιμο + + Δεν είναι δυνατή η φόρτωση αιτημάτων εποπτείας. + Δεν είναι δυνατή η ολοκλήρωση του αιτήματος εποπτείας. + Άλλο + Ακατάλληλο περιεχόμενο + Παρενόχληση ή κακοποίηση + Ανεπιθύμητο + Ευαίσθητες πληροφορίες + Παραβίαση πολιτικής + Η αναφορά υποβλήθηκε + Το αίτημα άνοιξε ξανά + Ολοκληρώθηκε χωρίς ενέργεια + Το περιεχόμενο αφαιρέθηκε + Λήφθηκαν αποδεικτικά στοιχεία + Κατάσταση αναφοράς εποπτείας + Αναφορά μηνύματος για εποπτεία + Κλείσιμο + Ακύρωση + Αναφορά + Έλεγχος κατάστασης αναφοράς… + Κατάσταση + Το περιεχόμενο αφαιρέθηκε + Δεν αφαιρέθηκε περιεχόμενο + Σημείωση συντονιστή + Αιτιολογία + Προσθέστε σημείωση (προαιρετικό) + Η αναφορά εποπτείας σας είναι + Αναφορά για εποπτεία + Εξηγήστε γιατί πρέπει να εξεταστεί αυτό το μήνυμα + Η αναφορά εποπτείας σας υποβλήθηκε. + Διαγραφή μηνύματος + Σίγαση χρήστη + Αποκλεισμός χρήστη + Άρση αποκλεισμού χρήστη + Κλείδωμα καναλιού + Ξεκλείδωμα καναλιού + Επίλυση αναφοράς + Ενέργεια #{0} + Φόρτωση… + Καμία δραστηριότητα εποπτείας. + Στόχος + Πότε + Μονάδα {0} + Προηγούμενη + Σελίδα {0} + Επόμενη + Να επιτρέπονται συνημμένες εικόνες + Να επιτρέπονται GIF + Να επιτρέπεται κοινή χρήση τοποθεσίας + Τα επείγοντα μηνύματα παρακάμπτουν τη σίγαση + Ο βοηθός είναι ενεργοποιημένος + Διατήρηση μηνυμάτων (ημέρες, 0 = για πάντα) + Μέγιστο μέγεθος συνημμένου (MB) + Αποθήκευση… + Αποθήκευση ρυθμίσεων + Αποθηκεύτηκε + Αποτυχία αποθήκευσης ρυθμίσεων εποπτείας. + Σε ουρά + Σε εξέλιξη + Ολοκληρώθηκε + Απέτυχε + Αποτυχία αιτήματος εξαγωγής συνομιλίας. + Μορφή + Ημερομηνία έναρξης + Ημερομηνία λήξης + Υποβολή αιτήματος… + Αίτημα εξαγωγής + Ζητήθηκε + Εύρος + Λήψη + Καμία εργασία εξαγωγής. + Τώρα + moderation-evidence-{0} + Αυτό το μήνυμα διαγράφηκε. + diff --git a/Core/Resgrid.Localization/Areas/User/Notes/Note.el.resx b/Core/Resgrid.Localization/Areas/User/Notes/Note.el.resx new file mode 100644 index 000000000..069d1c585 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Notes/Note.el.resx @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Μόνο Διαχειριστές + + + Σώμα + + + Το περιεχόμενο κειμένου της σημείωσης + + + Κατηγορία + + + Επιλέξτε υπάρχουσα Κατηγορία από την αναπτυσσόμενη λίστα ή πληκτρολογήστε στο πλαίσιο για να δημιουργήσετε Κατηγορία. + + + Διαγραφή Σημείωσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή τη σημείωση. Είστε βέβαιοι ότι θέλετε να διαγράψετε τη σημείωση; + + + Επεξεργασία Σημείωσης + + + Όλοι + + + Νέα Σημείωση + + + Προσθήκη νέας σημείωσης + + + Τίτλος + + + Τίτλος της Σημείωσης + + + Ορατή Από + + + Ορατή μόνο από Διαχειριστές + + + Ορατή από Όλους + + + Προβολή Σημείωσης + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Notifications/Notifications.el.resx b/Core/Resgrid.Localization/Areas/User/Notifications/Notifications.el.resx new file mode 100644 index 000000000..7f5201dda --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Notifications/Notifications.el.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ειδοποιήσεις + + + Ειδοποιήσεις + + + Νέα Ειδοποίηση + + + Προηγούμενη Κατάσταση + + + Τρέχουσα Κατάσταση + + + Ειδοποίηση + + + Καμία + + + Διαγραφή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την ειδοποίηση. Είστε βέβαιοι ότι θέλετε να συνεχίσετε; + + + Τύπος Μονάδας προς Παρακολούθηση + + + Ρόλος προς Παρακολούθηση + + + Αποδεκτές Καταστάσεις + + + Κατώτατο Όριο + + + Χρήστες προς Ειδοποίηση + + + Όλοι + + + Διαχειριστές Τμήματος + + + Κλείδωμα σε Ομάδα + + + Μόνο Διαχειριστές Ομάδας + + + Οι ειδοποιήσεις ημερολογίου καθορίζονται πλέον με βάση τον τύπο του συμβάντος ημερολογίου (Καμία, Ανάθεση, Απάντηση) και τις Οντότητες που συμμετέχουν στο συμβάν. Για τον τύπο συμβάντος Καμία θα ειδοποιηθούν οι χρήστες στην επιλογή Οντοτήτων (το τμήμα ή μια ομάδα). Για συμβάντα Απάντησης θα ειδοποιηθούν όλοι οι χρήστες του τμήματος και για συμβάντα με ανάθεση θα ειδοποιηθούν μόνο οι Υποχρεωτικοί και Προαιρετικοί συμμετέχοντες. + + + Οι ειδοποιήσεις βαρδιών καθορίζονται πλέον με βάση τις ενέργειες που εκτελούνται σε μια βάρδια (Δημιουργία, Ενημέρωση, Προσθήκη Ημερών). Οι χρήστες που συνδέονται με υπάρχουσα βάρδια θα ειδοποιούνται για ενημερώσεις, όλοι οι χρήστες του τμήματος θα ειδοποιούνται για νέες βάρδιες και για βάρδιες με δήλωση συμμετοχής όλοι οι χρήστες θα ειδοποιούνται για νέες ημέρες, ενώ για βάρδιες με ανάθεση μόνο τα άτομα εκείνης της βάρδιας. + + + Αποθήκευση Ρόλου Ειδοποίησης + + diff --git a/Core/Resgrid.Localization/Areas/User/Orders/Orders.el.resx b/Core/Resgrid.Localization/Areas/User/Orders/Orders.el.resx new file mode 100644 index 000000000..e95af0c87 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Orders/Orders.el.resx @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Παραγγελίες Πόρων + + + Ενημέρωση Ρυθμίσεων + + + Δημιουργία Παραγγελίας + + + Οι Παραγγελίες Σας + + + Διαθέσιμες Παραγγελίες + + + Δημιουργήθηκε + + + Ποσοστό Κάλυψης + + + Κλειστή + + + Καλυμμένη + + + Ανοιχτή + + + Προβολή + + + Τοποθεσία + + + Απόσταση + + + Νέα Παραγγελία + + + Τίτλος + + + Σύντομος Τίτλος για την Παραγγελία + + + Σύνοψη + + + Μια σύνοψη για την παραγγελία + + + Κλείσιμο Παραγγελίας Στις + + + Ο χρόνος μέχρι τον οποίο πρέπει να καλυφθεί η παραγγελία· ανάλογα με τον τύπο της, μπορεί να κλείσει αυτόματα εκείνη τη στιγμή. + + + Αριθμός Περιστατικού + + + Αναγνωριστικό ή αριθμός περιστατικού + + + Όνομα Περιστατικού + + + Όνομα του Περιστατικού + + + Διεύθυνση Περιστατικού + + + Διεύθυνση του Περιστατικού, της Διοίκησης ή του Κέντρου Διοίκησης + + + Οικονομικός Κωδικός + + + Οικονομικός κωδικός για το περιστατικό ή την παραγγελία + + + Όνομα Επαφής + + + Όνομα του σημείου επαφής για τη συνάντηση ή το περιστατικό + + + Αριθμός Επαφής + + + Αριθμός του σημείου επαφής για τη συνάντηση ή το περιστατικό + + + Τοποθεσία Συνάντησης + + + Τοποθεσία όπου θα συναντηθούν οι μονάδες από τις καλυμμένες παραγγελίες + + + Ώρα Συνάντησης + + + Ειδικές Οδηγίες + + + Τυχόν ειδικές οδηγίες ή απαιτήσεις για την παραγγελία + + + Να Επιτρέπονται Μερικές Καλύψεις + + + Επιτρέψτε σε ένα τμήμα να καλύψει μερικώς ένα στοιχείο παραγγελίας πόρων (αυτό επιτρέπει σε πολλαπλές μονάδες από πολλαπλά τμήματα να καλύψουν ένα στοιχείο) + + + Αυτόματη Αποδοχή Καλύψεων + + + Το σύστημα θα αποδέχεται αυτόματα τις καλύψεις (μερικές, βάσει της ρύθμισης Να Επιτρέπονται Μερικές Καλύψεις) κατά σειρά προτεραιότητας + + + Στοιχεία Παραγγελίας + + + Κάντε κλικ στο πράσινο κουμπί «Προσθήκη» για να προσθέσετε στοιχεία σε αυτή την παραγγελία· τα στοιχεία είναι ομάδες παρόμοιων μονάδων που θέλετε να αποτελέσουν μέρος της παραγγελίας. + + + Πόρος + + + Ελάχ. + + + Μέγ. + + + Οικονομικός Κωδικός + + + Ειδικές Ανάγκες + + + Απαιτήσεις + + + Προσθήκη + + + Δημιουργία Παραγγελίας + + + Ρυθμίσεις Παραγγελιών Πόρων + + + Να Μην Λαμβάνονται Παραγγελίες + + + Προεπιλεγμένη Ορατότητα Παραγγελίας + + + Εμβέλεια Ορατότητας (σε Μίλια) + + + Προεπιλεγμένος Χρήστης Διαχείρισης Παραγγελιών + + + Περιορισμός Ειδοποιήσεων Παραγγελιών βάσει Στελέχωσης + + + Επιτρεπόμενο Επίπεδο Στελέχωσης για Ειδοποιήσεις + + + Επιτρεπόμενοι Τύποι Τμημάτων + + + Αυτόματη Αποδοχή Καλύψεων + + + Ειδοποίηση Ρόλων + + + Ρόλοι προς Ειδοποίηση + + + Αποθήκευση Ρυθμίσεων + + + Κάλυψη Παραγγελίας + + + Κατάσταση: + + + Δημιουργήθηκε από: + + + Περιστατικό: + + + Αριθμός\Αναγνωριστικό: + + + Οικονομικός Κωδικός: + + + Τελευταία Ενημέρωση: + + + Δημιουργήθηκε: + + + Όνομα Επαφής: + + + Αριθμός Επαφής: + + + Κάλυψη: + + + Αυτή η παραγγελία πόρων είναι + + + καλυμμένη. + + + Καλύψεις + + + Πόρος: + + + Απαιτήσεις: + + + Ελάχ.: + + + Μέγ.: + + + Απαιτούνται: + + + Αποτέλεσμα Κάλυψης Παραγγελίας + + + Η Κάλυψη Απέτυχε + + + Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκπλήρωσης του αιτήματός σας. Δείτε το παρακάτω σφάλμα για περισσότερες λεπτομέρειες. + + + Η Κάλυψη Προστέθηκε και Έγινε Αποδεκτή + + + Η κάλυψή σας προστέθηκε στο σύστημα και έγινε αυτόματα αποδεκτή. + + + Ίσως θέλετε να επικοινωνήσετε με το Σημείο Επαφής της παραγγελίας για να διευκρινιστούν τυχόν άλλες λεπτομέρειες. + + + Η Κάλυψη Προτάθηκε + + + Η κάλυψή σας καταγράφηκε στο σύστημα και σημάνθηκε ως εκκρεμής. Ο δημιουργός του αιτήματος πόρων θα πρέπει να ελέγξει χειροκίνητα το αίτημα κάλυψής σας και να το εγκρίνει. Θα λάβετε ειδοποίηση όταν συμβεί αυτό. + + + Προβολή Παραγγελίας + + + Αίτημα Κάλυψης Παραγγελίας + + + Παραγγελίες + + + Τμήμα + + + Χρονική Σήμανση + + + Επαφή + + + Σημείωση + + + Μονάδες + + + Αποδεκτή + + + Μη Αποδεκτή + + + Εκκρεμής + + + Αποδοχή + + + Σύνοψη Παραγγελίας + + + Ειδικές Οδηγίες + + + Διεύθυνση Περιστατικού + + + Συνάντηση + + + Τοποθεσία: + + + Ημερομηνία: + + + Δεν Δόθηκε + + diff --git a/Core/Resgrid.Localization/Areas/User/Personnel/Person.el.resx b/Core/Resgrid.Localization/Areas/User/Personnel/Person.el.resx new file mode 100644 index 000000000..a360a9cbb --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Personnel/Person.el.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Πληροφορίες Λογαριασμού + + + Προσθήκη Ατόμου + + + Προσθήκη ενός ατόμου + + + Προστέθηκε Υπάρχων Χρήστης + + + Προσθήκη Ατόμου + + + Προσθήκη Βαθμού + + + Προσθήκη Ρόλου + + + Επιλογές Κλήσεων + + + Αλλαγή Κωδικού Πρόσβασης + + + Κάντε κλικ στο παρακάτω πλαίσιο για να διαγράψετε αυτόν τον χρήστη. + + + Επιβεβαίωση Διαγραφής; + + + Επιβεβαιώστε τον κωδικό πρόσβασης του χρήστη (πρέπει να ταιριάζει με τον παραπάνω) + + + Επιβεβαίωση Κωδικού Πρόσβασης + + + Στοιχεία Επικοινωνίας + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χρήστη; + + + Αν επιλέξετε να διαγράψετε αυτόν τον χρήστη, οι παρακάτω πληροφορίες θα διαγραφούν οριστικά. Η συνιστώμενη διαδικασία είναι να απενεργοποιήσετε τον χρήστη αν αποχωρήσει από το τμήμα ή τον οργανισμό. Διαγράψτε τον χρήστη μόνο αφού περάσει αρκετός χρόνος, αν ο λογαριασμός ήταν λάθος ή δεν χρησιμοποιήθηκε ποτέ. + + + Διαγραφή Ατόμου + + + Επεξεργασία Ρόλου + + + Email + + + Οι διευθύνσεις email πρέπει να είναι μοναδικές + + + Διεύθυνση Email + + + Διεύθυνση Email (πρέπει να είναι μοναδική) + + + Αυτός ο χρήστης + + + , με βάση τη διεύθυνση email, είχε ήδη λογαριασμό Resgrid σε άλλο τμήμα. Ο λογαριασμός χρήστη προστέθηκε πλέον <b>με τις προηγούμενες ρυθμίσεις προφίλ του (όνομα, αριθμοί τηλεφώνου, προτιμήσεις κ.λπ.)</b> στο τμήμα. Επειδή ο χρήστης ανήκει σε πολλά τμήματα, πρέπει να ενεργοποιήσει αυτό το τμήμα για να δει τις πληροφορίες του από την επιλογή «Προβολή των Τμημάτων Σας» στο αναπτυσσόμενο μενού του προφίλ του (κάτω από τη φωτογραφία προφίλ στην επάνω αριστερή γωνία). + + + Όνομα + + + Όνομα + + + Ομάδα + + + Αρχική + + + Αριθμός Ταυτότητας + + + Προσαρμοσμένος Αριθμός Ταυτοποίησης + + + Είναι Διαχειριστής Ομάδας; + + + Επώνυμο + + + Επώνυμο + + + Διαχείριση Προσκλήσεων + + + Πρόσκληση πολλών ατόμων + + + Διαχείριση Ρόλων + + + Επιλογές Μηνυμάτων + + + Κινητό + + + Πάροχος Κινητής + + + Αριθμός Κινητού + + + Αριθμός Κινητού Τηλεφώνου + + + Οι κωδικοί πρόσβασης πρέπει να έχουν 8 χαρακτήρες ή περισσότερους και να περιλαμβάνουν ένα ψηφίο (αριθμό), ένα κεφαλαίο και ένα πεζό γράμμα + + + Νέος Κωδικός Πρόσβασης + + + Δεν Υπάρχει Προσωπικό στο Τμήμα + + + Δεν Υπάρχει Προσωπικό σε Αυτή την Ομάδα + + + Επιλογές Ειδοποιήσεων + + + Ειδοποίηση Χρήστη; + + + Αποεπιλέξτε το αν δεν θέλετε το Resgrid να ειδοποιήσει τον χρήστη για τη δημιουργία αυτού του λογαριασμού. + + + Απαίτηση Αλλαγής Κωδικού Πρόσβασης + + + Ο χρήστης θα υποχρεωθεί να αλλάξει τον κωδικό πρόσβασής του κατά την πρώτη σύνδεση. + + + Δεν Υπάρχει Προσωπικό Εκτός Ομάδας + + + Προσωπικό + + + Αριθμός/οί Τηλεφώνου + + + Push + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον βαθμό. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον βαθμό + + + Βαθμοί Προσωπικού + + + Επανενεργοποίηση Ατόμου + + + Αυτός ο χρήστης + + + με βάση τη διεύθυνση email, είχε ήδη λογαριασμό στο τμήμα + + + αλλά διαγράφηκε προηγουμένως. Ο λογαριασμός χρήστη επαναφέρθηκε και ενεργοποιήθηκε ξανά μέσα σε αυτό το τμήμα και θα εμφανίζεται <b>με τις προηγούμενες ρυθμίσεις προφίλ του (όνομα, αριθμοί τηλεφώνου, προτιμήσεις κ.λπ.)</b> στο τμήμα. + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτόν τον ρόλο. Είστε βέβαιοι ότι θέλετε να διαγράψετε τον ρόλο + + + Μια περιγραφή του ρόλου + + + Όνομα του Ρόλου + + + Ρόλοι Προσωπικού + + + Ρόλοι + + + Ορισμός Στελέχωσης Ατόμου + + + Ορισμός Κατάστασης Ατόμου + + + Ορισμός Στελέχωσης + + + Ορισμός Στελέχωσης για Επιλεγμένο Προσωπικό + + + Ορισμός Κατάστασης + + + Ορισμός Κατάστασης για Επιλεγμένο Προσωπικό + + + Ειδική Σημείωση + + + Τα υποκείμενα δεδομένα χρήστη διατηρούνται στο σύστημα ώστε οι καταγραφές, οι αναφορές και το ιστορικό να διατηρούνται σωστά για το τμήμα. Συνιστάται να τροποποιηθούν ώστε να αφαιρεθούν όλα τα PII/PHI στον λογαριασμό χρήστη, δηλαδή αριθμοί τηλεφώνου, διευθύνσεις κ.λπ., πριν διαγράψετε τον χρήστη. + + + Στελέχωση + + + Νομός + + + SMS + + + Ενδέχεται να επιβαρυνθείτε με πρόσθετες χρεώσεις για SMS ανάλογα με το πρόγραμμα κινητής σας. + + + Χρήστης + + + Λεπτομέρειες Χρήστη + + + Το Όνομα Χρήστη πρέπει να είναι μοναδικό + + + Όνομα Χρήστη + + + Χρήστες στον Βαθμό + + + Χρήστες στον Ρόλο + + + Προβολή Ατόμου + + + Δημιουργία Αναφοράς + + + Προβολή Συμβάντων Ατόμου + + + Προβολή Συμβάντων + + + Συμβάντα για + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά όλες τις καταστάσεις για αυτό το άτομο; + + + Εκκαθάριση όλων των Καταστάσεων για το Άτομο + + + Διαγραφή Όλων + + + Ναι, είμαι βέβαιος + + + Προβολή Ρόλου + + diff --git a/Core/Resgrid.Localization/Areas/User/Profile/Profile.el.resx b/Core/Resgrid.Localization/Areas/User/Profile/Profile.el.resx new file mode 100644 index 000000000..8985be1f0 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Profile/Profile.el.resx @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Πιστοποίησης + + + Προσθήκη Πιστοποίησης + + + Εισαγάγετε τις παρακάτω πληροφορίες για να επεξεργαστείτε μια πιστοποίηση στο προφίλ σας. Παραδείγματα πιστοποιήσεων είναι πιστοποιήσεις EMT, ΚΑΡΠΑ/BLS, επικίνδυνων υλικών κ.λπ. Η παρακολούθηση των πιστοποιήσεων είναι χρήσιμη ώστε να ενημερώνονται/ανανεώνονται πριν λήξουν και το τμήμα να γνωρίζει ποιος είναι πιστοποιημένος για τι. Τα πεδία με <span class="required">μπλε πλάγια γράμματα</span> είναι υποχρεωτικά. + + + Προσθήκη Προγραμματισμένης Αναφοράς + + + Προσθήκη Προγράμματος Στελέχωσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την πιστοποίηση. Είστε βέβαιοι ότι θέλετε να διαγράψετε την πιστοποίηση; + + + Αρ. Ταυτότητας \ Αρ. Πιστοποίησης + + + Αρ. Ταυτότητας \ Αρ. Πιστοποίησης + + + Όνομα Πιστοποίησης + + + Πιστοποιήσεις + + + Αλλαγή Κωδικού Πρόσβασης + + + Αλλαγή Κωδικού Πρόσβασης για Χρήστη + + + Εδώ μπορείτε να αλλάξετε χειροκίνητα τον κωδικό πρόσβασης ενός χρήστη στο σύστημα. Εισαγάγετε τον νέο κωδικό πρόσβασης και επιβεβαιώστε τον για να τον αλλάξετε. Οι κωδικοί πρόσβασης πρέπει να έχουν μήκος τουλάχιστον 6 χαρακτήρων. Σημειώστε ότι το Resgrid δεν θα ειδοποιήσει τον χρήστη για αυτή την αλλαγή κωδικού. Τα πεδία με <span class="required">μπλε πλάγια γράμματα</span> είναι υποχρεωτικά. + + + Αλλαγή Στελέχωσης Σε + + + Επιβεβαίωση Κωδικού Πρόσβασης + + + Επιβεβαιώστε τον Κωδικό Πρόσβασης για αυτόν τον Χρήστη + + + Ημέρες της Εβδομάδας + + + Κωδικός Τμήματος + + + Αναγνωριστικό Τμήματος + + + Εισαγάγετε τις παρακάτω πληροφορίες για να επεξεργαστείτε μια πιστοποίηση στο προφίλ σας. Παραδείγματα πιστοποιήσεων είναι πιστοποιήσεις EMT, ΚΑΡΠΑ/BLS, επικίνδυνων υλικών κ.λπ. Η παρακολούθηση των πιστοποιήσεων είναι χρήσιμη ώστε να ενημερώνονται/ανανεώνονται πριν λήξουν και το τμήμα να γνωρίζει ποιος είναι πιστοποιημένος για τι. Τα πεδία με <span class="required">μπλε πλάγια γράμματα</span> είναι υποχρεωτικά. + + + Επεξεργασία Πιστοποίησης + + + Επεξεργασία Προφίλ + + + Επεξεργασία Προγραμματισμένης Αναφοράς + + + Επεξεργασία Προγράμματος Στελέχωσης + + + Αποστολή Email στον Χρήστη; + + + Επιλέξτε αυτό το πλαίσιο αν θέλετε να σταλεί email στον χρήστη με το Όνομα Χρήστη και τον νέο Κωδικό Πρόσβασής του. + + + Απαίτηση Αλλαγής Κωδικού Πρόσβασης + + + Ο χρήστης θα υποχρεωθεί να αλλάξει τον κωδικό πρόσβασής του κατά την επόμενη σύνδεση. + + + Λήγει Στις + + + Λήγει Στις + + + Εκδόθηκε Από + + + Όνομα εκδούσας αρχής + + + Εκδόθηκε Στις + + + Σύνδεση με Τμήμα + + + Σύνδεση με Τμήμα + + + Πρέπει να δώσετε το τρέχον Αναγνωριστικό και Κωδικό Τμήματος για το Τμήμα Resgrid στο οποίο θέλετε να συνδεθείτε. + + + Νέος Κωδικός Πρόσβασης + + + Νέος Κωδικός Πρόσβασης για αυτόν τον Χρήστη + + + Οι κωδικοί πρόσβασης πρέπει να έχουν 8 χαρακτήρες ή περισσότερους και να περιλαμβάνουν ένα ψηφίο (αριθμό), ένα κεφαλαίο και ένα πεζό γράμμα + + + Νέα Προγραμματισμένη Αναφορά + + + Νέο Πρόγραμμα Στελέχωσης + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα σας αφαιρέσει από αυτό το τμήμα. Είστε βέβαιοι; + + + Αφαίρεση του Εαυτού Σας από το Τμήμα + + + Παράδοση Αναφορών + + + Προγράμματα Παράδοσης Αναφορών + + + Αναφορά προς Εκτέλεση + + + Προγράμματα + + + Ορισμός ως Ενεργό + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα αλλάξει το τρέχον ενεργό τμήμα σας. Μπορείτε να είστε συνδεδεμένοι μόνο σε ένα τμήμα κάθε φορά. Είστε βέβαιοι; + + + Ορισμός ως Προεπιλογή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα αλλάξει το προεπιλεγμένο τμήμα σας. Αν θέλετε να δείτε αυτό το τμήμα, θα χρειαστεί να το ενεργοποιήσετε στη συνέχεια. + + + Συγκεκριμένη Ημερομηνία + + + Συγκεκριμένη Ημερομηνία και Ώρα + + + Πρόγραμμα Στελέχωσης + + + Πρόγραμμα Στελέχωσης + + + Ώρα Εκτέλεσης + + + Περιοχή Ισχύος + + + Περιοχή στην οποία ισχύει η πιστοποίηση + + + Τα Τμήματά Σας + + + + Απαιτήσεις Κωδικού Πρόσβασης + Τουλάχιστον {0} χαρακτήρες + Τουλάχιστον ένα ψηφίο (αριθμός) + Τουλάχιστον ένα κεφαλαίο και ένα πεζό γράμμα + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Protocols/Protocols.el.resx b/Core/Resgrid.Localization/Areas/User/Protocols/Protocols.el.resx new file mode 100644 index 000000000..5cdb9f4ff --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Protocols/Protocols.el.resx @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Πρωτόκολλα + + + Νέο Πρωτόκολλο + + + Δημιουργήθηκε Στις + + + Πρωτόκολλο Αποστολής + + + Προβολή + + + Διαγραφή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτό το πρωτόκολλο. Είστε βέβαιοι ότι θέλετε να διαγράψετε το πρωτόκολλο + + + Νέο Πρωτόκολλο + + + Όνομα Πρωτοκόλλου + + + Κωδικός Πρωτοκόλλου + + + Περιγραφή + + + Κείμενο Πρωτοκόλλου + + + Βαρύτητα Ερώτησης Ενεργοποίησης + + + Συνημμένα + + + Κανόνες Ενεργοποίησης + + + Οι κανόνες ενεργοποίησης χρησιμοποιούνται για να καθορίσουν πότε θα ενεργοποιείται αυτό το πρωτόκολλο ή πότε θα τίθενται οι παρακάτω ερωτήσεις για τις κλήσεις + + + Τύπος + + + Ξεκινά Στις + + + Λήγει Στις + + + Τύπος Κλήσης + + + Προτεραιότητα Κλήσης + + + Προσθήκη Κανόνα Ενεργοποίησης + + + Ερωτήσεις + + + Αν χρειάζεστε μόνο τους κανόνες ενεργοποίησης για να ενεργοποιηθεί το πρωτόκολλο, τότε δεν χρειάζεται να προσθέσετε ερωτήσεις. + + + Ερώτηση + + + Απαντήσεις + + + Προσθήκη Ερώτησης + + + Προσθήκη Πρωτοκόλλου + + + Προσθήκη Κανόνα Ενεργοποίησης + + + Τύπος: + + + Ξεκινά Στις: + + + Λήγει Στις: + + + Προτεραιότητα Κλήσης: + + + Τύπος Κλήσης: + + + Κλείσιμο + + + Προσθήκη Ερώτησης + + + Ερώτηση: + + + Προβολή Πρωτοκόλλου + + + Κατάσταση: + + + Ενεργό + + + Απενεργοποιημένο + + + Κωδικός: + + + Τύπος: + + + Δημιουργήθηκε Από: + + + Δημιουργήθηκε Στις: + + + Βαρύτητα: + + + Περιγραφή: + + + Κείμενο Πρωτοκόλλου: + + + Κανόνες Ενεργοποίησης + + + Συνημμένα + + + Ερωτήσεις + + + Προτεραιότητα + + + Προτεραιότητα Κλήσης + + + Τύπος Κλήσης + + + Προτεραιότητα και Τύπος Κλήσης + + + Χωρίς Ημερομηνία Έναρξης + + + Χωρίς Ημερομηνία Λήξης + + + Μ/Δ + + + Όνομα Αρχείου + + + Τύπος Αρχείου + + + Μέγεθος + + + Βαρύτητα + + + Απάντηση + + + Πρωτόκολλα + + diff --git a/Core/Resgrid.Localization/Areas/User/Reports/FlaggedReport.el.resx b/Core/Resgrid.Localization/Areas/User/Reports/FlaggedReport.el.resx new file mode 100644 index 000000000..caf85da0e --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Reports/FlaggedReport.el.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Αναφορά Επισημασμένων Σημειώσεων, Εικόνων και Αρχείων Κλήσεων + + + Αυτή η αναφορά εμφανίζει όλες τις επισημασμένες σημειώσεις κλήσεων, τις επισημασμένες εικόνες κλήσεων και τα επισημασμένα αρχεία κλήσεων του τμήματος, συμπεριλαμβανομένων των σχετικών πληροφοριών κλήσης, του περιεχομένου ή του ονόματος αρχείου, των λεπτομερειών επισήμανσης και του λόγου για τον οποίο επισημάνθηκε κάθε στοιχείο. + + + Ημερομηνία Έναρξης Αναφοράς + + + Ημερομηνία Λήξης Αναφοράς + + + Εκτέλεση Αναφοράς + + + Εφαρμόστηκε Φίλτρο Ημερομηνίας + + + Επισημασμένες Σημειώσεις + + + Επισημασμένες Εικόνες + + + Επισημασμένα Αρχεία + + + επισημασμένα + + + Δεν βρέθηκαν επισημασμένες σημειώσεις κλήσεων για αυτό το τμήμα. + + + Δεν βρέθηκαν επισημασμένες εικόνες κλήσεων για αυτό το τμήμα. + + + Δεν βρέθηκαν επισημασμένα αρχεία κλήσεων για αυτό το τμήμα. + + + Κλήση # + + + Όνομα Κλήσης + + + Τύπος + + + Διεύθυνση + + + Καταγραφή Κλήσης + + + Σημείωση + + + Σημείωση Από + + + Χρονική Σήμανση Σημείωσης + + + Πληροφορίες Επισήμανσης + + + Όνομα Αρχείου + + + Εικόνα Από + + + Χρονική Σήμανση Εικόνας + + + Τύπος Αρχείου + + + Αρχείο Από + + + Χρονική Σήμανση Αρχείου + + + Επισημάνθηκε Από + + + Επισημάνθηκε Στις + + + Αιτιολογία + + + Εύρος ημερομηνιών + + + Η αναφορά δημιουργήθηκε + + + + diff --git a/Core/Resgrid.Localization/Areas/User/Reports/Reports.el.resx b/Core/Resgrid.Localization/Areas/User/Reports/Reports.el.resx new file mode 100644 index 000000000..864d49b1a --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Reports/Reports.el.resx @@ -0,0 +1,743 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + Αναφορές + + + Προβολή Αναφοράς + + + Αναφορές + + + + + Αναφορά Ιστορικού Κατάστασης Προσωπικού + + + Αυτή η αναφορά εμφανίζει όλο το ιστορικό κατάστασης (αρχεία ενεργειών) του επιλεγμένου προσωπικού + + + + + Αναφορά Προσωπικού + + + Δείτε όλο το προσωπικό του τμήματός σας, τους ρόλους του και βασικά στοιχεία επικοινωνίας. + + + + + Αναφορά Στελέχωσης + + + Δείτε όλο το προσωπικό και την τρέχουσα κατάστασή του, το επίπεδο στελέχωσης και την επόμενη προγραμματισμένη αλλαγή επιπέδου στελέχωσης. + + + + + Αναφορά Πιστοποιήσεων + + + Δείτε όλες τις πιστοποιήσεις κάθε ατόμου στο τμήμα. + + + + + Αναφορά Ετοιμότητας Βαρδιών + + + Αυτή η αναφορά εμφανίζει την ετοιμότητα των επόμενων ημερών βάρδιας για κάθε βάρδια. + + + + + Αναφορά Δραστηριότητας Τμήματος από Αρχή Έτους + + + Αυτή η αναφορά εμφανίζει τη δραστηριότητα κλήσεων, καταγραφών, εκπαιδεύσεων και προσωπικού του τμήματος σε αριθμούς από την αρχή του έτους. + + + + + Αναφορά Ωρών Καταγραφών Προσωπικού + + + Αυτή η αναφορά εμφανίζει τον αριθμό των ωρών που το προσωπικό του τμήματος εργάστηκε, εκπαιδεύτηκε και ανταποκρίθηκε σε κλήσεις, όπως καταγράφηκαν μέσω καταχωρίσεων καταγραφών (απολογιστικών αναφορών). + + + + + Αναφορά Σύνοψης Κλήσεων + + + Αυτή η αναφορά εμφανίζει λεπτομέρειες για όλες τις κλήσεις σε επιλεγμένο εύρος ημερομηνιών (π.χ. τύποι κλήσεων, καταστάσεις κλεισίματος κ.λπ.) + + + + + Αναφορά Ιστορικού Στελέχωσης Προσωπικού + + + Αυτή η αναφορά εμφανίζει όλο το ιστορικό στελέχωσης του επιλεγμένου προσωπικού + + + + + Αναφορά Ιστορικού Καταστάσεων Μονάδων + + + Εμφανίζει τις Καταστάσεις Μονάδων για μία μονάδα, όλες ή τις μονάδες μιας ομάδας για καθορισμένο από τον χρήστη χρονικό εύρος + + + + + Αναφορά Ενεργών Κλήσεων και Πόρων + + + Η αναφορά περιγράφει λεπτομερώς τις ανοιχτές κλήσεις κατά τη στιγμή της εκτέλεσής της και τυχόν μονάδες και προσωπικό που είναι συνδεδεμένα με αυτές τις κλήσεις + + + + + Ώρα Έναρξης Αναφοράς + + + Ώρα Λήξης Αναφοράς + + + Εκτέλεση Αναφοράς + + + Τύπος + + + Ομάδα ή Χρήστης/ες + + + Ομάδα + + + Ομάδα + + + Χρήστης/ες + + + Μονάδες + + + Ομάδα ή Μονάδα/ες + + + + + Αναγνωριστικό + + + Όνομα + + + Ομάδα + + + Σύνολο + + + Πλήθος + + + Τύπος + + + Κατάσταση + + + Κατάσταση + + + Χρονική Σήμανση + + + Σημείωση + + + Ώρες + + + Αριθμός + + + Έναρξη + + + Λήξη + + + Ημερομηνία + + + Ρόλοι + + + Τοποθεσία + + + + + Αλλαγές Κατάστασης + + + Σύνολο + + + + + Τύποι Κλήσεων + + + Καταστάσεις Κλήσεων + + + Κλήσεις + + + Καταγράφηκε Στις + + + Έκλεισε Στις + + + Διάρκεια Κλήσης + + + Χρόνος στο Σημείο + + + Μονάδες + + + Προσωπικό + + + Ανοιχτή + + + % + + + + + Αναφορά Πιστοποιήσεων Resgrid + + + Αριθμός + + + Εκδόθηκε Από + + + Περιοχή + + + Λήγει + + + + + Αναφορά Δραστηριότητας Τμήματος από Αρχή Έτους + + + Λεπτομέρειες Ανταπόκρισης Προσωπικού + + + Εκπαιδεύσεις + + + Λεπτομέρειες Εκπαίδευσης Προσωπικού + + + Απεστάλη + + + Παρέστη + + + Ιανουάριος + + + Φεβρουάριος + + + Μάρτιος + + + Απρίλιος + + + Μάιος + + + Ιούνιος + + + Ιούλιος + + + Αύγουστος + + + Σεπτέμβριος + + + Οκτώβριος + + + Νοέμβριος + + + Δεκέμβριος + + + + + Αναφορά Συμβάντων Προσωπικού Resgrid + + + Προορισμός\Κλήση + + + + + Αναφορά Ωρών Προσωπικού + + + Αναλυτική Αναφορά Ωρών Προσωπικού + + + Ώρες Κλήσεων + + + Ώρες Εργασίας + + + Ώρες Εκπαίδευσης + + + Καταγραφές Εργασίας + + + + + Αναφορά Προσωπικού Resgrid + + + Όνομα Χρήστη + + + Email + + + Αριθμός Κινητού + + + Ταχυδρομική Διεύθυνση + + + + + Αναφορά Ιστορικού Στελέχωσης Προσωπικού + + + Αλλαγές Στελέχωσης + + + Στελέχωση + + + Προγραμματισμένη + + + + + Αναφορά Στελέχωσης Resgrid + + + Χρονική Σήμανση Στελέχωσης + + + Επόμενη Αλλαγή + + + Επόμενη Στελέχωση + + + Διαθέσιμος + + + Καθυστερημένος + + + Μη Διαθέσιμος + + + Δεσμευμένος + + + Σε Βάρδια + + + Άγνωστη + + + + + Αναφορά Συμβάντων Μονάδων Resgrid + + + + + Αναφορά Ιστορικού Καταστάσεων Μονάδων + + + Αλλαγές Κατάστασης + + + Σύνολο Καταστάσεων + + + + + Αναφορά Ετοιμότητας Προσεχών Βαρδιών + + + Έτοιμη + + + Μη Έτοιμη + + + Υποχρεωτικοί + + + Προαιρετικοί + + + Διαφορά + + + Ρόλος + + + Όνομα Δήλωσης + + + + + Αναφορά Ενεργών Κλήσεων και Πόρων + + + Όνομα Κλήσης + + + Μονάδες στην Κλήση + + + Προσωπικό στην Κλήση + + + Όνομα Μονάδας + + + Όνομα Ατόμου + + + Όνομα Ομάδας + + + Απεστάλη Στις + + + Ορόσημο 14 Ημερών + + + Ορόσημο 21 Ημερών + + + + + Καταγραφή Resgrid + + + Περιφέρεια + + + Τμήμα + + + Τύπος + + + Καταγράφηκε Στις + + + Καταγράφηκε Από + + + Ξεκίνησε Στις + + + Έληξε Στις + + + Προσωπικό + + + Αφήγηση + + + Δεν Δόθηκε + + + Εργασία + + + Αναγνωριστικό + + + Σταθμός + + + Μονάδες + + + Κατάσταση + + + Όνομα Κλήσης + + + Τύπος Κλήσης + + + Χωρίς Τύπο + + + Προτεραιότητα + + + Διεύθυνση Κλήσης + + + Απεστάλη + + + Καθ' Οδόν + + + Στο Σημείο + + + Αποδεσμεύτηκε + + + Στη Βάση + + + Έλεγχος + + + Εκπαίδευση\Μάθημα + + + Κωδικός + + + Εκπαιδευτές + + + Παρουσίες + + + Συνολικός Χρόνος + + + Τύπος Σύσκεψης + + + Τύπος Καταγραφής + + + Σύσκεψη + + + Τοποθεσία + + + Προεδρεύων + + + Άλλοι Συμμετέχοντες + + + Πρακτικά Σύσκεψης + + + Αρ. Υπόθεσης + + + Επικεφαλής + + + Προορισμός + + + Διαπίστωση Από + + + Τοποθεσία Σορού + + + Αναγνωριστικό Κλήσης + + + Αριθμός Κλήσης + + + Αναγνωριστικό Περιστατικού + + + Αναγνωριστικό Αναφοράς + + + Κανένα + + + Επανάκληση + + + + + Αναφορά Παρουσιών Συμβάντων + + + Εμφανίζει τις ώρες παρουσίας εθελοντών/συμβάντων ανά άτομο με παρακολούθηση άφιξης/αναχώρησης. + + + Αναφορά Παρουσιών Συμβάντων + + + Ώρες Παρουσίας σε Συμβάντα + + + Συμβάντα με Παρουσία + + + Συμβάντα με Απουσία + + + Συνολικές Ώρες + + + Λεπτομέρειες + + + Λεπτομέρειες Παρουσιών Συμβάντων + + + Συμβάν + + + Ώρα Άφιξης + + + Ώρα Αναχώρησης + + + Διάρκεια + + + Σημείωση Άφιξης + + + Σημείωση Αναχώρησης + + + Δήλωση Άφιξης Από + + + Δήλωση Αναχώρησης Από + + + Τοποθεσία Άφιξης + + + Τοποθεσία Αναχώρησης + + + Δεν έχει δηλωθεί αναχώρηση + + + Συνολικός Χρόνος + + diff --git a/Core/Resgrid.Localization/Areas/User/Routes/Routes.el.resx b/Core/Resgrid.Localization/Areas/User/Routes/Routes.el.resx new file mode 100644 index 000000000..3db005208 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Routes/Routes.el.resx @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Ακύρωση + + + Αποθήκευση + + + Όνομα + + + Περιγραφή + + + Κατάσταση + + + Ενέργειες + + + + Διαδρομές + + + Διαδρομές + + + Νέα Διαδρομή + + + Σε Εξέλιξη + + + Αρχειοθετημένες Διαδρομές + + + Στάσεις + + + Προφίλ + + + Δημιουργήθηκε + + + Προβολή + + + Επεξεργασία + + + Ιστορικό + + + Διαγραφή αυτού του σχεδίου διαδρομής; + + + + Ιστορικό Διαδρομών + + + Ιστορικό Διαδρομών + + + Ιστορικό + + + Μονάδα + + + Ξεκίνησε + + + Έληξε + + + Λεπτομέρεια + + + + Διαδρομές σε Εξέλιξη + + + Διαδρομές σε Εξέλιξη + + + Σε Εξέλιξη + + + Καμία Διαδρομή σε Εξέλιξη + + + Δεν υπάρχουν εκτελέσεις διαδρομών σε εξέλιξη αυτή τη στιγμή. + + + Αρχειοθετημένες Διαδρομές + + + Αρχειοθετημένες Διαδρομές + + + Αρχειοθετημένες + + + Καμία Αρχειοθετημένη Διαδρομή + + + Δεν υπάρχουν αρχειοθετημένα σχέδια διαδρομής. + + + Λεπτομέρειες Αρχειοθετημένης Διαδρομής + + + Μονάδα + + + Στάσεις + + + Ξεκίνησε + + + Προβολή Λεπτομερειών + + + + Νέα Διαδρομή + + + Νέο Σχέδιο Διαδρομής + + + Νέα + + + Λεπτομέρειες Διαδρομής + + + Μονάδα + + + -- Ανάθεση κατά την έναρξη -- + + + Πρόχειρη + + + Ενεργή + + + Σε Παύση + + + Αρχειοθετημένη + + + Χρώμα Διαδρομής + + + Προφίλ Διαδρομής + + + Οδήγηση + + + Πεζή + + + Ποδήλατο + + + Οδήγηση (Κυκλοφορία) + + + Ακτίνα Γεωφράχτη (μ) + + + Χρήση Σταθμού ως Αφετηρία + + + Χρήση Σταθμού ως Τερματισμό + + + Βελτιστοποίηση Σειράς Στάσεων + + + Χάρτης και Στάσεις + + + Οι στάσεις μπορούν να διαχειριστούν μετά τη δημιουργία της διαδρομής μέσω του API ή της φόρμας επεξεργασίας. + + + Δημιουργία Διαδρομής + + + + Έναρξη Διαδρομής + + + Έναρξη Διαδρομής + + + Έναρξη Διαδρομής + + + Ανάθεση Μονάδας + + + -- Επιλέξτε Μονάδα -- + + + Επιλέξτε τη μονάδα που θα εκτελέσει αυτή τη διαδρομή. + + + Πληροφορίες Διαδρομής + + + λεπτά εκτιμώμενα + + + Έναρξη Διαδρομής Τώρα + + + + Επεξεργασία Διαδρομής + + + Επεξεργασία Σχεδίου Διαδρομής + + + Επεξεργασία + + + # + + + Τύπος + + + Διεύθυνση + + + Προτεραιότητα + + + Αποθήκευση Αλλαγών + + + + Λεπτομέρειες Διαδρομής + + + Προβολή + + + Χάρτης Διαδρομής + + + Λεπτομέρειες + + + Κατάσταση + + + Προφίλ + + + Γεωφράχτης + + + Απόσταση + + + Διάρκεια + + + + Λεπτομέρειες Εκτέλεσης Διαδρομής + + + Εκτέλεση Διαδρομής + + + Λεπτομέρειες Εκτέλεσης + + + Πληροφορίες Εκτέλεσης + + + Ξεκίνησε + + + Έληξε + + + Πρόοδος + + + Χρονολόγιο Στάσεων + + + Εκκρεμής + + + Άφιξη + + + Ολοκληρώθηκε + + + Παραλείφθηκε + + + Άγνωστη + + + Άφιξη: + + + Αναχώρηση: + + + Παραμονή: + + + Παράλειψη: + + + Στάση + + + + Προσθήκη Στάσης + + + Χειροκίνητη + + + Προγραμματισμένη Κλήση + + + Σημείο Διαδρομής + + + Συνδεδεμένη Κλήση + + + Φόρτωση κλήσεων... + + + Κανονική + + + Υψηλή + + + Κρίσιμη + + + Προαιρετική + + + Εύρεση + + + What3Words + + + Τοποθεσία + + + Κάντε κλικ στον χάρτη για να ορίσετε τοποθεσία ή χρησιμοποιήστε τη διεύθυνση / What3Words παραπάνω. + + + Προγραμματισμένη Άφιξη + + + Προγραμματισμένη Αναχώρηση + + + Εκτ. Παραμονή (λεπ.) + + + Επιλογή Επαφής + + + -- Πληκτρολογήστε χειροκίνητα ή επιλέξτε υπάρχουσα -- + + + Η επιλογή επαφής συμπληρώνει αυτόματα το όνομα και τον αριθμό παρακάτω. + + + Όνομα Επαφής + + + Αριθμός Επαφής + + + Σημειώσεις + + + + Οδηγίες + + + Εκτύπωση + + + Έναρξη Διαδρομής + + + Αναλυτικές Οδηγίες Πλοήγησης + + + Υπολογισμός διαδρομής&#x2026; + + + Δεν ήταν δυνατή η ανάκτηση οδηγιών οδήγησης. Οι διευθύνσεις ή οι συντεταγμένες των στάσεων ενδέχεται να μην είναι προσβάσιμες οδικώς. Οι στάσεις παρατίθενται παρακάτω με τη σειρά. + + + Σύνοψη Στάσεων + + + Όνομα Στάσης + + + Παραμονή (λεπ.) + + + Επαφή + + + + Αυτή η διαδρομή είναι αρχειοθετημένη και δεν μπορεί να ξεκινήσει, να επεξεργαστεί ή να διαγραφεί. + + + Επιστροφή στις Αρχειοθετημένες Διαδρομές + + + + Τοποθεσία Έναρξης + + + Τοποθεσία Λήξης + + + Εισαγάγετε διεύθυνση + + + Κάντε κλικ στον χάρτη για να ορίσετε την τοποθεσία έναρξης ή χρησιμοποιήστε τη διεύθυνση / What3Words / συντεταγμένες παραπάνω. + + + Κάντε κλικ στον χάρτη για να ορίσετε την τοποθεσία λήξης ή χρησιμοποιήστε τη διεύθυνση / What3Words / συντεταγμένες παραπάνω. + + + Εφαρμογή + + + Γεωγραφικό Πλάτος + + + Γεωγραφικό Μήκος + + + Εύρεση τοποθεσίας μέσω + + diff --git a/Core/Resgrid.Localization/Areas/User/Security/Security.el.resx b/Core/Resgrid.Localization/Areas/User/Security/Security.el.resx new file mode 100644 index 000000000..28c4d9204 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Security/Security.el.resx @@ -0,0 +1,383 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ασφάλεια και Δικαιώματα + Εδώ μπορείτε να ορίσετε τα δικαιώματα του τμήματός σας, για παράδειγμα ποιοι χρήστες ή ρόλοι μπορούν να δημιουργούν κλήσεις ή ποιος είναι εξουσιοδοτημένος να δημιουργεί και να αφαιρεί χρήστες. Οι αλλαγές στα δικαιώματα θα τεθούν σε ισχύ κατά την επόμενη σύνδεση στη διαδικτυακή εφαρμογή Resgrid. + Αρχεία Ελέγχου + Δικαίωμα + Σημείωση + Τιμή + Ρόλοι + + + Επιβολή Ελέγχου Ταυτότητας Δύο Παραγόντων (2FA) + Απαιτήστε 2FA μέσω εφαρμογής ελέγχου ταυτότητας για τους διαχειριστές. Όταν είναι ενεργοποιημένο, οι διαχειριστές που δεν έχουν ρυθμίσει 2FA θα ανακατευθύνονται για εγγραφή πριν αποκτήσουν πρόσβαση σε διαχειριστικές λειτουργίες. + Απαίτηση 2FA για Διαχειριστές + Ελέγχει ποιοι χρήστες διαχειριστικού επιπέδου πρέπει να εγγραφούν σε 2FA μέσω εφαρμογής ελέγχου ταυτότητας (Google Authenticator, Microsoft Authenticator, Authy κ.λπ.). + Απενεργοποιημένη (καμία απαίτηση) + Διαχειριστές Τμήματος + Διαχειριστικός Χρήστης + Διαχειριστές Τμήματος + Διαχειριστικός Χρήστης + Διαχειριστές Ομάδων + Η ρύθμιση επιβολής 2FA αποθηκεύτηκε. + Αποτυχία αποθήκευσης της ρύθμισης επιβολής 2FA. + Δεν είναι δυνατή η ενεργοποίηση της επιβολής 2FA: ο διαχειριστικός χρήστης αυτού του τμήματος δεν έχει ενεργοποιημένο τον έλεγχο ταυτότητας δύο παραγόντων στον λογαριασμό του. Ο διαχειριστικός χρήστης πρέπει να ενεργοποιήσει το 2FA πριν ενεργοποιηθεί αυτή η ρύθμιση. + Δεν είναι δυνατή η ενεργοποίηση της επιβολής 2FA: δεν έχετε ενεργοποιημένο τον έλεγχο ταυτότητας δύο παραγόντων στον δικό σας λογαριασμό. Πρέπει να ενεργοποιήσετε το 2FA πριν το απαιτήσετε από άλλους. + Δεν είναι δυνατή η ενεργοποίηση της επιβολής 2FA: ούτε εσείς ούτε ο διαχειριστικός χρήστης έχετε ενεργοποιημένο τον έλεγχο ταυτότητας δύο παραγόντων. Και οι δύο πρέπει να ενεργοποιήσουν το 2FA πριν ενεργοποιηθεί αυτή η ρύθμιση. + Αυτή η ρύθμιση είναι κλειδωμένη μέχρι να πληρούνται οι παραπάνω προϋποθέσεις. + + + Δικαίωμα + Σημείωση + Επιλογή + Μόνο Ομάδα + Ρόλοι + Χωρίς Ρόλους + Μ/Δ + + + Ασφάλεια και Δικαιώματα + Εδώ μπορείτε να ορίσετε τα δικαιώματα του τμήματός σας, για παράδειγμα ποιοι χρήστες ή ρόλοι μπορούν να δημιουργούν κλήσεις ή ποιος είναι εξουσιοδοτημένος να δημιουργεί και να αφαιρεί χρήστες. Οι αλλαγές στα δικαιώματα θα τεθούν σε ισχύ κατά την επόμενη σύνδεση στη διαδικτυακή εφαρμογή Resgrid. + + + Ποιος μπορεί να Προσθέτει Χρήστες + Αυτή η επιλογή καθορίζει ποιος μπορεί να προσθέτει χρήστες/προσωπικό στο τμήμα. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος (και το διαχειριστικό μέλος) μπορούν να προσθέτουν χρήστες. Ωστόσο, μπορεί να επιτραπεί και στους Διαχειριστές Ομάδων να προσθέτουν χρήστες (περιορισμένα μόνο στην ομάδα της οποίας είναι διαχειριστές). + + Ποιος μπορεί να Αφαιρεί Χρήστες + Αυτή η επιλογή καθορίζει ποιος μπορεί να αφαιρεί χρήστες/προσωπικό από το τμήμα. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος (και το διαχειριστικό μέλος) μπορούν να αφαιρούν χρήστες. Ωστόσο, μπορεί να επιτραπεί και στους Διαχειριστές Ομάδων να αφαιρούν χρήστες (περιορισμένα μόνο στους χρήστες της ομάδας της οποίας είναι διαχειριστές). + + Ποιος μπορεί να Δημιουργεί Κλήσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί χειροκίνητα κλήσεις από το σύστημα Resgrid. Από προεπιλογή όλοι μπορούν να δημιουργούν κλήσεις. + + Ποιος μπορεί να Διαγράφει Κλήσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να διαγράφει κλήσεις από το σύστημα Resgrid. Από προεπιλογή όλοι μπορούν να διαγράφουν κλήσεις. + + Ποιος μπορεί να Κλείνει Κλήσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να κλείνει κλήσεις από το σύστημα Resgrid. Από προεπιλογή όλοι μπορούν να κλείνουν κλήσεις. + + Ποιος μπορεί να Προσθέτει Δεδομένα σε Κλήσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να προσθέτει δεδομένα, όπως εικόνες, σημειώσεις και αρχεία, σε κλήσεις από το σύστημα Resgrid. Από προεπιλογή όλοι μπορούν να προσθέτουν δεδομένα σε κλήσεις. + + Ποιος μπορεί να Δημιουργεί Εκπαιδεύσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί εκπαιδεύσεις. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος μπορούν να δημιουργούν εκπαιδεύσεις. + + Ποιος μπορεί να Προσθέτει Έγγραφα + Αυτή η επιλογή καθορίζει ποιος μπορεί να προσθέτει έγγραφα. Από προεπιλογή όλοι μπορούν να προσθέτουν έγγραφα. + + Ποιος μπορεί να Δημιουργεί Καταχωρίσεις Ημερολογίου + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί καταχωρίσεις ημερολογίου. Από προεπιλογή όλοι μπορούν να δημιουργούν καταχωρίσεις ημερολογίου. + + Ποιος μπορεί να Δημιουργεί Σημειώσεις + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί σημειώσεις. Από προεπιλογή όλοι μπορούν να δημιουργούν σημειώσεις. + + Ποιος μπορεί να Προσθέτει Καταχωρίσεις Καταγραφών + Αυτή η επιλογή καθορίζει ποιος μπορεί να προσθέτει καταχωρίσεις καταγραφών. Από προεπιλογή όλοι μπορούν να προσθέτουν καταχωρίσεις καταγραφών. + + Ποιος μπορεί να Δημιουργεί Βάρδιες + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί και να επεξεργάζεται βάρδιες. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος μπορούν να δημιουργούν και να επεξεργάζονται βάρδιες. + + Ποιος μπορεί να Βλέπει Προσωπικές Πληροφορίες + Αυτή η επιλογή καθορίζει ποιος μπορεί να βλέπει προσωπικές πληροφορίες (PII) για το προσωπικό στο σύστημα. Για παράδειγμα: Διεύθυνση Email, Αριθμοί Τηλεφώνου κ.λπ. Από προεπιλογή όλοι μπορούν να βλέπουν αυτές τις πληροφορίες. + + Ποιος μπορεί να Προσαρμόζει το Απόθεμα + Αυτή η επιλογή καθορίζει ποιος μπορεί να προσαρμόζει τα επίπεδα αποθέματος στο σύστημα. Από προεπιλογή όλοι μπορούν να προσαρμόζουν το απόθεμα. + + Ποιος μπορεί να βλέπει την Τοποθεσία του Προσωπικού + Αυτή η επιλογή καθορίζει ποιος μπορεί να βλέπει την τοποθεσία του προσωπικού στους χάρτες. Για να περιορίσετε την επιλογή μόνο σε διαχειριστές ομάδων και ρόλους εντός ομάδας, πρέπει να επιλέξετε την επιλογή Μόνο Ομάδα. + + Ποιος μπορεί να βλέπει την Τοποθεσία των Μονάδων + Αυτή η επιλογή καθορίζει ποιος μπορεί να βλέπει την τοποθεσία των μονάδων στους χάρτες. Για να περιορίσετε την επιλογή μόνο σε διαχειριστές ομάδων και ρόλους εντός ομάδας, πρέπει να επιλέξετε την επιλογή Μόνο Ομάδα. + + Ποιος μπορεί να στέλνει μηνύματα + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί και να στέλνει μηνύματα (εσωτερική αλληλογραφία). Από προεπιλογή όλοι μπορούν να δημιουργούν και να στέλνουν μηνύματα. + + Ποιος μπορεί να βλέπει χρήστες + Από προεπιλογή όλοι οι χρήστες μπορούν να βλέπουν όλους τους άλλους χρήστες στο σύστημα. Αυτή η επιλογή σας επιτρέπει να περιορίσετε ποιος μπορεί να βλέπει χρήστες στο σύστημα. + + Ποιος μπορεί να βλέπει μονάδες + Από προεπιλογή όλοι οι χρήστες μπορούν να βλέπουν όλες τις μονάδες στο σύστημα. Αυτή η επιλογή σας επιτρέπει να περιορίσετε ποιος μπορεί να βλέπει μονάδες στο σύστημα. + + Ποιος μπορεί να βλέπει Επαφές + Από προεπιλογή όλοι οι χρήστες μπορούν να βλέπουν όλες τις επαφές στο σύστημα. Αυτή η επιλογή σας επιτρέπει να περιορίσετε ποιος μπορεί να βλέπει επαφές στο σύστημα. Αν ένας χρήστης δεν μπορεί να βλέπει Επαφές, δεν θα μπορεί επίσης να τις προσθέτει σε κλήση. + + Ποιος μπορεί να επεξεργάζεται ή να δημιουργεί Επαφές + Από προεπιλογή όλοι οι χρήστες μπορούν να δημιουργούν και να επεξεργάζονται επαφές στο σύστημα. Αυτή η επιλογή σας επιτρέπει να περιορίσετε ποιος μπορεί να δημιουργεί ή να επεξεργάζεται επαφές στο σύστημα. + + Ποιος μπορεί να διαγράφει Επαφές + Από προεπιλογή όλοι οι χρήστες μπορούν να διαγράφουν επαφές στο σύστημα. Αυτή η επιλογή σας επιτρέπει να περιορίσετε ποιος μπορεί να διαγράφει επαφές στο σύστημα. + + Ποιος μπορεί να Δημιουργεί/Επεξεργάζεται Ροές Εργασίας + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί, να επεξεργάζεται και να διαγράφει ροές εργασίας και βήματα ροών εργασίας. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος μπορούν να διαχειρίζονται ροές εργασίας. + + Ποιος μπορεί να Διαχειρίζεται Διαπιστευτήρια Ροών Εργασίας + Αυτή η επιλογή καθορίζει ποιος μπορεί να δημιουργεί, να επεξεργάζεται και να διαγράφει κρυπτογραφημένα διαπιστευτήρια που χρησιμοποιούνται από ενέργειες ροών εργασίας (π.χ. κωδικούς SMTP, κλειδιά API). Από προεπιλογή μόνο οι Διαχειριστές Τμήματος μπορούν να διαχειρίζονται διαπιστευτήρια. + + Ποιος μπορεί να Βλέπει Εκτελέσεις Ροών Εργασίας + Αυτή η επιλογή καθορίζει ποιος μπορεί να βλέπει το ιστορικό εκτέλεσης ροών εργασίας, τα αρχεία εκτελέσεων και τους πίνακες υγείας. Από προεπιλογή μόνο οι Διαχειριστές Τμήματος μπορούν να βλέπουν εκτελέσεις ροών εργασίας. + + + Ενιαία Σύνδεση (SSO) και SCIM + Ενιαία Σύνδεση και Παροχή Ταυτοτήτων + SSO / SCIM + Πολιτική Ασφαλείας + Προσθήκη Διαμόρφωσης OIDC + Προσθήκη Διαμόρφωσης SAML 2.0 + Τι είναι το SSO και το SCIM; + Ενιαία Σύνδεση (SSO) + Επιτρέψτε στα μέλη του τμήματος να συνδέονται με την υπάρχουσα εταιρική τους ταυτότητα (Microsoft Entra ID, Okta, Google Workspace κ.λπ.) αντί για ξεχωριστό κωδικό πρόσβασης Resgrid. + OIDC — Σύγχρονο πρωτόκολλο, ιδανικό για Entra, Okta, Auth0, Google + SAML 2.0 — Ευρέως υποστηριζόμενο από κυβερνητικούς και εταιρικούς παρόχους ταυτότητας + Παροχή SCIM 2.0 + Συγχρονίστε αυτόματα χρήστες από τον πάροχο ταυτότητάς σας. Όταν εντάσσετε ή αποχωρεί προσωπικό στον εταιρικό σας κατάλογο, το Resgrid αντικατοπτρίζει αυτόματα αυτές τις αλλαγές — χωρίς χειροκίνητα βήματα πρόσκλησης/αφαίρεσης. + Αυτόματη δημιουργία νέων μελών όταν προστίθενται στον πάροχο ταυτότητάς σας + Απενεργοποίηση/αφαίρεση μελών κατά την κατάργηση παροχής + Διατήρηση ονομάτων και διευθύνσεων email σε συγχρονισμό + Πολιτική Ασφαλείας + Επιβάλετε ελέγχους συμμόρφωσης σε επίπεδο τμήματος παράλληλα με το SSO: + Επιβολή MFA για όλα τα μέλη + Περιορισμός σύνδεσης μόνο μέσω SSO (απενεργοποίηση κωδικών πρόσβασης) + Περιορισμός συνδέσεων σε συγκεκριμένα εύρη IP CIDR + Ορισμός κανόνων λήξης και πολυπλοκότητας κωδικών πρόσβασης + Ταξινόμηση επιπέδου δεδομένων (Αδιαβάθμητα / CUI / Εμπιστευτικά) + Διεύθυνση URL Ανακάλυψης Εφαρμογής Κινητών + Η εφαρμογή Resgrid για κινητά χρησιμοποιεί αυτή τη διεύθυνση URL για να ανακαλύψει τις ρυθμίσεις SSO σας πριν εμφανίσει την οθόνη σύνδεσης. Μοιραστείτε την με την ομάδα κινητών σας αν χρειάζεται. + Αντιγραφή + Αντιγράφηκε στο πρόχειρο! + Διαμορφώσεις SSO + Δεν υπάρχουν ακόμη διαμορφώσεις SSO. Χρησιμοποιήστε τα παραπάνω κουμπιά για να προσθέσετε OIDC ή SAML 2.0. + Πάροχος + Αναγνωριστικό / Τελικό Σημείο + Κατάσταση + Τοπική Σύνδεση + Αυτόματη Παροχή + SCIM + Δημιουργήθηκε + Ενέργειες + Ενεργοποιημένη + Απενεργοποιημένη + Ναι + Μόνο SSO + Ενεργή + Ανενεργή + Ενεργό + Χωρίς Διακριτικό + Ανενεργό + Επεξεργασία + SCIM + Επιβεβαίωση Διαγραφής + Είστε βέβαιοι ότι θέλετε να διαγράψετε τη διαμόρφωση SSO {0}; Αυτό δεν μπορεί να αναιρεθεί. + Ακύρωση + Διαγραφή + + + Νέα Διαμόρφωση SSO + Επεξεργασία Διαμόρφωσης SSO + Νέα + Επεξεργασία + Βήμα 1 + Πάροχος και Βασικές Ρυθμίσεις + Πρωτόκολλο Παρόχου Ταυτότητας + Ενεργοποίηση αυτής της διαμόρφωσης SSO + Μόνο μία διαμόρφωση ανά τύπο παρόχου είναι ενεργή κάθε φορά. + Να επιτρέπεται τοπική σύνδεση με κωδικό πρόσβασης + Όταν είναι επιλεγμένο, οι χρήστες μπορούν ακόμη να συνδέονται με όνομα χρήστη και κωδικό πρόσβασης επιπλέον του SSO. Απενεργοποιήστε το μαζί με τη σημαία Απαίτηση SSO της Πολιτικής Ασφαλείας για να επιβάλετε σύνδεση μόνο μέσω SSO. + Αυτόματη παροχή νέων χρηστών + Δημιουργήστε αυτόματα λογαριασμό Resgrid όταν ένας χρήστης ταυτοποιείται μέσω SSO για πρώτη φορά και δεν βρεθεί αντίστοιχο email. Αφήστε το ανενεργό για να απαιτείται πρώτα χειροκίνητη πρόσκληση. + Ενεργοποίηση παροχής SCIM 2.0 + Επιτρέπει στον πάροχο ταυτότητάς σας να δημιουργεί/ενημερώνει/απενεργοποιεί αυτόματα μέλη. Μετά την αποθήκευση, μεταβείτε στη σελίδα Ρύθμισης SCIM για να δημιουργήσετε διακριτικό bearer. + Προεπιλεγμένος Βαθμός για Χρήστες Αυτόματης Παροχής + Προαιρετικό. Εφαρμόζεται μόνο όταν η Αυτόματη Παροχή δημιουργεί νέο μέλος. + Βήμα 2 — OIDC + Ρυθμίσεις OpenID Connect + Πού θα βρείτε αυτές τις τιμές: Στον πάροχο ταυτότητάς σας, καταχωρίστε το Resgrid ως Δημόσιο Πελάτη (PKCE, χωρίς απαίτηση μυστικού πελάτη για κινητά) ή Διαδικτυακή Εφαρμογή (με μυστικό για ροές διακομιστή). Αντιγράψτε το Client ID και τη διεύθυνση URL Εκδότη από την καταχωρισμένη εφαρμογή. + Διεύθυνση URL Αρχής / Εκδότη + https://login.microsoftonline.com/{tenant-id}/v2.0 + Παραδείγματα: Entra ID: https://login.microsoftonline.com/{tenant-id}/v2.0 | Okta: https://{your-domain}.okta.com/oauth2/default | Google: https://accounts.google.com + Client ID + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + Το δημόσιο αναγνωριστικό πελάτη που εκχωρήθηκε από τον πάροχο ταυτότητάς σας. Ασφαλές για εμφάνιση — δεν είναι μυστικό. + Client Secret + Το μυστικό αποθηκεύτηκε + Κρυπτογραφημένο κατά την αποθήκευση με το κλειδί του τμήματός σας. Αφήστε το κενό αν χρησιμοποιείτε PKCE (κινητά/SPA — δεν απαιτείται μυστικό). + Αφήστε το κενό για να διατηρήσετε το υπάρχον μυστικό αμετάβλητο. + Βήμα 2 — SAML 2.0 + Ρυθμίσεις SAML 2.0 + Πού θα βρείτε αυτές τις τιμές: Στον πάροχο ταυτότητάς σας, δημιουργήστε νέα εφαρμογή SAML. Εισαγάγετε το SP Entity ID και τη διεύθυνση URL ACS που εμφανίζονται παρακάτω στον πάροχο ταυτότητάς σας και μετά επικολλήστε εδώ τη διεύθυνση URL μεταδεδομένων ή το πιστοποιητικό του παρόχου. + SP Entity ID (εισαγάγετέ το στον πάροχο ταυτότητάς σας) + Διεύθυνση URL ACS (Assertion Consumer Service — εισαγάγετέ την στον πάροχο ταυτότητάς σας) + Αυτή η διεύθυνση URL χρησιμοποιεί κρυπτογραφημένο διακριτικό τμήματος — δεν εκθέτει ποτέ τον απλό κωδικό του τμήματός σας. + SP Entity ID (επεξεργάσιμο) + https://app.resgrid.com/saml/DEPT + Πρέπει να ταιριάζει ακριβώς με ό,τι εισαγάγατε στον πάροχο ταυτότητας. + Διεύθυνση URL Μεταδεδομένων Παρόχου Ταυτότητας + https://idp.example.com/metadata.xml + Το Resgrid θα ανακτά περιοδικά αυτή τη διεύθυνση URL ώστε να παραμένει ενημερωμένο με τα πιστοποιητικά του παρόχου ταυτότητας. + Διεύθυνση URL ACS (αποθηκευμένη) + Συμπληρώνεται αυτόματα από την παραπάνω παραγόμενη διεύθυνση URL. Τροποποιήστε την μόνο αν διαμορφώσατε διαφορετική διεύθυνση URL ACS στον πάροχο ταυτότητάς σας. + Πιστοποιητικό Υπογραφής Παρόχου Ταυτότητας (PEM) + Το πιστοποιητικό αποθηκεύτηκε + Κρυπτογραφημένο κατά την αποθήκευση. Αφήστε το κενό για να διατηρήσετε το υπάρχον πιστοποιητικό. + Κλειδί Υπογραφής SP (PEM, προαιρετικό) + Το κλειδί αποθηκεύτηκε + Κρυπτογραφημένο κατά την αποθήκευση. Αφήστε το κενό αν ο πάροχος ταυτότητάς σας δεν απαιτεί υπογεγραμμένα αιτήματα. + Βήμα 3 + Αντιστοίχιση Χαρακτηριστικών + Αντιστοιχίστε τα ονόματα αξιώσεων που στέλνει ο πάροχος ταυτότητάς σας στα πεδία χρήστη του Resgrid. Αφήστε το κενό για αυτόματη χρήση των τυπικών URI αξιώσεων. Η τιμή είναι το όνομα της αξίωσης όπως εκδίδεται από τον πάροχο ταυτότητάς σας. + JSON Αντιστοίχισης Χαρακτηριστικών + Πρέπει να είναι έγκυρο JSON. Χρησιμοποιήστε τα κουμπιά γρήγορης συμπλήρωσης στα δεξιά. + Προρυθμίσεις γρήγορης συμπλήρωσης + Κάντε κλικ για αυτόματη συμπλήρωση του JSON αντιστοίχισης με γνωστές προεπιλογές για τον πάροχο ταυτότητάς σας. + Υποστηριζόμενα κλειδιά πεδίων Resgrid + Κλειδί + Περιγραφή + Διεύθυνση email του χρήστη + Όνομα + Επώνυμο + Μοναδικό υποκείμενο παρόχου ταυτότητας (για σύνδεση λογαριασμού) + Δημιουργία Διαμόρφωσης + Αποθήκευση Αλλαγών + Ακύρωση + + + Ρύθμιση SCIM 2.0 + Ρύθμιση Παροχής SCIM 2.0 + Ρύθμιση SCIM + Επιστροφή στο SSO + Σημαντικό — Αντιγράψτε τώρα το Διακριτικό Bearer SCIM σας + Αυτό το διακριτικό εμφανίζεται μόνο μία φορά και δεν μπορεί να ανακτηθεί ξανά. Αντιγράψτε το αμέσως και επικολλήστε το στη διαμόρφωση SCIM του παρόχου ταυτότητάς σας. Αν το χάσετε, κάντε κλικ στην Εναλλαγή Διακριτικού για να δημιουργήσετε νέο. + Αντιγραφή Διακριτικού + Αυτό το διακριτικό αποθηκεύεται κρυπτογραφημένο στη βάση δεδομένων Resgrid με το κλειδί κρυπτογράφησης του τμήματός σας. + Κατάσταση SCIM + Πάροχος + SCIM Ενεργοποιημένο + Ναι + Όχι + Διακριτικό Bearer + Διαμορφωμένο + Δεν Έχει Οριστεί + Εναλλαγή Διακριτικού SCIM + Δημιουργία Διακριτικού SCIM + Η εναλλαγή δημιουργεί νέο διακριτικό και ακυρώνει το παλιό. + Είστε βέβαιοι; Οποιαδήποτε υπάρχουσα ενσωμάτωση SCIM θα σταματήσει να λειτουργεί μέχρι να ενημερώσετε το διακριτικό στον πάροχο ταυτότητάς σας. + Ρυθμίσεις Συνδέσμου SCIM + Εισαγάγετε αυτές τις τιμές στη διαμόρφωση παροχής SCIM του παρόχου ταυτότητάς σας. + Ρύθμιση + Τιμή + Βασική Διεύθυνση URL Συνδέσμου SCIM + Μέθοδος Ελέγχου Ταυτότητας + Κεφαλίδα Εξουσιοδότησης + Όνομα Προσαρμοσμένης Κεφαλίδας + Τιμή Προσαρμοσμένης Κεφαλίδας + Υποστηριζόμενοι Πόροι + Υποστηριζόμενη Μέθοδος Ενημέρωσης + Οδηγός Ρύθμισης Βήμα προς Βήμα + Okta + Microsoft Entra ID + Google Workspace + Άλλο / Γενικό + Οποιοσδήποτε πελάτης συμβατός με SCIM 2.0 μπορεί να διαμορφωθεί με τα ακόλουθα: + Αντιστοίχιση πεδίων SCIM του Resgrid + Παράμετρος + Πεδίο SCIM + Επίδραση στο Resgrid + + + Παροχή SCIM 2.0 + Αναφορά API SCIM + Ο πάροχος ταυτότητάς σας καλεί αυτά τα τελικά σημεία χρησιμοποιώντας έλεγχο ταυτότητας HTTP Bearer Token. Όλα τα αιτήματα πρέπει να περιλαμβάνουν την κεφαλίδα εξουσιοδότησης και την κεφαλίδα αναγνωριστικού τμήματος που εμφανίζονται παρακάτω. + Τελικό Σημείο + Μέθοδος + Περιγραφή + Βασική Διεύθυνση URL + Κεφαλίδα Εξουσιοδότησης + Κεφαλίδα Τμήματος + Απαιτείται σε κάθε αίτημα ώστε το Resgrid να δρομολογεί στο σωστό τμήμα. + Παραδείγματα Αιτημάτων + Λίστα χρηστών (GET) + Παροχή χρήστη (POST) + Απενεργοποίηση χρήστη (PATCH) + Πλήρης ενημέρωση (PUT) + Λίστα / αναζήτηση χρηστών + Λήψη μεμονωμένου χρήστη βάσει αναγνωριστικού SCIM + Δημιουργία (παροχή) νέου χρήστη + Πλήρης ενημέρωση (αντικατάσταση όλων των χαρακτηριστικών) + Μερική ενημέρωση (π.χ. απενεργοποίηση) + Διαγραφή / κατάργηση παροχής χρήστη + Λίστα ομάδων (μόνο για ανάγνωση) + Παροχή Διακριτικού SCIM + Δημιουργήστε ή εναλλάξτε το διακριτικό bearer που χρησιμοποιεί ο πάροχος ταυτότητάς σας για την ταυτοποίηση αιτημάτων SCIM. Το διακριτικό εμφανίζεται μόνο μία φορά — αντιγράψτε το πριν κλείσετε αυτό το παράθυρο. + Παροχή Διακριτικού + Εναλλαγή Διακριτικού + Διακριτικό Bearer SCIM + Αυτό το διακριτικό εμφανίζεται μόνο μία φορά και δεν μπορεί να ανακτηθεί ξανά. Αντιγράψτε το τώρα και επικολλήστε το στη διαμόρφωση SCIM του παρόχου ταυτότητάς σας. + Η εναλλαγή του διακριτικού θα ακυρώσει άμεσα το τρέχον διακριτικό. Οποιοσδήποτε ενεργός συγχρονισμός SCIM θα αποτύχει μέχρι να ενημερώσετε το διακριτικό στον πάροχο ταυτότητάς σας. Συνέχεια; + Αυτό θα δημιουργήσει νέο διακριτικό bearer SCIM για αυτή τη διαμόρφωση. Αντιγράψτε και φυλάξτε το με ασφάλεια — θα εμφανιστεί μόνο μία φορά. Συνέχεια; + Αντιγραφή Διακριτικού + Αποθηκεύεται κρυπτογραφημένο στη βάση δεδομένων Resgrid με το κλειδί του τμήματός σας. + Πλήρης Οδηγός Ρύθμισης SCIM + Για οδηγίες βήμα προς βήμα ανά πάροχο ταυτότητας (Okta, Microsoft Entra ID, Google Workspace) και διαχείριση διακριτικών, επισκεφθείτε τη σελίδα Ρύθμισης SCIM για κάθε διαμόρφωση. + + + Πολιτική Ασφαλείας Τμήματος + Πολιτική Ασφαλείας Τμήματος + Πολιτική Ασφαλείας + Επιστροφή στο SSO + Η πολιτική ασφαλείας αποθηκεύτηκε με επιτυχία. + Δεν υπάρχει ενεργή διαμόρφωση SSO. Μπορείτε να διαμορφώσετε πολιτικές κωδικών πρόσβασης παρακάτω, αλλά η Απαίτηση SSO δεν μπορεί να ενεργοποιηθεί μέχρι να έχετε ενεργή διαμόρφωση SSO. + Διαμόρφωση SSO → + Έλεγχοι Ταυτοποίησης + Απαίτηση MFA για όλα τα μέλη + Τα μέλη που δεν έχουν εγγραφεί σε MFA θα κληθούν να το κάνουν κατά την επόμενη σύνδεση. + Απαίτηση SSO — απενεργοποίηση σύνδεσης με κωδικό πρόσβασης + Απαιτεί ενεργή διαμόρφωση SSO + Προειδοποίηση: Η ενεργοποίηση αυτού αποκλείει όλες τις συνδέσεις με όνομα χρήστη/κωδικό πρόσβασης. Βεβαιωθείτε ότι τουλάχιστον ένας διαχειριστής έχει δοκιμάσει επιτυχώς τη σύνδεση SSO πριν την ενεργοποίηση. + Λήξη Συνεδρίας (λεπτά) + 0 = χρήση προεπιλογής συστήματος. 480 = 8 ώρες. + Μέγιστες Ταυτόχρονες Συνεδρίες ανά Χρήστη + 0 = απεριόριστες. Τα κυβερνητικά περιβάλλοντα συνήθως ορίζουν 1. + Επιτρεπόμενα Εύρη IP (CIDR) + Ένα μπλοκ CIDR ανά γραμμή ή χωρισμένα με κόμμα. Κενό = να επιτρέπονται όλα. Οι συνδέσεις εκτός αυτών των ευρών θα απορρίπτονται. + Επίπεδο Ταξινόμησης Δεδομένων + Χρησιμοποιείται για αναφορές συμμόρφωσης και αρχεία ελέγχου. + Πολιτική Κωδικών Πρόσβασης + Οι πολιτικές κωδικών πρόσβασης ισχύουν μόνο για τοπικές συνδέσεις (εκτός SSO). Αν ενεργοποιήσετε την Απαίτηση SSO παραπάνω, αυτές οι ρυθμίσεις δεν έχουν αποτέλεσμα. + Λήξη Κωδικού Πρόσβασης (ημέρες) + 0 = οι κωδικοί πρόσβασης δεν λήγουν ποτέ. Το 90 είναι τυπικό για περιβάλλοντα CUI. + Ελάχιστο Μήκος Κωδικού Πρόσβασης + Ελάχιστο 8 (προεπιλογή συστήματος). Οι πολιτικές τμήματος μπορούν μόνο να το αυξήσουν. Το NIST συνιστά 12+· το CUI απαιτεί 14+. + Πολυπλοκότητα Κωδικού Πρόσβασης Επιβαλλόμενη από το Σύστημα + Όλοι οι λογαριασμοί Resgrid πρέπει να πληρούν το ακόλουθο πρότυπο πολυπλοκότητας κωδικού πρόσβασης. Αυτό δεν μπορεί να απενεργοποιηθεί από πολιτική τμήματος. + Τουλάχιστον 8 χαρακτήρες (ή το παραπάνω ελάχιστο μήκος του τμήματος) + Τουλάχιστον ένα ψηφίο (αριθμός) + Τουλάχιστον ένα κεφαλαίο και ένα πεζό γράμμα + Απαίτηση πολυπλοκότητας κωδικού πρόσβασης + Επιβάλλει τουλάχιστον ένα κεφαλαίο γράμμα, ένα ψηφίο και έναν ειδικό χαρακτήρα. + Γρήγορες προρυθμίσεις + Κυβερνητικό / CUI + Τυπικό Εταιρικό + Ελάχιστο + Αποθήκευση Πολιτικής Ασφαλείας + Ακύρωση + Δεν είναι δυνατή η ενεργοποίηση σύνδεσης μόνο μέσω SSO: δεν υπάρχει ενεργή διαμόρφωση SSO. Δημιουργήστε και ενεργοποιήστε πρώτα μια διαμόρφωση SSO. + + + Ο κωδικός πρόσβασης δεν μπορεί να είναι κενός. + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον ένα ψηφίο (αριθμό). + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον ένα κεφαλαίο γράμμα. + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον ένα πεζό γράμμα. + Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρων. + Το ελάχιστο μήκος κωδικού πρόσβασης δεν μπορεί να είναι μικρότερο από την προεπιλογή του συστήματος των 8 χαρακτήρων. + Διαγραφή Καταχωρίσεων Καταγραφών + Ποιος στο τμήμα σας επιτρέπεται να διαγράφει καταχωρίσεις καταγραφών + Χρήση Συγχρονισμού Ημερολογίου + Ελέγχει ποιος μπορεί να ενεργοποιεί και να χρησιμοποιεί διευθύνσεις URL συνδρομής ημερολογίου για συγχρονισμό συμβάντων ημερολογίου Resgrid με εξωτερικές εφαρμογές ημερολογίου. + Σύνδεση στην Εφαρμογή Dispatch + Ελέγχει ποιος μπορεί να συνδέεται στην εφαρμογή Dispatch. Το Dispatch εμφανίζει ιδιωτικές επικοινωνίες διοίκησης, μονάδων και ανταποκριτών για κάθε περιστατικό, οπότε περιορίστε το αν τα μέλη σας δεν είναι όλα διαβιβαστές. + Σύνδεση στην Εφαρμογή Command + Ελέγχει ποιος μπορεί να ενεργεί ως διοικητής: να συνδέεται στην εφαρμογή IC, να εγκαθιστά διοίκηση περιστατικού σε μια κλήση και να βλέπει πίνακες διοίκησης. Ο περιορισμός αυτού πέρα από το «Όλοι» επιτρέπει επίσης στα άτομα που επιλέγετε να βοηθούν στη λειτουργία οποιουδήποτε πίνακα διοίκησης (ανάθεση και μετακίνηση πόρων, εκτέλεση χρονομέτρων και λογοδοσίας) χωρίς να κατέχουν θέση ICS σε αυτόν &#8212; χρήσιμο για να βοηθούν οι διαβιβαστές στην εφαρμογή Dispatch. Όσο είναι ορισμένο σε «Όλοι», οι ενέργειες στον πίνακα παραμένουν περιορισμένες στον διοικητή περιστατικού και στους ανατεθειμένους ρόλους ICS. + + + diff --git a/Core/Resgrid.Localization/Areas/User/Shifts/Shifts.el.resx b/Core/Resgrid.Localization/Areas/User/Shifts/Shifts.el.resx new file mode 100644 index 000000000..93eb62211 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Shifts/Shifts.el.resx @@ -0,0 +1,636 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Διαγραφή Στατικής Βάρδιας + + + Η επεξεργασία μιας στατικής βάρδιας θα διαγράψει όλες τις αναθέσεις ή δηλώσεις συμμετοχής για αυτή τη βάρδια και θα τις ανακατανείμει βάσει των νέων ρυθμίσεων. Αν υπάρχουν αναθέσεις ή δηλώσεις για αυτή τη βάρδια, θα αφαιρεθούν και θα εφαρμοστούν οι νέες ρυθμίσεις. + + + Επεξεργασία Στατικής Βάρδιας + + + Ημερομηνία/Ώρα Λήξης + + + Σημειώστε ότι το κείμενο στο Ημερολόγιο είναι μαύρο· λάβετέ το υπόψη όταν επιλέγετε χρώμα βάρδιας, καθώς το χρώμα θα χρησιμοποιηθεί ως φόντο + + + Η ημερομηνία και ώρα λήξης της βάρδιας (δηλαδή επιστροφή στη βάση) όπου οι οντότητες που ανήκουν σε αυτή θεωρούνται διαθέσιμες. + + + Η ημερομηνία και ώρα έναρξης της βάρδιας (δηλαδή έναρξη μετακίνησης) όπου οι οντότητες που ανήκουν σε αυτή θεωρούνται δεσμευμένες. + + + Ημερομηνία/Ώρα Έναρξης + + + Προβολή Ημέρας Στατικής Βάρδιας + + + + Βάρδιες + + + Οι Βάρδιες Σας + + + Στελέχωση Βάρδιας + + + Επαναλαμβανόμενες Ομαδικές Βάρδιες + + + Προσθήκη Επαναλαμβανόμενης Ομαδικής Βάρδιας + + + Όνομα + + + Τύπος + + + Πρόγραμμα + + + Ομάδες + + + Προσωπικό + + + Ανατεθειμένη + + + Δήλωση Συμμετοχής + + + Χειροκίνητο + + + Προσαρμοσμένο + + + 48 σε υπηρεσία, 96 εκτός + + + 24 σε υπηρεσία, 48 εκτός + + + 24 σε υπηρεσία, 72 εκτός + + + Προβολή Ημερολογίου + + + Επεξεργασία Λεπτομερειών + + + Επεξεργασία Ημερολογίου + + + Επεξεργασία Ομάδων + + + Διαγραφή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή τη βάρδια. Είστε βέβαιοι ότι θέλετε να διαγράψετε τη βάρδια + + + Ημερολόγιο + + + Προσθήκη Στατικής Βάρδιας + + + + Νέα Βάρδια + + + Καλώς ήρθατε στον οδηγό νέας Βάρδιας. Αυτός ο οδηγός θα σας καθοδηγήσει στη δημιουργία νέας βάρδιας για το τμήμα σας. Παρακάτω ορίστε το όνομα, τον κωδικό και το προαιρετικό χρώμα της βάρδιας. Για να συνεχίσετε, κάντε κλικ στο κουμπί «Επόμενο» στην κάτω δεξιά γωνία. + + + Όνομα + + + Όνομα της Βάρδιας + + + Για παράδειγμα («Βάρδια Α», «Βάρδια Β», «Βάρδια Γ») + + + Κωδικός + + + Παράδειγμα («Α», «Β», «Γ») + + + Τύπος Ανάθεσης + + + Για ανατεθειμένη ορίζετε εσείς το προσωπικό της βάρδιας· για δήλωση συμμετοχής το προσωπικό δηλώνει μόνο του + + + Χρώμα + + + Παρακάτω ορίζετε ποιες ημέρες η βάρδια θα είναι σε υπηρεσία και την ώρα έναρξης της βάρδιας για την πρώτη ημέρα. Αν η βάρδιά σας είναι συνεχής, η ώρα έναρξης αφορά την πρώτη ημέρα της βάρδιας (δηλαδή την Ημέρα 1 ενός 48ωρου μπλοκ). + + + Ώρα Έναρξης + + + Πότε ξεκινά η βάρδια για την πρώτη ημέρα (ή τη μοναδική ημέρα) + + + Ώρα Λήξης + + + Πότε λήγει η βάρδια για την τελευταία ημέρα (ή τη μοναδική ημέρα) + + + Εδώ μπορείτε να διαμορφώσετε τους απαιτούμενους ρόλους για κάθε ομάδα του τμήματός σας για τη βάρδια. Κάντε κλικ στο κουμπί «Προσθήκη ομάδας στη βάρδια» για να προσθέσετε ομάδα στη βάρδια· η βάρδιά σας μπορεί να έχει πολλές ομάδες, τόσο σταθμού όσο και οργανωτικές. Αφού επιλέξετε την ομάδα (από την αριστερή αναπτυσσόμενη λίστα), μπορείτε να κάνετε κλικ στο κουμπί «Προσθήκη ρόλου στην Ομάδα» για να προσθέσετε τους απαιτούμενους ρόλους για την ομάδα. + + + Ομάδα Βάρδιας + + + Ρόλοι Βάρδιας + + + Προσθήκη Ομάδας στη Βάρδια + + + Τώρα επιλέξτε το προσωπικό που εργάζεται σε αυτή τη βάρδια για το τμήμα σας. Αυτό το προσωπικό θα καλύψει δυναμικά τη θέση ρόλου ομάδας βάσει της ομάδας στην οποία ανήκει, εφόσον έχουν οριστεί ρόλοι ομάδας βάρδιας. + + + Προσωπικό Εκτός Ομάδας + + + Προσωπικό που έχει ανατεθεί στη βάρδια αλλά δεν έχει ανατεθεί άμεσα σε σταθμό ή οργανωτική ομάδα (δηλαδή μετακινούμενοι ή εφεδρείες) + + + Κάντε κλικ στο κουμπί ολοκλήρωσης παρακάτω για να δημιουργήσετε τη νέα σας βάρδια. Αν χρειαστεί να προσθέσετε περισσότερες ημέρες ή να αλλάξετε λεπτομέρειες, μπορείτε πάντα να επεξεργαστείτε τη βάρδια αργότερα από τη σελίδα λίστας Βαρδιών. + + + Έναρξη > + + + Πρόγραμμα > + + + Θέσεις > + + + Προσωπικό > + + + Ολοκλήρωση + + + Πρώτο + + + Προηγούμενο + + + Επόμενο + + + Το Όνομα Βάρδιας είναι υποχρεωτικό + + + Ο Κωδικός Βάρδιας είναι υποχρεωτικός + + + Η ώρα έναρξης της βάρδιας είναι υποχρεωτική. + + + + Επεξεργασία Λεπτομερειών Βάρδιας + + + Κωδικός Βάρδιας + + + Προσωπικό που αποτελεί μέρος μιας βάρδιας αλλά δεν έχει ανατεθεί σε ομάδα (δηλαδή μετακινούμενοι ή περιφερόμενο προσωπικό) + + + Ακύρωση + + + Ενημέρωση Βάρδιας + + + + Επεξεργασία Ημερολογίου Βάρδιας + + + Ενημέρωση Ημερών Βάρδιας + + + + Επεξεργασία Ομάδων Βάρδιας + + + Αποθήκευση Ομάδων Βάρδιας + + + + Ημερολόγιο + + + + Στελέχωση Βάρδιας + + + Βάρδια + + + Επιλέξτε τη βάρδια για την οποία θα επεξεργαστείτε τη στελέχωση. Θα βλέπετε μόνο τις βάρδιες στις οποίες έχετε πρόσβαση (είστε Διαχειριστής Ομάδας ή Διαχειριστής Τμήματος) + + + Ημέρα Βάρδιας + + + Επιλέξτε την ημέρα για την οποία θα επεξεργαστείτε τη στελέχωση. + + + Σημείωση + + + Εισαγάγετε ελεύθερη σημείωση για τη βάρδια (π.χ. στελέχωση προσωπικού, αλλαγές στη μέση της βάρδιας κ.λπ.) + + + Αναθέσεις + + + Μονάδες + + + Ορισμός Στελέχωσης Ημέρας Βάρδιας + + + Επιλέξτε Προσωπικό Εκτός Ομάδας... + + + Επιλέξτε Προσωπικό για την Ομάδα... + + + Επιλέξτε Άτομο... + + + Προσωπικό Εκτός Ομάδας + + + + Οι Βάρδιες Σας + + + Βάρδια + + + Ομάδα + + + Ημέρα + + + Εγκεκριμένη + + + Κατάσταση + + + Χρονική Σήμανση + + + Όχι + + + Ναι + + + Η Ανταλλαγή Ολοκληρώθηκε + + + Ανταλλαγή σε Εξέλιξη + + + Κανονική + + + για + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα σας αφαιρέσει από αυτή τη βάρδια. Είστε βέβαιοι ότι θέλετε να απορρίψετε τη βάρδια + + + Απόρριψη Ημέρας Βάρδιας + + + Αίτημα Ανταλλαγής + + + Ολοκλήρωση Ανταλλαγής + + + Οι Εκκρεμείς Ανταλλαγές Σας + + + Ώρα + + + Επεξεργασία Αιτήματος Ανταλλαγής + + + Απορρίφθηκε + + + Έγινε Αποδεκτή + + + Καλύφθηκε + + + Προτάθηκε Ανταλλαγή + + + + Προβολή Βάρδιας + + + Έναρξη Βάρδιας + + + Λεπτομέρειες Ημέρας Βάρδιας + + + Τύπος: + + + Κατάσταση: + + + Έναρξη: + + + Λήξη: + + + Προσεχής + + + Ολοκληρωμένη + + + Χωρίς Ώρα Λήξης + + + Ομάδες + + + Ρόλος + + + Υποχρεωτικός + + + Προαιρετικός + + + Απαιτούνται + + + Δηλώσεις Συμμετοχής + + + Ρόλοι + + + Ανταλλαγές + + + Ανταλλαγή + + + {0} αντάλλαξε με {1} + + + + Δήλωση Συμμετοχής σε Βάρδια + + + Διαγραφή Δήλωσης Συμμετοχής σε Βάρδια + + + Δήλωση συμμετοχής στη Βάρδια με + + + + Επιτυχής Δήλωση Συμμετοχής σε Βάρδια + + + Δηλώσατε επιτυχώς συμμετοχή σε αυτή την ημέρα βάρδιας. Αν θέλετε να δηλώσετε συμμετοχή σε περισσότερες ημέρες της βάρδιας, πρέπει να δηλώσετε συμμετοχή για κάθε ημέρα ξεχωριστά. + + + Ξεκινά στις {0} και Λήγει στις {1} + + + Ξεκινά στις {0} + + + + Αίτημα Ανταλλαγής + + + Χρήστες προς Αίτημα + + + Κάντε κλικ μέσα στο πεδίο για να επιλέξετε σε ποιον θα ζητήσετε ανταλλαγή (εμφανίζονται μόνο χρήστες που πληρούν τις ανάγκες ρόλων της βάρδιας) + + + Υποβολή Αιτήματος Ανταλλαγής + + + + Επεξεργασία Αιτήματος Ανταλλαγής + + + Ημερομηνίες προς Ανταλλαγή + + + Κάντε κλικ μέσα στο πεδίο για να επιλέξετε τις βάρδιες στις οποίες ήδη συμμετέχετε ώστε να ολοκληρωθεί η ανταλλαγή. Αν δεν ορίσετε ημέρα, πρόκειται για μη ισοσκελισμένη ανταλλαγή. + + + Μια σημείωση ή ενημέρωση για αυτή την ανταλλαγή + + + Απόρριψη Αιτήματος Ανταλλαγής + + + Είστε βέβαιοι ότι θέλετε να απορρίψετε αυτό το αίτημα ανταλλαγής; + + + Πρόταση Αιτήματος Ανταλλαγής + + + Ξεκινά στις + + + + Οριστικοποίηση Ανταλλαγής + + + Ημερομηνία + + + Καμία + + + Οριστικοποίηση Ανταλλαγής + + + + Ρόλος Βάρδιας + + + Πλήθος Ρόλων + + + Προσθήκη Ρόλου στην Ομάδα + + + Προσθήκη Ρόλων Βάρδιας στην Ομάδα + + + Αφαίρεση Ομάδας + + + Αφαίρεση Ρόλου + + + Αφαίρεση αυτού του ρόλου από την ομάδα + + + Το πλήθος ρόλων είναι υποχρεωτικό + + diff --git a/Core/Resgrid.Localization/Areas/User/Subscription/Subscription.el.resx b/Core/Resgrid.Localization/Areas/User/Subscription/Subscription.el.resx new file mode 100644 index 000000000..09b4e6cc3 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Subscription/Subscription.el.resx @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Επιτυχής Ενημέρωση Χρέωσης + + + Σας ευχαριστούμε που ενημερώσατε τα στοιχεία χρέωσής σας. Η νέα σας κάρτα δεν χρεώθηκε τώρα, αλλά θα χρησιμοποιηθεί όταν ανανεωθεί η συνδρομή στον κανονικό της χρόνο. + + + Αγορά Πρόσθετου + + + Αγοράζετε το ακόλουθο πρόσθετο + + + Η παραπάνω αγορά πρόσθετου θα προστεθεί στην υπάρχουσα συνδρομή σας και θα κατανεμηθεί αναλογικά για τον χρόνο που απομένει στην τρέχουσα συχνότητα χρέωσής σας. Θα χρεωθεί η τρέχουσα κάρτα σας· βεβαιωθείτε ότι είναι ενημερωμένη για να αποφύγετε διακοπή υπηρεσίας. + + + Προειδοποίηση + + + Έχετε ήδη ακυρωμένο πρόσθετο αυτού του τύπου με χρόνο που απομένει. Για να αποφύγετε διπλή χρέωση, περιμένετε μέχρι εκείνη τη στιγμή για να προσθέσετε ξανά αυτό το πρόσθετο. Αν δεν θέλετε να περιμένετε, μπορείτε να το προσθέσετε τώρα και ένα μέρος του χρόνου που απομένει μπορεί να κατανεμηθεί αναλογικά. Ο χρόνος που απομένει λήγει στις + + + Αγορά Τώρα + + + Αγορά Τώρα + + + Αγοράζετε το ακόλουθο πακέτο + + + για + + + έτος + + + μήνα + + + Η επόμενη πληρωμή σας θα είναι στις + + + Μεταβαίνετε από ετήσιο σε μηνιαίο πακέτο και διατηρείτε την πίστωση από το ετήσιο πακέτο σας. Μόλις εξαντληθεί αυτή η πίστωση, θα χρεώνεστε με κανονικό μηνιαίο πρόγραμμα. + + + Το εκτιμώμενο σύνολο αναβάθμισής σας είναι + + + Εκτιμώμενο κόστος· το πραγματικό κόστος μπορεί να διαφέρει ελαφρώς. + + + Διεύθυνση Χρέωσης Πιστωτικής Κάρτας + + + Διεύθυνση οδού χρέωσης της πιστωτικής κάρτας + + + Πρέπει να ταιριάζει με τη διεύθυνση στην οποία αποστέλλονται οι λογαριασμοί της πιστωτικής κάρτας + + + Ταχυδρομικός κώδικας χρέωσης + + + Χώρα χρέωσης + + + Οι πληροφορίες πληρωμής προστατεύονται με κρυπτογράφηση SSL. <b>Το Resgrid ΔΕΝ αποθηκεύει στοιχεία πληρωμής.</b> + + + Όνομα στην κάρτα + + + Επώνυμο στην κάρτα + + + Πλήρης αριθμός πιστωτικής κάρτας + + + Αριθμός CVV/CVC + + + 3ψήφιος ή 4ψήφιος αριθμός στην κάρτα + + + περισσότερες πληροφορίες + + + Λήξη Πιστωτικής Κάρτας + + + Ακύρωση Συνδρομής + + + Λυπούμαστε που ακυρώνετε τη συνδρομή σας. Συμπληρώστε την παρακάτω φόρμα για να ολοκληρώσετε το αίτημα ακύρωσης της συνδρομής σας. Το πεδίο αιτιολογίας είναι προαιρετικό· βεβαιωθείτε ότι έχετε επιλέξει το πλαίσιο επιβεβαίωσης και πατήστε OK στο μήνυμα επιβεβαίωσης του προγράμματος περιήγησης όταν εμφανιστεί. + + + Το τμήμα σας και όλα τα δεδομένα σας δεν θα αφαιρεθούν· θα ακυρωθεί μόνο η ανανέωση της συνδρομής σας. Για τον υπόλοιπο χρόνο της συνδρομής σας θα εξακολουθείτε να έχετε πρόσβαση στις υπάρχουσες δυνατότητες αυτού του πακέτου. + + + Πακέτο + + + Τρέχον Πακέτο + + + Αγοράστηκε Στις + + + Επιβεβαίωση + + + Αιτιολογία + + + Επιστροφή στις Συνδρομές + + + Ακύρωση Συνδρομής + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα ακυρώσει την ενεργή συνδρομή σας. Είστε βέβαιοι; + + + Αποτυχία Ακύρωσης + + + Δεν μπορέσαμε να ακυρώσουμε τη συνδρομή σας. Αυτό μπορεί να οφείλεται στο ότι δεν έχετε ενεργή συνδρομή (δηλαδή είστε στο δωρεάν πακέτο) ή σε προσωρινό πρόβλημα με τον πάροχο πληρωμών. Δοκιμάστε ξανά σε λίγα λεπτά. Αν το πρόβλημα επιμείνει, επικοινωνήστε μαζί μας για να το επιλύσουμε. + + + Η Συνδρομή Ακυρώθηκε + + + Η συνδρομή σας ακυρώθηκε. Μπορεί να χρειαστούν λίγα λεπτά για να το αντικατοπτρίσει το σύστημα, αλλά η κάρτα σας δεν θα χρεώνεται πλέον. Θα διατηρήσουμε τα δεδομένα σας ασφαλή σε περίπτωση που θέλετε να επιστρέψετε ή να χρησιμοποιήσετε το δωρεάν πακέτο Resgrid. + + + Πακέτο Συνδρομής και Χρέωση + + + Προβολή Ιστορικού Πληρωμών + + + Αλλαγή Στοιχείων Χρέωσης + + + Ακυρωμένη + + + Ενεργή + + + Το Πακέτο Λήγει + + + Το Πακέτο Ανανεώνεται + + + Προσωπικό + + + Ομάδες + + + Μονάδες + + + Προειδοποίηση + + + Standard + + + Premium + + + Professional + + + Ultimate + + + Enterprise + + + Unified + + + Εισερχόμενα SMS + + + Τηλεφωνική Ειδοποίηση + + + Τιμή + + + ανά έτος + + + ανά μήνα + + + Ποσό + + + Αγοράζετε το ακόλουθο πακέτο + + + Μηνιαία Αγορά + + + Διατίθενται επίσης μεγαλύτερα πακέτα, ιδιωτική φιλοξενία, υποστήριξη προτεραιότητας, υποστήριξη ανοιχτού κώδικα και υπηρεσίες υλοποίησης. + + + για περισσότερες πληροφορίες. + + + Ετήσια Αγορά + + + Αν επιθυμείτε να πραγματοποιήσετε την αλλαγή στη συνδρομή σας, πατήστε το κουμπί «Αλλαγή Πακέτου» παρακάτω. Ο πάροχος πληρωμών μας θα υπολογίσει την αναλογική κατανομή του χρόνου του τρέχοντος πακέτου σας, θα καθορίσει το οφειλόμενο υπόλοιπο και θα χρεώσει την κάρτα σας (αν χρειάζεται). Θα λάβετε email με τις λεπτομέρειες μόλις ολοκληρωθεί η αλλαγή. + + + Επικοινωνήστε μαζί μας + + + Κόστος + + + Πελάτης + + + Τμήμα + + + Περιγραφή + + + Ημερομηνία Λήξης + + + Τιμολόγιο + + + Ημερομηνία Τιμολογίου + + + Μέθοδος + + + Σας ευχαριστούμε για την αγορά σας. Η πληρωμή σας ολοκληρώθηκε και το πρόσθετο ή η αναβάθμιση πακέτου θα είναι σύντομα διαθέσιμα. Ίσως χρειαστεί να αποσυνδεθείτε και να συνδεθείτε ξανά για να δείτε τις αλλαγές. + + + Η Πληρωμή Ολοκληρώθηκε + + + Λυπούμαστε, αλλά παρουσιάστηκε πρόβλημα κατά την επεξεργασία της πληρωμής σας. Πατήστε το κουμπί επιστροφής στο πρόγραμμα περιήγησής σας και επαληθεύστε τα στοιχεία πληρωμής σας (δηλαδή ταχυδρομικό κώδικα, αριθμό CCV, ημερομηνία λήξης). + + + Η Πληρωμή Απέτυχε + + + Ιστορικό Πληρωμών + + + Λόγω υψηλότερου από το αναμενόμενο όγκου, η πληρωμή σας βρίσκεται αυτή τη στιγμή σε ουρά. Μπορείτε να φύγετε από αυτή τη σελίδα και να χρησιμοποιείτε κανονικά το Resgrid. Μόλις επεξεργαστούμε την πληρωμή, ο λογαριασμός σας θα αναβαθμιστεί και θα λάβετε email από εμάς. Αν δεν λάβετε email εντός 30 λεπτών, επικοινωνήστε μαζί μας στο team@resgrid.com. + + + Η Πληρωμή Εκκρεμεί + + + Σας ευχαριστούμε για την αγορά σας. Η πληρωμή σας βρίσκεται σε επεξεργασία. Περιμένετε μέχρι να ολοκληρώσει ο πάροχος πληρωμών μας και το τμήμα σας θα αναβαθμιστεί. + + + Επεξεργασία Πληρωμής + + + PayPal + + + Περίοδος + + + Πάροχος + + + Ημερομηνία Αγοράς + + + Ημερομηνία Έναρξης + + + Stripe + + + Σας ευχαριστούμε που ενημερώσατε τα στοιχεία χρέωσής σας. Η νέα σας κάρτα δεν χρεώθηκε τώρα, αλλά θα χρησιμοποιηθεί όταν ανανεωθεί η συνδρομή στον κανονικό της χρόνο. + + + Επιτυχής Ενημέρωση Χρέωσης + + + Σας ευχαριστούμε για την αγορά σας. Η πληρωμή σας βρίσκεται σε επεξεργασία. Περιμένετε μέχρι να ολοκληρώσει το Stripe, ο πάροχος πληρωμών μας, και το τμήμα σας θα αναβαθμιστεί. + + + Επεξεργασία Πληρωμής + + + Σύστημα + + + Σας Ευχαριστούμε! + + + Σύνολο + + + Συναλλαγή + + + Αναγνωριστικό Συναλλαγής + + + Σας ευχαριστούμε για το ενδιαφέρον σας να αγοράσετε πακέτο για την υπηρεσία Resgrid. Έχουμε ήδη ένα προσεχές πακέτο για το τμήμα σας και επιτρέπουμε μόνο 1 μελλοντικό πακέτο ανά πελάτη. Αν θέλετε να αγοράσετε πολλαπλά έτη, επικοινωνήστε μαζί μας και θα χαρούμε να σας εξυπηρετήσουμε. + + + Δεν Είναι Δυνατή η Αγορά + + + Προσεχές Πακέτο + + + Ενημέρωση Στοιχείων Χρέωσης + + + Αλλαγή Πακέτου + + + Προβολή Τιμολογίου + + + Βρίσκεστε αυτή τη στιγμή στο ακόλουθο πακέτο + + + Διαχειρίζεστε το Πρόσθετο PTT + + + Στο παρακάτω πλαίσιο αριθμού ορίστε το πλήθος των πακέτων 10 Ταυτόχρονων Χρηστών Push-To-Talk που επιθυμείτε να αγοράσετε σε μηνιαία βάση. Αν αυξήσετε τον αριθμό, θα χρεωθείτε άμεσα· αν τον μειώσετε, η ανανέωσή σας θα γίνει με το μειωμένο πλήθος. Αν ορίσετε μηδέν, δεν θα χρεωθείτε (το PTT δεν θα ανανεωθεί). Για αυτή τη Συνδρομή Πρόσθετου PTT θα χρησιμοποιηθεί η κάρτα που έχετε καταχωρίσει για την κανονική σας συνδρομή Resgrid. + + + Διαχειριστείτε τη Συνδρομή Σας + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Templates/Templates.el.resx b/Core/Resgrid.Localization/Areas/User/Templates/Templates.el.resx new file mode 100644 index 000000000..1c8a4e940 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Templates/Templates.el.resx @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Προτύπου Σημείωσης Κλήσης + + + Όνομα Κλήσης προς Ορισμό + + + Φύση Κλήσης προς Ορισμό + + + Σημειώσεις Κλήσης + + + Πρότυπα Σημειώσεων Κλήσης + + + Προτεραιότητα Κλήσης προς Ορισμό + + + Τύπος Κλήσης προς Ορισμό + + + Επεξεργασία Σημείωσης Κλήσης + + + Επεξεργασία Προτύπου Σημείωσης Κλήσης + + + Επεξεργασία Προτύπου + + + Νέο Πρότυπο Σημείωσης Κλήσης + + + Νέο Πρότυπο + + + Κείμενο Σημείωσης + + + Όνομα Προτύπου + + + Προσθήκη Προτύπου + + + Σημείωση Κλήσης + + + Γρήγορο Πρότυπο Κλήσης + + + Ενεργοποίηση Χρονομέτρων Παρουσίας + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/Trainings/Trainings.el.resx b/Core/Resgrid.Localization/Areas/User/Trainings/Trainings.el.resx new file mode 100644 index 000000000..a83b386a8 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Trainings/Trainings.el.resx @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Αρχική + + + Εκπαιδεύσεις + + + Νέα Εκπαίδευση + + + Προσθήκη Εκπαίδευσης + + + Επεξεργασία Εκπαίδευσης + + + Αποθήκευση Εκπαίδευσης + + + Όνομα εκπαίδευσης + + + Περιγραφή + + + Κείμενο Εκπαίδευσης + + + Ελάχιστη Βαθμολογία + + + Ημερομηνία Λήξης + + + Υπάρχοντα Συνημμένα + + + Προσθήκη Συνημμένων + + + Συνημμένα + + + Τρέχουσες Αναθέσεις + + + {0} χρήστης/χρήστες έχουν ανατεθεί αυτή τη στιγμή σε αυτή την εκπαίδευση. + + + Προσθήκη όλου του Προσωπικού + + + Προσθήκη Ομάδων + + + Προσθήκη Ρόλων + + + Προσθήκη Χρηστών + + + Ομάδες προς Προσθήκη + + + Ρόλοι προς Προσθήκη + + + Χρήστες προς Προσθήκη + + + Ερωτήσεις + + + Οι ερωτήσεις είναι προαιρετικές· αν θέλετε απλώς το προσωπικό σας να δει την εκπαίδευση, αυτό θα καταγραφεί χωρίς ερωτήσεις. + + + Ερώτηση + + + Απαντήσεις + + + Προσθήκη Ερώτησης + + + Αφαίρεση + + + Σωστή + + + Ακύρωση + + + Εκπαιδεύσεις + + + Προσθήκη Εκπαίδευσης + + + Όνομα + + + Περιγραφή + + + Ημερομηνία Λήξης + + + Ενέργεια + + + Χωρίς Ημερομηνία Λήξης + + + Προβολή Εκπαίδευσης + + + Η Εκπαίδευση Ολοκληρώθηκε + + + Επεξεργασία + + + Αναφορά + + + Διαγραφή + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή την εκπαίδευση. Είστε βέβαιοι ότι θέλετε να διαγράψετε την εκπαίδευση + + + Προβολή Εκπαίδευσης + + + Κατάσταση: + + + Ενεργή + + + Δημιουργήθηκε Από: + + + Προθεσμία: + + + Δημιουργήθηκε Στις: + + + Συνημμένα Εκπαίδευσης + + + Παρακάτω είναι τα αρχεία (συνημμένα) αυτής της εκπαίδευσης. Κατεβάστε, ανοίξτε και μελετήστε όλα αυτά τα συνημμένα καθώς και το κείμενο της εκπαίδευσης. Πληροφορίες από αυτά τα συνημμένα μπορεί να αποτελέσουν ερωτήσεις στο κουίζ της εκπαίδευσης. + + + Αρχεία εκπαίδευσης + + + Κουίζ Εκπαίδευσης + + + Αυτή η εκπαίδευση διαθέτει κουίζ. Μελετήστε όλο το κείμενο της εκπαίδευσης και όλα τα συνημμένα πριν συμμετάσχετε στο κουίζ. + + + Συμμετοχή στο Κουίζ + + + Κουίζ Εκπαίδευσης + + + Εκπαίδευση + + + Έναρξη > + + + Ερώτηση + + + Ολοκλήρωση + + + Καλώς ήρθατε στο κουίζ της εκπαίδευσης ({0}). Επιλέξτε τη σωστή απάντηση για κάθε ερώτηση. Πατήστε το κουμπί Επόμενο για να ξεκινήσετε το κουίζ. + + + Κάντε κλικ στο κουμπί ολοκλήρωσης παρακάτω για να υποβάλετε τις απαντήσεις του κουίζ. Μπορείτε να επιστρέψετε για να βεβαιωθείτε ότι απαντήσατε σε κάθε ερώτηση που θέλατε. Το κουίζ σας θα βαθμολογηθεί και το αποτέλεσμα θα εμφανιστεί στην αρχική σελίδα της εκπαίδευσης. + + + Πρώτη + + + Προηγούμενη + + + Επόμενη + + + Ολοκλήρωση + + + Αναφορά Εκπαίδευσης + + + Ομάδα + + + Προβλήθηκε + + + Ολοκληρώθηκε + + + Βαθμολογία + + + Αποτέλεσμα + + + Δεν Προβλήθηκε + + + Δεν Ολοκληρώθηκε + + + Χωρίς Βαθμολογία + + + Επιτυχία + + + Αποτυχία + + + Ολοκληρωμένη + + + Εκκρεμής + + + Επαναφορά Χρήστη + + + Επιλέξτε ομάδες... + + + Επιλέξτε ρόλους... + + + Επιλέξτε χρήστες... + + + Αφαίρεση αυτής της ερώτησης + + + Αφαίρεση αυτής της απάντησης από την ερώτηση + + + Προσθήκη Απάντησης + + + Η απάντηση είναι υποχρεωτική + + + Προσθήκη Ερώτησης + + + Κείμενο Απάντησης + + diff --git a/Core/Resgrid.Localization/Areas/User/TwoFactor/TwoFactor.el.resx b/Core/Resgrid.Localization/Areas/User/TwoFactor/TwoFactor.el.resx new file mode 100644 index 000000000..bf43b9ce4 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/TwoFactor/TwoFactor.el.resx @@ -0,0 +1,77 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Έλεγχος Ταυτότητας Δύο Παραγόντων + Ενεργοποίηση Ελέγχου Ταυτότητας Δύο Παραγόντων + Απενεργοποίηση Ελέγχου Ταυτότητας Δύο Παραγόντων + Κωδικοί Ανάκτησης + Επαλήθευση Ταυτότητας + + + Έλεγχος Ταυτότητας Δύο Παραγόντων + Ρύθμιση + Απενεργοποίηση + Κωδικοί Ανάκτησης + + + Εφαρμογή Ελέγχου Ταυτότητας (TOTP) + Ο έλεγχος ταυτότητας δύο παραγόντων είναι <strong>ενεργοποιημένος</strong> στον λογαριασμό σας. + Ο έλεγχος ταυτότητας δύο παραγόντων <strong>δεν είναι ενεργοποιημένος</strong>. Ενεργοποιήστε τον τώρα για να ασφαλίσετε τον λογαριασμό σας με εφαρμογή ελέγχου ταυτότητας όπως το Google Authenticator, το Microsoft Authenticator ή το Authy. + Σας απομένουν {0} κωδικός/κωδικοί ανάκτησης. + Σας απομένουν μόνο {0} κωδικός/κωδικοί ανάκτησης. Δημιουργήστε νέους κωδικούς ανάκτησης προτού εξαντληθούν. + Επαναδημιουργία Κωδικών Ανάκτησης + Απενεργοποίηση 2FA + Ενεργοποίηση Εφαρμογής Ελέγχου Ταυτότητας + + + Ρύθμιση Εφαρμογής Ελέγχου Ταυτότητας + Το τμήμα σας απαιτεί έλεγχο ταυτότητας δύο παραγόντων για τους διαχειριστές. Ολοκληρώστε τη ρύθμιση προτού συνεχίσετε. + Για να χρησιμοποιήσετε εφαρμογή ελέγχου ταυτότητας, ακολουθήστε αυτά τα βήματα: + Κατεβάστε μια εφαρμογή ελέγχου ταυτότητας όπως το {0}, το {1} ή το {2}. + Σαρώστε τον παρακάτω κωδικό QR με την εφαρμογή σας ή εισαγάγετε το κλειδί χειροκίνητα: + Μόλις τον σαρώσετε, εισαγάγετε τον 6ψήφιο κωδικό που εμφανίζεται στην εφαρμογή ελέγχου ταυτότητας: + Κωδικός Επαλήθευσης + 000000 + Επαλήθευση και Ενεργοποίηση + Ο κωδικός επαλήθευσης δεν είναι έγκυρος. Ελέγξτε την εφαρμογή ελέγχου ταυτότητας και δοκιμάστε ξανά. + + + Αποθηκεύστε τους Κωδικούς Ανάκτησής Σας + Σημαντικό: Φυλάξτε αυτούς τους κωδικούς ανάκτησης σε ασφαλές μέρος. Κάθε κωδικός μπορεί να χρησιμοποιηθεί μόνο μία φορά. Αν χάσετε την πρόσβαση στην εφαρμογή ελέγχου ταυτότητας, μπορείτε να χρησιμοποιήσετε έναν κωδικό ανάκτησης για να συνδεθείτε. + Ολοκληρώθηκε + + + Η απενεργοποίηση του 2FA θα μειώσει την ασφάλεια του λογαριασμού σας. Το κλειδί ελέγχου ταυτότητάς σας θα επαναφερθεί. + Εισαγάγετε τον τρέχοντα κωδικό ελέγχου ταυτότητας για να επιβεβαιώσετε ότι θέλετε να απενεργοποιήσετε το 2FA: + Κωδικός Ελέγχου Ταυτότητας + Απενεργοποίηση 2FA + + + Επιβεβαιώστε την Ταυτότητά Σας + Αυτή η ενέργεια απαιτεί πρόσφατη επαλήθευση ελέγχου ταυτότητας δύο παραγόντων. Εισαγάγετε τον τρέχοντα κωδικό από την εφαρμογή ελέγχου ταυτότητάς σας. + Επαλήθευση + + + Έλεγχος Ταυτότητας Δύο Παραγόντων + Η σύνδεσή σας προστατεύεται με εφαρμογή ελέγχου ταυτότητας. Εισαγάγετε τον 6ψήφιο κωδικό από την εφαρμογή σας παρακάτω. + Κωδικός Ελέγχου Ταυτότητας + Να μην ερωτηθώ ξανά σε αυτή τη συσκευή για 30 ημέρες + Επαλήθευση Κωδικού + Χάσατε την πρόσβαση στην εφαρμογή σας; Χρησιμοποιήστε κωδικό ανάκτησης + + + Σύνδεση με Κωδικό Ανάκτησης + Έχετε χάσει την πρόσβαση στην εφαρμογή ελέγχου ταυτότητάς σας. Εισαγάγετε έναν από τους κωδικούς ανάκτησής σας παρακάτω. Κάθε κωδικός ανάκτησης μπορεί να χρησιμοποιηθεί μόνο μία φορά. + Κωδικός Ανάκτησης + xxxx-xxxx-xxxx + Σύνδεση με Κωδικό Ανάκτησης + Χρήση εφαρμογής ελέγχου ταυτότητας αντ' αυτού + Μη έγκυρος κωδικός ελέγχου ταυτότητας. + Μη έγκυρος κωδικός ανάκτησης. + + diff --git a/Core/Resgrid.Localization/Areas/User/Units/Units.el.resx b/Core/Resgrid.Localization/Areas/User/Units/Units.el.resx new file mode 100644 index 000000000..2e074848a --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Units/Units.el.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Προσθήκη Καταγραφής + + + Προσθήκη Ρόλου + + + Προσθήκη Καταγραφής Μονάδας + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά όλες τις καταστάσεις για αυτή τη μονάδα; + + + Εκκαθάριση όλων των Καταστάσεων για τη Μονάδα + + + Διαγραφή Όλων + + + ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα διαγράψει οριστικά αυτή τη μονάδα. Είστε βέβαιοι ότι θέλετε να διαγράψετε τη μονάδα + + + Επεξεργασία Μονάδας + + + Συμβάντα για + + + Δημιουργία Αναφοράς + + + Σώμα Καταγραφής (Αφήγηση) + + + Αφήγηση για την καταγραφή της μονάδας + + + Καταγραφές + + + Νέα Μονάδα + + + Μη Κατειλημμένη + + + Δεν Υπάρχουν Μονάδες Εκτός Ομάδας + + + Δεν Υπάρχουν Μονάδες στο Τμήμα + + + Δεν Υπάρχουν Μονάδες σε Αυτή την Ομάδα + + + Για να ορίσετε στελέχωση σε μονάδες πρέπει να έχετε δημιουργήσει Μονάδες και Ρόλους (π.χ. Οδηγός, Χειριστής κ.λπ.) εντός αυτών των μονάδων. + + + Αφαίρεση αυτού του ρόλου + + + Όνομα Ρόλου + + + Όνομα Ρόλου + + + Επιλογή Μονάδας + + + Ορισμός Κατάστασης για Μονάδες + + + Ορισμός Κατάστασης + + + Ορισμός Κατάστασης για Επιλεγμένες Μονάδες + + + Ορισμός Κατάστασης Μονάδας + + + Έχετε επιλέξει μια μονάδα· μπορούν να επιλεγούν μόνο άλλες μονάδες με τις ίδιες προσαρμοσμένες (ή προεπιλεγμένες) καταστάσεις. + + + Στελέχωση Μονάδας + + + Στελέχωση Μονάδας + + + Προβολή Συμβάντων + + + Προβολή Συμβάντων Μονάδας + + + Προβολή Καταγραφών Μονάδας + + + Ναι, είμαι βέβαιος + + + Μπορείτε να προσθέσετε νέο Τύπο + + + εδώ + + + Απαιτούμενος Ρόλος Προσωπικού + + + Απαιτείται; + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/UserDefinedFields/UserDefinedFields.el.resx b/Core/Resgrid.Localization/Areas/User/UserDefinedFields/UserDefinedFields.el.resx new file mode 100644 index 000000000..ca470864a --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/UserDefinedFields/UserDefinedFields.el.resx @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Πεδία Οριζόμενα από τον Χρήστη + + + Διαχείριση προσαρμοσμένων πεδίων για οντότητες του τμήματος + + + Τύπος Οντότητας + + + Έκδοση + + + Πεδία + + + Κατάσταση + + + Ενέργειες + + + Ενεργό + + + Δεν έχει διαμορφωθεί + + + Διαμόρφωση + + + Επεξεργασία + + + Διαγραφή + + + Διαγραφή όλης της διαμόρφωσης πεδίων για {0}; Η ενέργεια δεν μπορεί να αναιρεθεί. + + + Ορισμοί Πεδίων ανά Οντότητα + + + + Διαμόρφωση Πεδίων — {0} + + + Διαμόρφωση Πεδίων — {0} + + + Ορισμοί Πεδίων + + + Προσθήκη Πεδίου + + + Δεν έχουν προστεθεί ακόμη πεδία. Κάντε κλικ στο «Προσθήκη Πεδίου» για να ορίσετε το πρώτο πεδίο. + + + Όνομα Συστήματος + + + Ετικέτα + + + Τύπος Δεδομένων + + + Υποχρεωτικό + + + Ενεργοποιημένο + + + Κινητά + + + Αναφορές + + + Σειρά + + + Αφαίρεση + + + Αφαίρεση αυτού του πεδίου; + + + Αποθήκευση Ορισμού + + + Ακύρωση + + + + Λεπτομέρειες Πεδίου + + + Εσωτερικό αναγνωριστικό (γράμματα, ψηφία, κάτω παύλες· πρέπει να ξεκινά με γράμμα ή κάτω παύλα). Πρέπει να είναι μοναδικό εντός αυτού του τύπου οντότητας. + + + Αναγνώσιμη ετικέτα που εμφανίζεται στους χρήστες. + + + Περιγραφή / Επεξήγηση + + + Κείμενο Υποδείγματος + + + Προεπιλεγμένη Τιμή + + + Ομάδα / Ενότητα + + + Μόνο για Ανάγνωση + + + Όταν είναι ενεργοποιημένο, το πεδίο εμφανίζεται αλλά δεν μπορεί να επεξεργαστεί από τους χρήστες. + + + Κανόνες Επικύρωσης + + + Ελάχιστο Μήκος + + + Μέγιστο Μήκος + + + Ελάχιστη Τιμή + + + Μέγιστη Τιμή + + + Μοτίβο Regex + + + Μήνυμα Σφάλματος Regex + + + Επιλογές (Κλειδί=Ετικέτα, μία ανά γραμμή) + + + Εισαγάγετε μία επιλογή ανά γραμμή σε μορφή Κλειδί=Ετικέτα, π.χ. "yes=Ναι" ή "no=Όχι". + + + + Ποιος Μπορεί να Δει Αυτό το Πεδίο; + + + Όλοι + + + Διαχειριστές Τμήματος και Ομάδας + + + Μόνο Διαχειριστές Τμήματος + + + Ελέγχει ποιος μπορεί να δει και να συμπληρώσει αυτό το πεδίο. Οι χρήστες που δεν πληρούν την απαίτηση δεν θα το βλέπουν κατά την προσθήκη ή προβολή εγγραφών. + + + Προσθήκη Πεδίου + + + Κλείσιμο + + + + Αρχική + + + Νέο + + + Πεδία Οριζόμενα από τον Χρήστη + + + + Κλήση + + + Προσωπικό + + + Μονάδα + + + Επαφή + + + + Προεπισκόπηση + + + Προεπισκόπηση Πεδίων — {0} + + + Μόνο Προεπισκόπηση + + + Αυτό είναι ένα δείγμα του πώς θα φαίνονται και θα αποδίδονται τα πεδία στη σελίδα. Δεν θα αποθηκευτούν δεδομένα. + + + Δεν υπάρχουν ενεργοποιημένα πεδία για προεπισκόπηση. + + + Κλείσιμο + + + + Ο ορισμός πεδίων αποθηκεύτηκε με επιτυχία. + + + Ο ορισμός πεδίων διαγράφηκε. + + + Διορθώστε τα παρακάτω σφάλματα πριν από την αποθήκευση. + + + Το όνομα συστήματος είναι υποχρεωτικό. + + + Χρησιμοποιήστε μόνο γράμματα, ψηφία και κάτω παύλες, ξεκινώντας με γράμμα ή κάτω παύλα. + + + Διπλότυπα ονόματα συστήματος: {0}. Κάθε όνομα συστήματος πρέπει να είναι μοναδικό εντός αυτού του τύπου οντότητας. + + + diff --git a/Core/Resgrid.Localization/Areas/User/Voice/Voice.el.resx b/Core/Resgrid.Localization/Areas/User/Voice/Voice.el.resx new file mode 100644 index 000000000..c9ae18f2f --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Voice/Voice.el.resx @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Νέα Ροή Ήχου + + + Προσθήκη Ροής Ήχου + + + Νέο Φωνητικό Κανάλι + + + Όνομα Ροής Ήχου + + + Ροές Ήχου + + + Διεύθυνση URL Ροής Ήχου + + + Όνομα Καναλιού + + + Επεξεργασία Ροής Ήχου + + + Είναι Προεπιλεγμένο + + + Συμμετέχοντες + + + Το Πρόσθετο Push-To-Talk Δεν Έχει Αγοραστεί + + + Το τμήμα σας δεν έχει αγοράσει το πρόσθετο Push-To-Talk. Το Διαχειριστικό Μέλος του τμήματός σας μπορεί να αγοράσει το πρόσθετο από τη σελίδα Συνδρομής και Χρέωσης. Το Push-To-Talk επιτρέπει στο τμήμα σας να ενεργοποιήσει φωνητικές επικοινωνίες σε πραγματικό χρόνο μέσω των εφαρμογών Resgrid. + + + Αποθήκευση Ροής Ήχου + + + Φωνητικά Κανάλια (PTT) + + + Ήχος και Push-To-Talk + + + Προσθήκη Καναλιού PTT + + + Ροή Ήχου + + + Μπορείτε να έχετε μόνο 1 κανάλι ορισμένο ως προεπιλογή κάθε φορά. Για να ορίσετε άλλο κανάλι ως προεπιλογή, πρέπει πρώτα να απενεργοποιήσετε την επιλογή προεπιλογής στο τρέχον κανάλι. + + + Επεξεργασία Καναλιού Push-To-Talk + + + Σημειώστε ότι μπορεί να υπάρχει μόνο ένα προεπιλεγμένο κανάλι κάθε φορά· η επιλογή νέου προεπιλεγμένου καναλιού θα απενεργοποιήσει την προεπιλογή για το υπάρχον. + + + Νέο Κανάλι Push-To-Talk + + + Push-To-Talk + + + Αποθήκευση Καναλιού PTT + + \ No newline at end of file diff --git a/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.el.resx b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.el.resx new file mode 100644 index 000000000..73ec71901 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/WeatherAlerts/WeatherAlerts.el.resx @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Καιρικές Ειδοποιήσεις + + + Καιρικές Ειδοποιήσεις + + + Ενεργές Καιρικές Ειδοποιήσεις + + + Ιστορικό Ειδοποιήσεων + + + Πηγές Ειδοποιήσεων + + + Ζώνες Παρακολούθησης + + + Ρυθμίσεις + + + Προβολή Ιστορικού + + + Επιστροφή στις Ειδοποιήσεις + + + Επιστροφή στις Ρυθμίσεις + + + Διαχείριση Ζωνών + + + Λεπτομέρειες + + + Καμία ενεργή καιρική ειδοποίηση για το τμήμα σας. + + + Δεν είναι δυνατή η φόρτωση καιρικών ειδοποιήσεων. Ελέγξτε τη διαμόρφωσή σας. + + + Φόρτωση καιρικών ειδοποιήσεων... + + + Φόρτωση ιστορικού... + + + Συμβάν + + + Σοβαρότητα + + + Περιοχή + + + Λήγει + + + Κατάσταση + + + Κατηγορία + + + Ενέργειες + + + Ακραία + + + Σοβαρή + + + Μέτρια + + + Ήπια + + + Άγνωστη + + + Άμεσο + + + Αναμενόμενο + + + Μελλοντικό + + + Παρελθόν + + + Άγνωστο + + + Μετεωρολογική + + + Πυρκαγιά + + + Υγεία + + + Περιβαλλοντική + + + Άλλη + + + Ενεργή + + + Ενημερώθηκε + + + Έληξε + + + Ακυρώθηκε + + + National Weather Service + + + Environment Canada + + + MeteoAlarm (Ευρώπη) + + + Προσθήκη Πηγής + + + Όνομα Πηγής + + + Τύπος Πηγής + + + Φίλτρο Περιοχής (κωδικοί ζωνών JSON) + + + Διάστημα Ανίχνευσης (λεπτά) + + + Αποθήκευση + + + Ακύρωση + + + Διαγραφή + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την πηγή; + + + Αποτυχία αποθήκευσης πηγής. Δοκιμάστε ξανά. + + + Αποτυχία διαγραφής πηγής. Δοκιμάστε ξανά. + + + Προσθήκη Ζώνης Παρακολούθησης + + + Επεξεργασία Ζώνης Παρακολούθησης + + + Όνομα Ζώνης + + + Κωδικός Ζώνης + + + Κεντρική Τοποθεσία + + + Ακτίνα (μίλια) + + + Κύρια Ζώνη + + + Ενεργή + + + Ανενεργή + + + Κύρια + + + Ενεργοποίηση + + + Απενεργοποίηση + + + Επεξεργασία + + + Δεν έχουν διαμορφωθεί ζώνες παρακολούθησης. Προσθέστε μια ζώνη παρακάτω για να ορίσετε τις γεωγραφικές περιοχές που θέλετε να παρακολουθείτε για καιρικές ειδοποιήσεις. + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη ζώνη; + + + Αποτυχία αποθήκευσης ζώνης. Δοκιμάστε ξανά. + + + Αποτυχία διαγραφής ζώνης. Δοκιμάστε ξανά. + + + Αποτυχία ενημέρωσης κατάστασης ζώνης. Δοκιμάστε ξανά. + + + Εισαγάγετε όνομα ζώνης. + + + Αποθήκευση Ζώνης + + + Επιλογές + + + Ημερομηνία Έναρξης + + + Ημερομηνία Λήξης + + + Αναζήτηση + + + Επιλέξτε ημερομηνία έναρξης και λήξης. + + + Δεν βρέθηκε ιστορικό ειδοποιήσεων για το επιλεγμένο εύρος ημερομηνιών. + + + Δεν είναι δυνατή η φόρτωση του ιστορικού ειδοποιήσεων. + + + Λεπτομέρειες Ειδοποίησης + + + Επικεφαλίδα + + + Περιγραφή + + + Οδηγίες + + + Περιγραφή Περιοχής + + + Επείγον + + + Βεβαιότητα + + + Ημερομηνία Ισχύος + + + Ημερομηνία Έναρξης + + + Ημερομηνία Λήξης + + + Ημερομηνία Αποστολής + + + Πρώτη Εμφάνιση + + + Τελευταία Ενημέρωση + + + Αποστολέας + + + Εξωτερικό Αναγνωριστικό + + + Στάλθηκε Ειδοποίηση + + + Ναι + + + Όχι + + + Διαμορφωμένες Πηγές + + + Διάστημα Ανίχνευσης + + + Τελευταία Ανίχνευση + + + Κατάσταση + + + Υγιής + + + Σφάλμα + + + Ποτέ Δεν Ανιχνεύθηκε + + + Δεν έχουν διαμορφωθεί πηγές καιρικών ειδοποιήσεων. Προσθέστε μια πηγή για να αρχίσετε να λαμβάνετε καιρικές ειδοποιήσεις. + + + Καιρική Ειδοποίηση: {0} + + + Λήγει: {0} + + + Περιοχή: {0} + + + Οδηγίες: {0} + + + Ρυθμίσεις Ειδοποιήσεων + + + Διαμορφώστε ποιες καιρικές ειδοποιήσεις δημιουργούν αυτόματα μηνύματα προς τα μέλη του τμήματος. + + + Ενεργοποίηση Καιρικών Ειδοποιήσεων + + + Ελάχιστη Σοβαρότητα προς Εμφάνιση + + + Μόνο ειδοποιήσεις σε αυτή τη σοβαρότητα ή ανώτερη θα εμφανίζονται στον πίνακα ελέγχου. + + + Όριο Σοβαρότητας Αυτόματου Μηνύματος + + + Ειδοποιήσεις σε αυτή τη σοβαρότητα ή ανώτερη θα στέλνουν αυτόματα μήνυμα σε όλα τα μέλη του τμήματος. Χαμηλότερη τιμή = υψηλότερη σοβαρότητα (Ακραία=0, Σοβαρή=1, Μέτρια=2, Ήπια=3). + + + Εξαιρούμενα Συμβάντα Ειδοποιήσεων + + + π.χ. Προειδοποίηση Ανέμων Λίμνης, Προειδοποίηση Ανέμων + + + Λίστα τίτλων συμβάντων ειδοποίησης χωρισμένη με κόμμα, τα οποία δεν πρέπει να δημιουργούν αυτόματα μηνύματα. Οι ειδοποιήσεις θα εξακολουθούν να εμφανίζονται στον πίνακα ελέγχου, αλλά δεν θα δημιουργείται μήνυμα Resgrid για αυτά τα συμβάντα. + + + Επισύναψη Καιρικού Πλαισίου στις Κλήσεις + + + Όταν είναι ενεργοποιημένο, οι ενεργές καιρικές ειδοποιήσεις κοντά στην τοποθεσία μιας κλήσης θα επισυνάπτονται ως σημείωση κλήσης. + + + Αποθήκευση Ρυθμίσεων + + + Οι ρυθμίσεις αποθηκεύτηκαν με επιτυχία. + + + Αποτυχία αποθήκευσης ρυθμίσεων. Δοκιμάστε ξανά. + + + Αποτυχία φόρτωσης ρυθμίσεων. + + + Αποτυχία ενημέρωσης κατάστασης πηγής. Δοκιμάστε ξανά. + + + Επεξεργασία Πηγής + + + Προσθήκη / Επεξεργασία Πηγής + + + Εισαγάγετε όνομα πηγής. + + + Λίστα κωδικών πολιτειών, επαρχιών ή ζωνών NWS χωρισμένη με κόμμα. + + diff --git a/Core/Resgrid.Localization/Areas/User/Workflows/Workflows.el.resx b/Core/Resgrid.Localization/Areas/User/Workflows/Workflows.el.resx new file mode 100644 index 000000000..c71ad45e5 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Workflows/Workflows.el.resx @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Ροές Εργασίας + + + Ροές Εργασίας Τμήματος + + + Νέα Ροή Εργασίας + + + Διαπιστευτήρια + + + Εκκρεμείς Εκτελέσεις + + + Δεν έχουν δημιουργηθεί ακόμη ροές εργασίας. + + + Δημιουργήστε την πρώτη σας ροή εργασίας. + + + Όνομα + + + Συμβάν Ενεργοποίησης + + + Κατάσταση + + + Δημιουργήθηκε + + + Ενέργειες + + + Ενεργοποιημένη + + + Απενεργοποιημένη + + + Επεξεργασία + + + Εκτελέσεις + + + Υγεία + + + Διαγραφή + + + Διαγραφή ροής εργασίας {0}; + + + + Νέα Ροή Εργασίας + + + Δημιουργία Ροής Εργασίας + + + Περιγραφή + + + Μέγιστο Πλήθος Επαναλήψεων + + + Βάση Καθυστέρησης Επανάληψης (δευτερόλεπτα) + + + π.χ. Ειδοποίηση Slack σε Νέα Κλήση + + + Προαιρετική περιγραφή + + + Ακύρωση + + + + Επεξεργασία Ροής Εργασίας + + + Ρυθμίσεις Ροής Εργασίας + + + Μέγιστες Επαναλήψεις + + + Καθυστέρηση (δευτ.) + + + Αποθήκευση Αλλαγών + + + Βήματα + + + Προσθήκη Βήματος + + + Τύπος Ενέργειας + + + Σειρά + + + Πρότυπο (πρώτοι 120 χαρακτήρες) + + + Ναι + + + Όχι + + + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το βήμα; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. + + + Διαγραφή Βήματος + + + Μεταβλητές Προτύπου + + + Κοινές μεταβλητές (πάντα διαθέσιμες): + + + Μεταβλητές συμβάντος κλήσης: + + + + Διαπιστευτήρια Ροών Εργασίας + + + Αποθηκευμένα Διαπιστευτήρια + + + Προσθήκη Διαπιστευτηρίου + + + Δεν έχουν διαμορφωθεί διαπιστευτήρια. Προσθέστε διαπιστευτήρια για χρήση σε βήματα ενεργειών ροής εργασίας. + + + Τύπος + + + Απόρρητα Δεδομένα + + + Διαγραφή διαπιστευτηρίου {0}; + + + + Διορθώστε τα ακόλουθα σφάλματα πριν από την αποθήκευση. + + + Προσθήκη Διαπιστευτηρίου + + + Επεξεργασία Διαπιστευτηρίου + + + Λεπτομέρειες Διαπιστευτηρίου + + + Τύπος + + + Αποθήκευση Διαπιστευτηρίου + + + Τα υπάρχοντα απόρρητα αποθηκεύονται κρυπτογραφημένα και δεν μπορούν να εμφανιστούν. Συμπληρώστε τα παρακάτω πεδία για να τα αντικαταστήσετε ή αφήστε τα όλα κενά για να διατηρήσετε τα τρέχοντα απόρρητα. + + + + Ρυθμίσεις SMTP + + + Διακομιστής + + + Θύρα + + + Όνομα Χρήστη + + + Κωδικός Πρόσβασης + + + Χρήση SSL/TLS + + + Διεύθυνση Αποστολέα + + + Η διεύθυνση email που θα εμφανίζεται στο πεδίο «Από» των απεσταλμένων μηνυμάτων. + + + + Ρυθμίσεις Twilio + + + Account SID + + + Auth Token + + + Αριθμός Αποστολέα + + + Μορφή E.164, π.χ. +15551234567 + + + + Ρυθμίσεις FTP + + + Διακομιστής + + + Θύρα + + + Όνομα Χρήστη + + + Κωδικός Πρόσβασης + + + Χρήση Παθητικής Λειτουργίας + + + + Ρυθμίσεις SFTP + + + Διακομιστής + + + Θύρα + + + Όνομα Χρήστη + + + Κωδικός Πρόσβασης + + + Ιδιωτικό Κλειδί (PEM) + + + Δώστε είτε κωδικό πρόσβασης είτε ιδιωτικό κλειδί (ή και τα δύο αν το κλειδί προστατεύεται με φράση πρόσβασης). + + + + Ρυθμίσεις AWS S3 + + + Access Key ID + + + Secret Access Key + + + Περιοχή + + + Όνομα Bucket + + + + Διακριτικό HTTP Bearer + + + Διακριτικό Bearer + + + Αποστέλλεται ως Authorization: Bearer <token> + + + + Βασικός Έλεγχος Ταυτότητας HTTP + + + Όνομα Χρήστη + + + Κωδικός Πρόσβασης + + + + Κλειδί API HTTP + + + Όνομα Κεφαλίδας + + + Το όνομα της κεφαλίδας HTTP που μεταφέρει το κλειδί API. + + + Κλειδί API + + + + Webhook Microsoft Teams + + + Διεύθυνση URL Εισερχόμενου Webhook + + + Δημιουργήστε έναν σύνδεσμο Incoming Webhook μέσα στο επιθυμητό κανάλι Teams. + + + + Slack + + + Διεύθυνση URL Εισερχόμενου Webhook + + + Διακριτικό Bot + + + Δώστε διεύθυνση URL webhook, διακριτικό bot ή και τα δύο. + + + + Discord + + + Διεύθυνση URL Webhook + + + Διακριτικό Bot + + + Δώστε διεύθυνση URL webhook, διακριτικό bot ή και τα δύο. + + + + Azure Blob Storage + + + Συμβολοσειρά Σύνδεσης + + + Όνομα Container + + + + Box + + + Client ID + + + Client Secret + + + Enterprise ID + + + Public Key ID + + + Ιδιωτικό Κλειδί (PEM) + + + Το ιδιωτικό κλειδί RSA από τη διαμόρφωση της εφαρμογής Box JWT (μορφή PEM). + + + Φράση Πρόσβασης Ιδιωτικού Κλειδιού + + + Προαιρετική φράση πρόσβασης για την αποκρυπτογράφηση του ιδιωτικού κλειδιού, αν προστατεύεται με φράση πρόσβασης. + + + + Dropbox + + + Refresh Token + + + Διακριτικό ανανέωσης OAuth2 μακράς διάρκειας που λαμβάνεται από τη ροή εξουσιοδότησης OAuth2 του Dropbox. + + + App Key + + + App Secret + + + + Εκκρεμείς / Εκτελούμενες Ροές Εργασίας + + + Ενεργές Εκτελέσεις + + + Εκκαθάριση Όλων των Εκκρεμών + + + Ακύρωση ΟΛΩΝ των εκκρεμών εκτελέσεων για αυτό το τμήμα; + + + Καμία εκκρεμής ή εκτελούμενη εκτέλεση ροής εργασίας. + + + Αναγνωριστικό Εκτέλεσης + + + Ροή Εργασίας + + + Σε Ουρά + + + Προσπάθεια + + + Ακύρωση + + + Ακύρωση αυτής της εκτέλεσης; + + + + Εκτελέσεις Ροής Εργασίας + + + Ιστορικό Εκτελέσεων + + + Δεν βρέθηκαν εκτελέσεις για αυτή τη ροή εργασίας. + + + Ξεκίνησε + + + Ολοκληρώθηκε + + + Σφάλμα + + + Προηγούμενη + + + Επόμενη + + + + Υγεία Ροής Εργασίας + + + Δεν υπάρχουν ακόμη διαθέσιμα δεδομένα υγείας για αυτή τη ροή εργασίας. + + + Τελευταίες 24 Ώρες + + + Τελευταίες 7 Ημέρες + + + Τελευταίες 30 Ημέρες + + + Επιτυχείς + + + Αποτυχημένες + + + Σύνολο + + + Ποσοστό επιτυχίας + + + Απόδοση + + + Μέση Διάρκεια + + + Τελευταία Εκτέλεση + + + Τελευταίο Σφάλμα + + + Προβολή Εκτελέσεων + + + Ποτέ + + + Καμία + + + + Αρχική + + + Νέα + + + Εκκρεμής + + + + Λεπτομέρειες Εκτέλεσης + + + Σύνοψη Εκτέλεσης + + + Καταγραφές Βημάτων + + + Δεν καταγράφηκαν βήματα για αυτή την εκτέλεση. + + + Βήμα: + + + Το βήμα παραλείφθηκε λόγω συνθήκης. + + + Η συνθήκη αποτιμήθηκε ως: + + + Αποτέλεσμα + + + Αποδοθέν Αποτέλεσμα + + + + Όλοι οι διαθέσιμοι τύποι συμβάντων ενεργοποίησης έχουν ήδη διαμορφωμένη ροή εργασίας. Διαγράψτε μια υπάρχουσα ροή εργασίας για να ελευθερώσετε τον τύπο συμβάντος της. + + + Κάθε τύπος συμβάντος μπορεί να έχει μόνο μία ροή εργασίας. Εμφανίζονται μόνο οι μη χρησιμοποιούμενοι τύποι συμβάντων. + + + Λεπτομέρεια + + + diff --git a/Core/Resgrid.Localization/Common.el.resx b/Core/Resgrid.Localization/Common.el.resx new file mode 100644 index 000000000..b22bf65e7 --- /dev/null +++ b/Core/Resgrid.Localization/Common.el.resx @@ -0,0 +1,687 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ενέργεια + + + Ενέργειες + + + Ενεργοποίηση + + + Ενεργό + + + Αρχειοθετημένες Κλήσεις + + + Συνημμένο + + + Συνημμένα + + + Ήχος και Push-To-Talk + + + Διαθέσιμος + + + Διαθέσιμος στο + + + Διαθέσιμος στον Σταθμό + + + Big Board + + + Ιστολόγιο + + + Ημερολόγιο + + + Κλήση + + + Διεύθυνση Κλήσης + + + Ρυθμίσεις Εισαγωγής Κλήσεων + + + Κλήσεις + + + Κλήσεις + + + Χρονική Σήμανση Κλήσης + + + Ακύρωση + + + Ακυρώθηκε + + + Πιστοποιήσεις + + + Κλείσιμο + + + Κλειστή + + + Κωδικός + + + Χρώμα + + + Εντολές + + + Δεσμευμένος + + + Σύνδεση + + + Επαφές + + + Συνομιλία + + + Βοηθός + + + Εποπτεία + + + Επικοινωνήστε Μαζί Μας + + + Δημιουργήθηκε Στις + + + Προσαρμοσμένες Καταστάσεις + + + Δεδομένα + + + Ημέρα + + + Ημέρες + + + Απενεργοποίηση + + + Προεπιλογή + + + Καθυστερημένη + + + Διαγραφή + + + Τμήμα + + + Ρυθμίσεις Τμήματος + + + Τύποι Τμήματος + + + Περιγραφή + + + Απόρριψη + + + Απεστάλη + + + Λίστες Διανομής + + + Έγγραφα + + + Έγγραφα + + + Λήψη + + + Λήξη + + + Εκτ. Άφιξη + + + Συμβάν + + + Έληξε + + + Εξαγωγή + + + Αρχεία + + + Φόρμες + + + Παρασκευή + + + Γενικά + + + Ομάδες + + + Βοήθεια + + + Βοήθεια και Υποστήριξη + + + Αρχική + + + Ώρα + + + Ώρες + + + Αναγνωριστικό + + + Εικόνες + + + Πληροφορίες + + + Στη Βάση + + + Εκπαιδευτές + + + Απόθεμα + + + Βάση Γνώσης + + + Γλώσσα + + + Σύνδεσμοι + + + Τοποθεσία + + + Αποσύνδεση + + + Καταγραφές + + + Γραμματοκιβώτιο + + + Χάρτης + + + Χαρτογράφηση + + + Ίσως + + + Λεπτά + + + Δευτέρα + + + Μηνιαία + + + Όνομα + + + Όνομα + + + Φύση της Κλήσης + + + Ποτέ + + + Όχι + + + Κανένα + + + Σημείωση + + + Σημειώσεις + + + Σημειώσεις + + + Ειδοποιήσεις + + + Δεν Ανταποκρίνεται + + + Αριθμός + + + Στο Σημείο + + + Σε Βάρδια + + + Προαιρετικό + + + Επιλογές + + + Σειρά + + + Παραγγελίες + + + Προσωπικό + + + Προσωπικό + + + Προεπισκόπηση + + + Εκτύπωση + + + Προτεραιότητα + + + Επεξεργασία + + + Προφίλ + + + Πρωτόκολλα + + + Σημείο Ενδιαφέροντος + + + Push-To-Talk + + + Αποδεσμεύτηκε + + + Αφαίρεση + + + Επανάληψη + + + Αναφορές + + + Υποχρεωτικό + + + Ανταποκρίνεται + + + Ανταποκρίνεται σε + + + Ανταπόκριση σε Κλήση + + + Ανταπόκριση σε Σταθμό + + + Ρόλοι + + + Διαδρομή + + + Σάββατο + + + Αποθήκευση + + + Αποθήκευση + + + Ασφάλεια και Δικαιώματα + + + Αποστολή + + + Βάρδιες + + + Δήλωση Συμμετοχής + + + Μέγεθος + + + Στελέχωση + + + Σε Αναμονή + + + Σε Αναμονή στον Σταθμό + + + Έναρξη + + + Σταθμός + + + Σταθμοί και Ομάδες + + + Σταθμοί + + + Σημεία Ενδιαφέροντος + + + Κατάσταση + + + Κατάσταση + + + Κατάσταση Συστήματος + + + Φόρμα Νέας Κλήσης + + + Συνδρομή και Χρέωση + + + Κυριακή + + + Σύστημα + + + Πρότυπα + + + SMS + + + Μηνύματα SMS + + + Πέμπτη + + + Χρονική Σήμανση + + + Τίτλος + + + Εκπαίδευση + + + Κωδικός Μαθήματος\Εκπαίδευσης + + + Μάθημα\Εκπαίδευση + + + Εκπαιδεύσεις + + + Τρίτη + + + Τύπος + + + Τύποι + + + Μη Διαθέσιμος + + + Αβάσιμη + + + Μονάδες + + + Μονάδες + + + Άγνωστο + + + Ενημέρωση + + + Αναβαθμίστε το Πακέτο Σας + + + Μεταφορτώθηκε Από + + + Μεταφορτώθηκε Στις + + + Χρήστες + + + Έγκυρο + + + Βίντεο + + + Βίντεο + + + Προβολή + + + Προβολή Όλων των Κλήσεων + + + Προβολή των Εισερχομένων Μηνυμάτων Σας + + + Προειδοποίηση + + + Τετάρτη + + + Εβδομάδα + + + Εβδομαδιαία + + + Εβδομάδες + + + Καταγραφή Εργασίας + + + Ετήσια + + + Ναι + + + Πεδία Οριζόμενα από τον Χρήστη + + + Τα Τμήματά Σας + + diff --git a/Core/Resgrid.Localization/SupportedLocales.cs b/Core/Resgrid.Localization/SupportedLocales.cs index d34faffa5..1ab6ba75d 100644 --- a/Core/Resgrid.Localization/SupportedLocales.cs +++ b/Core/Resgrid.Localization/SupportedLocales.cs @@ -19,6 +19,7 @@ public static class SupportedLocales {"pl", "Polish"}, //"pl-PL" {"uk", "Ukrainian"}, //"uk-UA" {"ar", "Arabic"}, //"ar" + {"el", "Greek"}, //"el-GR" }; public static string[] GetSupportedCultures() diff --git a/Core/Resgrid.Model/DepartmentSettingTypes.cs b/Core/Resgrid.Model/DepartmentSettingTypes.cs index 2d32cdc48..08ed247ad 100644 --- a/Core/Resgrid.Model/DepartmentSettingTypes.cs +++ b/Core/Resgrid.Model/DepartmentSettingTypes.cs @@ -62,5 +62,17 @@ public enum DepartmentSettingTypes DispatchRecommendationMode = 58, DispatchRecommendationAutoDispatch = 59, DispatchRecommendationConfig = 60, + + /// + /// ProtoBuf-serialized : which built-in new-call fields a + /// department shows, and which it requires before a call can be created. + /// + NewCallFieldPolicy = 61, + + /// + /// ProtoBuf-serialized : how long a unit may sit in a status + /// before the board highlights it. + /// + UnitStatusThresholds = 62, } } diff --git a/Core/Resgrid.Model/Helpers/NewCallFieldPolicyValidator.cs b/Core/Resgrid.Model/Helpers/NewCallFieldPolicyValidator.cs new file mode 100644 index 000000000..d5476eb4c --- /dev/null +++ b/Core/Resgrid.Model/Helpers/NewCallFieldPolicyValidator.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; + +namespace Resgrid.Model.Helpers +{ + /// A field the policy requires that the submitted call did not supply. + public class NewCallFieldViolation + { + public string Key { get; set; } + } + + /// + /// Values pulled off whichever call-creation payload is being validated. Every call-creation path + /// (web form, v4 API, chatbot, email import, TTC) maps its own shape onto this once, so the + /// policy is enforced identically everywhere instead of being re-implemented per surface. + /// + public class NewCallFieldValues + { + public string Note { get; set; } + public string Address { get; set; } + public string Geolocation { get; set; } + public string What3Words { get; set; } + public string PlusCode { get; set; } + public string ContactName { get; set; } + public string ContactInfo { get; set; } + public string ExternalId { get; set; } + public string IncidentId { get; set; } + public string ReferenceId { get; set; } + public int? DestinationPoiId { get; set; } + public string IndoorMapZoneId { get; set; } + public bool HasProtocols { get; set; } + public bool HasLinkedCall { get; set; } + public DateTime? DispatchOn { get; set; } + public bool HasDispatchList { get; set; } + } + + /// + /// Checks a call against its department's . + /// + /// + /// This is the point of the whole feature for the customer who asked for it: a call-taker must not + /// be able to forward an incident to the field until the information the crews need is actually + /// on it. Clients enforce the same policy for usability, but a client can be old, offline-queued + /// or simply another integration -- so the server decides. + /// + public static class NewCallFieldPolicyValidator + { + /// + /// Returns the required fields the submitted call left blank. Empty means the call may proceed. + /// + public static List Validate(NewCallFieldPolicy policy, NewCallFieldValues values) + { + var violations = new List(); + + if (policy == null || policy.IsEmpty || values == null) + return violations; + + void RequireText(string key, string value) + { + if (policy.IsRequired(key) && string.IsNullOrWhiteSpace(value)) + violations.Add(new NewCallFieldViolation { Key = key }); + } + + void Require(string key, bool hasValue) + { + if (policy.IsRequired(key) && !hasValue) + violations.Add(new NewCallFieldViolation { Key = key }); + } + + RequireText(NewCallFieldKeys.Note, values.Note); + RequireText(NewCallFieldKeys.Address, values.Address); + RequireText(NewCallFieldKeys.Geolocation, values.Geolocation); + RequireText(NewCallFieldKeys.What3Words, values.What3Words); + RequireText(NewCallFieldKeys.PlusCode, values.PlusCode); + RequireText(NewCallFieldKeys.ContactName, values.ContactName); + RequireText(NewCallFieldKeys.ContactInfo, values.ContactInfo); + RequireText(NewCallFieldKeys.ExternalId, values.ExternalId); + RequireText(NewCallFieldKeys.IncidentId, values.IncidentId); + RequireText(NewCallFieldKeys.ReferenceId, values.ReferenceId); + RequireText(NewCallFieldKeys.IndoorLocation, values.IndoorMapZoneId); + + Require(NewCallFieldKeys.DestinationPoi, values.DestinationPoiId.HasValue && values.DestinationPoiId.Value > 0); + Require(NewCallFieldKeys.Protocols, values.HasProtocols); + Require(NewCallFieldKeys.LinkedCall, values.HasLinkedCall); + Require(NewCallFieldKeys.DispatchOn, values.DispatchOn.HasValue); + Require(NewCallFieldKeys.DispatchList, values.HasDispatchList); + + return violations; + } + + /// + /// One-line summary for an API error body or a form validation message. + /// + public static string DescribeViolations(List violations) + { + if (violations == null || violations.Count == 0) + return string.Empty; + + return "Required call fields are missing: " + string.Join(", ", violations.ConvertAll(x => x.Key)); + } + } +} diff --git a/Core/Resgrid.Model/Helpers/PoiIconHelper.cs b/Core/Resgrid.Model/Helpers/PoiIconHelper.cs new file mode 100644 index 000000000..52357605a --- /dev/null +++ b/Core/Resgrid.Model/Helpers/PoiIconHelper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace Resgrid.Model.Helpers +{ + /// + /// POI types store their icon as a web-font class name ("map-icon-hospital") because that is what + /// the web POI editor offers. The mobile and board apps ship PNG assets keyed by short names + /// ("hospital"), and previously received no icon at all for POIs -- so every POI fell through to + /// the client's default marker, which is the call icon (a flame). Resolving here means one + /// implementation for every client instead of four, and shipped clients pick up the fix without a + /// release because they already render whatever ImagePath they are handed. + /// + public static class PoiIconHelper + { + /// + /// Neutral marker used whenever a POI class has no dedicated asset. Deliberately not "call" -- + /// showing a hospital as a structure fire is worse than showing it as a generic pin. + /// + public const string DefaultIconName = "flag"; + + private static readonly Dictionary IconsByPoiClass = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + // Medical + { "hospital", "hospital" }, + { "health", "firstaid" }, + { "doctor", "firstaid" }, + { "dentist", "firstaid" }, + { "pharmacy", "firstaid" }, + { "physiotherapist", "firstaid" }, + { "veterinary-care", "firstaid" }, + + // Emergency services + { "fire-station", "station" }, + { "police", "flag" }, + + // Transport + { "airport", "aircraft" }, + { "bus-station", "bus" }, + { "car-dealer", "car" }, + { "car-rental", "car" }, + { "car-repair", "car" }, + { "car-wash", "car" }, + { "gas-station", "car" }, + { "taxi-stand", "car" }, + + // Temporary accommodation / staging + { "campground", "camper" }, + { "rv-park", "camper" }, + + // Trades and works + { "electrician", "tools" }, + { "general-contractor", "tools" }, + { "locksmith", "tools" }, + { "moving-company", "tools" }, + { "painter", "tools" }, + { "plumber", "tools" }, + { "roofing-contractor", "tools" }, + { "storage", "tools" } + }; + + /// + /// Maps a POI type's stored icon value onto an icon name the apps can resolve. + /// + /// + /// The stored value, normally a "map-icon-*" class name. Bare names ("hospital") and empty + /// values are both accepted. + /// + public static string ResolveIconName(string poiTypeImage) + { + if (string.IsNullOrWhiteSpace(poiTypeImage)) + return DefaultIconName; + + var poiClass = poiTypeImage.Trim(); + + if (poiClass.StartsWith("map-icon-", StringComparison.OrdinalIgnoreCase)) + poiClass = poiClass.Substring("map-icon-".Length); + + if (string.IsNullOrWhiteSpace(poiClass)) + return DefaultIconName; + + if (IconsByPoiClass.TryGetValue(poiClass, out var iconName)) + return iconName; + + return DefaultIconName; + } + } +} diff --git a/Core/Resgrid.Model/NewCallFieldPolicy.cs b/Core/Resgrid.Model/NewCallFieldPolicy.cs new file mode 100644 index 000000000..526f49cef --- /dev/null +++ b/Core/Resgrid.Model/NewCallFieldPolicy.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProtoBuf; + +namespace Resgrid.Model +{ + /// + /// The built-in new-call fields a department can hide or make mandatory. + /// + /// + /// Values are the wire contract shared with every client, so they are stable strings rather than + /// enum ordinals. Only fields a department could plausibly not use are listed: Name, Nature, + /// Priority and Type are load-bearing for dispatch, matching and reporting and are never + /// configurable. + /// + public static class NewCallFieldKeys + { + public const string Note = "note"; + public const string Address = "address"; + public const string Geolocation = "geolocation"; + public const string What3Words = "what3words"; + public const string PlusCode = "pluscode"; + public const string ContactName = "contactName"; + public const string ContactInfo = "contactInfo"; + public const string ExternalId = "externalId"; + public const string IncidentId = "incidentId"; + public const string ReferenceId = "referenceId"; + public const string DestinationPoi = "destinationPoi"; + public const string IndoorLocation = "indoorLocation"; + public const string Protocols = "protocols"; + public const string LinkedCall = "linkedCall"; + public const string DispatchOn = "dispatchOn"; + public const string DispatchList = "dispatchList"; + + /// Every configurable key, in the order the admin screen should list them. + public static readonly IReadOnlyList All = new List + { + Address, + Geolocation, + What3Words, + PlusCode, + DestinationPoi, + IndoorLocation, + Note, + ContactName, + ContactInfo, + ExternalId, + IncidentId, + ReferenceId, + Protocols, + LinkedCall, + DispatchOn, + DispatchList + }; + + public static bool IsKnown(string key) => + !string.IsNullOrWhiteSpace(key) && All.Any(x => string.Equals(x, key, StringComparison.OrdinalIgnoreCase)); + } + + /// Visibility and requiredness for one built-in new-call field. + [ProtoContract] + public class NewCallFieldRule + { + [ProtoMember(1)] + public string Key { get; set; } + + /// False hides the field from every call-creation surface. + [ProtoMember(2)] + public bool Visible { get; set; } = true; + + /// True blocks call creation until the field has a value. + [ProtoMember(3)] + public bool Required { get; set; } + } + + /// + /// A department's new-call form policy: which built-in fields appear, and which must be filled in + /// before a call can be created and forwarded to the field. + /// + /// + /// The default (no stored policy, or a key with no rule) is "visible, not required" — exactly how + /// Resgrid behaved before this existed, so a department that never configures anything sees no + /// change. + /// + /// A hidden field is implicitly not required: requiring something nobody can fill in would make + /// call creation impossible, so refuses that combination rather than + /// trusting stored data to be sane. + /// + [ProtoContract] + public class NewCallFieldPolicy + { + [ProtoMember(1)] + public List Rules { get; set; } = new List(); + + private NewCallFieldRule Find(string key) => + Rules?.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)); + + /// True when the field should be shown. Unknown or unconfigured fields are shown. + public bool IsVisible(string key) + { + var rule = Find(key); + + return rule?.Visible ?? true; + } + + /// + /// True when the field must have a value. Hidden fields are never required, whatever the stored + /// rule says — otherwise a bad configuration would lock the department out of creating calls. + /// + public bool IsRequired(string key) + { + var rule = Find(key); + + if (rule == null) + return false; + + return rule.Visible && rule.Required; + } + + /// True when nothing is configured, i.e. stock behaviour. + public bool IsEmpty => Rules == null || Rules.Count == 0; + + /// + /// Drops rules for keys we do not recognise and rules that say nothing (visible, not required), + /// so what gets stored stays small and a renamed key cannot linger forever. + /// + public NewCallFieldPolicy Normalize() + { + Rules = (Rules ?? new List()) + .Where(x => NewCallFieldKeys.IsKnown(x?.Key)) + .GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase) + .Select(g => g.Last()) + .Where(x => !x.Visible || x.Required) + .ToList(); + + return this; + } + } +} diff --git a/Core/Resgrid.Model/Resgrid.Model.csproj b/Core/Resgrid.Model/Resgrid.Model.csproj index 9ef49185e..b641af444 100644 --- a/Core/Resgrid.Model/Resgrid.Model.csproj +++ b/Core/Resgrid.Model/Resgrid.Model.csproj @@ -84,7 +84,7 @@ - + diff --git a/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs b/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs index 12feda8f3..edd6c67c7 100644 --- a/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs +++ b/Core/Resgrid.Model/Services/IDepartmentSettingsService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -89,6 +89,32 @@ public interface IDepartmentSettingsService /// Task<System.Boolean>. Task IsTestingEnabledForDepartmentAsync(int departmentId); + /// + /// Gets the department's new-call field policy: which built-in fields the call form shows and + /// which it requires. Returns an empty policy (everything visible, nothing required) when the + /// department has not configured one, which is how Resgrid behaved before the setting existed. + /// + /// + /// Gets how long a unit may sit in a status before the board highlights it. Returns an empty set + /// (no highlighting) when the department has not configured any, which is the pre-feature + /// behaviour. + /// + Task GetUnitStatusThresholdsAsync(int departmentId, bool bypassCache = false); + + /// + /// Saves the department's time-in-status thresholds, returning the normalised set that was stored. + /// + Task SaveUnitStatusThresholdsAsync(int departmentId, UnitStatusThresholds thresholds, + CancellationToken cancellationToken = default(CancellationToken)); + + Task GetNewCallFieldPolicyAsync(int departmentId, bool bypassCache = false); + + /// + /// Saves the department's new-call field policy, returning the normalised policy that was stored. + /// + Task SaveNewCallFieldPolicyAsync(int departmentId, NewCallFieldPolicy policy, + CancellationToken cancellationToken = default(CancellationToken)); + /// /// Gets the map center coordinates asynchronous. /// @@ -96,6 +122,24 @@ public interface IDepartmentSettingsService /// Task<Coordinates>. Task GetMapCenterCoordinatesAsync(Department department); + /// + /// Persists the department's default map center. + /// + /// + /// Supplied coordinates always win and are stored verbatim. When both are blank the department's + /// own address is geocoded and the result stored instead, so a department that never touches + /// these fields still gets its maps centred on itself rather than on the system default. An + /// operator who has set coordinates by hand is never overwritten by a geocode. + /// + /// The department identifier. + /// Operator-supplied latitude, or null/blank to geocode the address. + /// Operator-supplied longitude, or null/blank to geocode the address. + /// The department's address, used only when no coordinates were supplied. + /// Cancellation token. + /// The coordinates that were stored, or null when nothing could be resolved. + Task SaveMapCenterCoordinatesAsync(int departmentId, string latitude, string longitude, Address address, + CancellationToken cancellationToken = default(CancellationToken)); + /// /// Gets the disable automatic available for department asynchronous. /// diff --git a/Core/Resgrid.Model/UnitStatusThresholds.cs b/Core/Resgrid.Model/UnitStatusThresholds.cs new file mode 100644 index 000000000..10a670d75 --- /dev/null +++ b/Core/Resgrid.Model/UnitStatusThresholds.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProtoBuf; + +namespace Resgrid.Model +{ + /// + /// How long a unit may sit in a status before the board flags it. + /// + /// + /// Keyed by rather than by the department's own status ids, because + /// departments name and colour their statuses freely: "a unit has been dispatched for more than + /// four minutes without reporting that it has departed" is a statement about the *meaning* of the + /// status, not its label. + /// + [ProtoContract] + public class UnitStatusThreshold + { + /// The value this threshold applies to. + [ProtoMember(1)] + public int BaseType { get; set; } + + /// Seconds after which the unit is highlighted. 0 disables the warning. + [ProtoMember(2)] + public int WarnSeconds { get; set; } + + /// + /// Seconds after which the unit is escalated to a high-priority alert. 0 disables it. A value at + /// or below is treated as "alert only" -- see + /// . + /// + [ProtoMember(3)] + public int AlertSeconds { get; set; } + + /// True when this row would never fire and is not worth storing. + public bool IsEmpty => WarnSeconds <= 0 && AlertSeconds <= 0; + } + + /// + /// A department's time-in-status thresholds, driving the board's "this unit has been sitting here + /// too long" highlighting. + /// + /// + /// No configuration means no highlighting at all -- exactly how the board behaved before this + /// existed, so a department that never opens the screen sees no change. + /// + [ProtoContract] + public class UnitStatusThresholds + { + [ProtoMember(1)] + public List Thresholds { get; set; } = new List(); + + public bool IsEmpty => Thresholds == null || Thresholds.Count == 0; + + public UnitStatusThreshold Find(int baseType) => Thresholds?.FirstOrDefault(x => x.BaseType == baseType); + + /// + /// Drops rows that would never fire, collapses duplicates, and clamps an alert that is not + /// actually later than its warning. + /// + public UnitStatusThresholds Normalize() + { + Thresholds = (Thresholds ?? new List()) + .Where(x => x != null) + .GroupBy(x => x.BaseType) + .Select(g => g.Last()) + .Select(x => new UnitStatusThreshold + { + BaseType = x.BaseType, + WarnSeconds = Math.Max(0, x.WarnSeconds), + // An alert that fires no later than the warning is not an escalation; treating it as + // alert-only keeps the two levels meaningful instead of showing both at once. + AlertSeconds = Math.Max(0, x.AlertSeconds) + }) + .Select(x => + { + if (x.AlertSeconds > 0 && x.WarnSeconds > 0 && x.AlertSeconds <= x.WarnSeconds) + x.WarnSeconds = 0; + + return x; + }) + .Where(x => !x.IsEmpty) + .OrderBy(x => x.BaseType) + .ToList(); + + return this; + } + } +} diff --git a/Core/Resgrid.Model/VisibilityPayloadUnits.cs b/Core/Resgrid.Model/VisibilityPayloadUnits.cs index 296085f68..a8d2c265a 100644 --- a/Core/Resgrid.Model/VisibilityPayloadUnits.cs +++ b/Core/Resgrid.Model/VisibilityPayloadUnits.cs @@ -1,4 +1,5 @@ using ProtoBuf; +using System; using System.Collections.Generic; namespace Resgrid.Model @@ -11,5 +12,13 @@ public class VisibilityPayloadUnits [ProtoMember(2)] public Dictionary> Units { get; set; } + + /// + /// When the payload was built. A unit created after this point is not in , + /// which is indistinguishable from "no restriction" unless we know the payload's age -- so + /// consumers use this to tell a stale matrix from a permissive one and trigger a rebuild. + /// + [ProtoMember(3)] + public DateTime GeneratedOn { get; set; } } } diff --git a/Core/Resgrid.Model/VisibilityPayloadUsers.cs b/Core/Resgrid.Model/VisibilityPayloadUsers.cs index 0da9ba0ec..a0214d60d 100644 --- a/Core/Resgrid.Model/VisibilityPayloadUsers.cs +++ b/Core/Resgrid.Model/VisibilityPayloadUsers.cs @@ -1,4 +1,5 @@ using ProtoBuf; +using System; using System.Collections.Generic; @@ -12,5 +13,13 @@ public class VisibilityPayloadUsers [ProtoMember(2)] public Dictionary> Users { get; set; } + + /// + /// When the payload was built. A user who joined the department after this point is not in + /// , which is indistinguishable from "no restriction" unless we know the + /// payload's age -- so consumers use this to tell a stale matrix from a permissive one. + /// + [ProtoMember(3)] + public DateTime GeneratedOn { get; set; } } } diff --git a/Core/Resgrid.Services/AuthorizationService.cs b/Core/Resgrid.Services/AuthorizationService.cs index d821abbd5..9801232fc 100644 --- a/Core/Resgrid.Services/AuthorizationService.cs +++ b/Core/Resgrid.Services/AuthorizationService.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using Microsoft.VisualBasic; using MongoDB.Driver; using Resgrid.Model; +using Resgrid.Model.Events; using Resgrid.Model.Providers; using Resgrid.Model.Services; @@ -31,18 +33,27 @@ public class AuthorizationService : IAuthorizationService private readonly INotesService _notesService; private readonly ICacheProvider _cacheProvider; private readonly IContactsService _contactsService; + private readonly IEventAggregator _eventAggregator; private static string WhoCanViewUnitsCacheKey = "ViewUnitsSecurityMaxtix_{0}"; private static string WhoCanViewUnitLocationsCacheKey = "ViewUnitLocationsSecurityMaxtix_{0}"; private static string WhoCanViewPersonnelCacheKey = "ViewUsersSecurityMaxtix_{0}"; private static string WhoCanViewPersonnelLocationsCacheKey = "ViewUserLocationsSecurityMaxtix_{0}"; + /// + /// Suppression window for the self-heal refresh below, so one stale matrix does not enqueue a + /// rebuild per entity per request. + /// + private static readonly TimeSpan StaleMatrixRefreshDebounce = TimeSpan.FromMinutes(2); + private static readonly ConcurrentDictionary LastStaleMatrixRefresh = new ConcurrentDictionary(); + public AuthorizationService(IDepartmentsService departmentsService, IInvitesService invitesService, ICallsService callsService, IMessageService messageService, IWorkLogsService workLogsService, ISubscriptionsService subscriptionsService, IDepartmentGroupsService departmentGroupsService, IPersonnelRolesService personnelRolesService, IUnitsService unitsService, IPermissionsService permissionsService, ICalendarService calendarService, IProtocolsService protocolsService, IShiftsService shiftsService, ICustomStateService customStateService, ICertificationService certificationService, - IDocumentsService documentsService, INotesService notesService, ICacheProvider cacheProvider, IContactsService contactsService) + IDocumentsService documentsService, INotesService notesService, ICacheProvider cacheProvider, IContactsService contactsService, + IEventAggregator eventAggregator) { _departmentsService = departmentsService; _invitesService = invitesService; @@ -63,6 +74,43 @@ public AuthorizationService(IDepartmentsService departmentsService, IInvitesServ _notesService = notesService; _cacheProvider = cacheProvider; _contactsService = contactsService; + _eventAggregator = eventAggregator; + } + + /// + /// The visibility matrix is a snapshot: entities created after it was built are simply absent, + /// and every matrix check treats "absent" as unrestricted. That is why a unit added yesterday is + /// visible to everyone and a user added to a group yesterday can see nothing -- the snapshot + /// never mentions either of them. Rather than guess, ask for a rebuild and answer this one + /// request permissively; the next request reads a correct matrix. + /// + private void RequestMatrixRefresh(int departmentId, SecurityCacheTypes type) + { + var key = $"{departmentId}_{(int)type}"; + var now = DateTime.UtcNow; + + var shouldSend = false; + LastStaleMatrixRefresh.AddOrUpdate(key, _ => + { + shouldSend = true; + return now; + }, (_, last) => + { + if (now - last < StaleMatrixRefreshDebounce) + return last; + + shouldSend = true; + return now; + }); + + if (!shouldSend) + return; + + _eventAggregator?.SendMessage(new SecurityRefreshEvent + { + DepartmentId = departmentId, + Type = type + }); } #endregion Private Members and Constructors @@ -1319,8 +1367,11 @@ public async Task CanUserViewPersonViaMatrixAsync(string userToView, strin if (userToView == userId) return true; - if (!matrix.Users.ContainsKey(userToView)) + if (matrix.Users == null || !matrix.Users.ContainsKey(userToView)) + { + RequestMatrixRefresh(departmentId, SecurityCacheTypes.WhoCanViewPersonnel); return true; + } var userViewList = matrix.Users[userToView]; @@ -1344,8 +1395,11 @@ public async Task CanUserViewPersonLocationViaMatrixAsync(string userToVie if (userToView == userId) return true; - if (!matrix.Users.ContainsKey(userToView)) + if (matrix.Users == null || !matrix.Users.ContainsKey(userToView)) + { + RequestMatrixRefresh(departmentId, SecurityCacheTypes.WhoCanViewPersonnelLocations); return true; + } var userViewList = matrix.Users[userToView]; @@ -1366,8 +1420,11 @@ public async Task CanUserViewUnitViaMatrixAsync(int unitToView, string use if (matrix.EveryoneNoGroupLock) return true; - if (!matrix.Units.ContainsKey(unitToView)) + if (matrix.Units == null || !matrix.Units.ContainsKey(unitToView)) + { + RequestMatrixRefresh(departmentId, SecurityCacheTypes.WhoCanViewUnits); return true; + } var userViewList = matrix.Units[unitToView]; @@ -1388,8 +1445,11 @@ public async Task CanUserViewUnitLocationViaMatrixAsync(int unitToView, st if (matrix.EveryoneNoGroupLock) return true; - if (!matrix.Units.ContainsKey(unitToView)) + if (matrix.Units == null || !matrix.Units.ContainsKey(unitToView)) + { + RequestMatrixRefresh(departmentId, SecurityCacheTypes.WhoCanViewUnitLocations); return true; + } var userViewList = matrix.Units[unitToView]; diff --git a/Core/Resgrid.Services/DepartmentGroupsService.cs b/Core/Resgrid.Services/DepartmentGroupsService.cs index 7bc52a739..e60ee69a2 100644 --- a/Core/Resgrid.Services/DepartmentGroupsService.cs +++ b/Core/Resgrid.Services/DepartmentGroupsService.cs @@ -102,6 +102,7 @@ public async Task> GetAllAsync() // Invalidate after the transaction commits so the cache is refreshed from the fully consistent state. await InvalidateGroupInCache(saved.DepartmentGroupId); + SendGroupVisibilityRefresh(saved.DepartmentId); return saved; } @@ -117,6 +118,23 @@ public async Task InvalidateGroupInCache(int groupId) await _cacheProvider.RemoveAsync(string.Format(CacheKey, groupId)); } + /// + /// All four visibility matrices are derived from group membership and group-admin standing -- + /// unit visibility through each unit's station group, personnel visibility through the member's + /// own group. Without this, a user added to a group keeps the visibility they had when the + /// matrix was last built, which is what made units vanish from the mobile apps. + /// + private void SendGroupVisibilityRefresh(int departmentId) + { + if (departmentId <= 0) + return; + + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnits }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnitLocations }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnel }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnelLocations }); + } + public async Task> GetAllGroupsForDepartmentUnlimitedAsync(int departmentId) { // Repository returns null when the underlying query throws (it logs and swallows) @@ -242,6 +260,7 @@ async Task getDepartmentGroup() await _departmentGroupsRepository.DeleteAsync(group, cancellationToken); await InvalidateGroupInCache(groupId); + SendGroupVisibilityRefresh(group?.DepartmentId ?? 0); return true; } @@ -293,6 +312,7 @@ async Task getDepartmentGroup() var saved = await _departmentGroupsRepository.SaveOrUpdateAsync(departmentGroup, cancellationToken, true); await InvalidateGroupInCache(departmentGroup.DepartmentGroupId); + SendGroupVisibilityRefresh(saved?.DepartmentId ?? departmentGroup.DepartmentId); return saved; } @@ -309,6 +329,7 @@ await _departmentGroupMembersRepository.GetAllGroupMembersByGroupIdAsync(departm } await InvalidateGroupInCache(departmentGroup.DepartmentGroupId); + SendGroupVisibilityRefresh(departmentGroup.DepartmentId); return true; } @@ -361,6 +382,8 @@ await _departmentGroupMembersRepository await InvalidateGroupInCache(membership.DepartmentGroupId); } + SendGroupVisibilityRefresh(departmentId); + return true; } @@ -403,6 +426,7 @@ public async Task GetGroupMemberForUserAsync(string userI await _departmentGroupMembersRepository.SaveOrUpdateAsync(depMember, cancellationToken); await InvalidateGroupInCache(depMember.DepartmentGroupId); + SendGroupVisibilityRefresh(depMember.DepartmentId); return depMember; } @@ -504,6 +528,7 @@ private static Coordinates ParseCoordinates(string coordinateString) await InvalidateGroupInCache(groupId); _eventAggregator.SendMessage(new UserAssignedToGroupEvent() { DepartmentId = departmentGroup.DepartmentId, UserId = userId, Group = departmentGroup }); + SendGroupVisibilityRefresh(departmentGroup.DepartmentId); return saved; } @@ -564,7 +589,12 @@ public List GetAllUsersForGroup(int groupId) public async Task DeleteGroupMembersByGroupIdAsync(int groupId, int departmentId, CancellationToken cancellationToken = default(CancellationToken)) { - return await _departmentGroupMembersRepository.DeleteGroupMembersByGroupIdAsync(groupId, departmentId, cancellationToken); + var result = await _departmentGroupMembersRepository.DeleteGroupMembersByGroupIdAsync(groupId, departmentId, cancellationToken); + + await InvalidateGroupInCache(groupId); + SendGroupVisibilityRefresh(departmentId); + + return result; } public async Task> GetAllGroupAdminsByDepartmentIdAsync(int departmentId) diff --git a/Core/Resgrid.Services/DepartmentSettingsService.cs b/Core/Resgrid.Services/DepartmentSettingsService.cs index 5d1fbcf88..8a3819942 100644 --- a/Core/Resgrid.Services/DepartmentSettingsService.cs +++ b/Core/Resgrid.Services/DepartmentSettingsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -30,6 +31,8 @@ public class DepartmentSettingsService : IDepartmentSettingsService private static string DispatchRecommendationModeCacheKey = "DSetDispatchRecMode_{0}"; private static string DispatchRecommendationAutoDispatchCacheKey = "DSetDispatchRecAuto_{0}"; private static string DispatchRecommendationConfigCacheKey = "DSetDispatchRecConfig_{0}"; + private static string NewCallFieldPolicyCacheKey = "DSetNewCallFieldPolicy_{0}"; + private static string UnitStatusThresholdsCacheKey = "DSetUnitStatusThresholds_{0}"; private static TimeSpan LongCacheLength = TimeSpan.FromDays(14); private static TimeSpan ThatsNotLongThisIsLongCacheLength = TimeSpan.FromDays(365); private static TimeSpan TwoYearCacheLength = TimeSpan.FromDays(730); @@ -288,6 +291,76 @@ public async Task IsTestingEnabledForDepartmentAsync(int departmentId) return false; } + public async Task SaveMapCenterCoordinatesAsync(int departmentId, string latitude, string longitude, Address address, + CancellationToken cancellationToken = default(CancellationToken)) + { + // Operator-supplied coordinates are authoritative. Someone who dropped a pin on the exact + // spot they want their boards centred on must never have it moved by a geocoder. + if (!String.IsNullOrWhiteSpace(latitude) && !String.IsNullOrWhiteSpace(longitude)) + { + var sanitizedLatitude = StringHelpers.SanitizeCoordinatesString(latitude); + var sanitizedLongitude = StringHelpers.SanitizeCoordinatesString(longitude); + + await SaveOrUpdateSettingAsync(departmentId, $"{sanitizedLatitude},{sanitizedLongitude}", + DepartmentSettingTypes.BigBoardMapCenterGpsCoordinates, cancellationToken); + + if (double.TryParse(sanitizedLatitude, out var storedLatitude) && double.TryParse(sanitizedLongitude, out var storedLongitude)) + return new Coordinates { Latitude = storedLatitude, Longitude = storedLongitude }; + + return null; + } + + // Only one of the two filled in is an operator mid-edit, not an instruction to geocode. + // Leaving the stored value alone is the safe reading. + if (!String.IsNullOrWhiteSpace(latitude) || !String.IsNullOrWhiteSpace(longitude)) + return null; + + if (address == null || String.IsNullOrWhiteSpace(address.Address1)) + return null; + + var geocoded = await GeocodeAddressAsync(address); + + if (geocoded == null) + return null; + + await SaveOrUpdateSettingAsync(departmentId, + $"{geocoded.Latitude.Value.ToString(CultureInfo.InvariantCulture)},{geocoded.Longitude.Value.ToString(CultureInfo.InvariantCulture)}", + DepartmentSettingTypes.BigBoardMapCenterGpsCoordinates, cancellationToken); + + return geocoded; + } + + /// + /// Geocodes an address into coordinates, or null when the provider could not resolve it. + /// Failures are not fatal anywhere this is used -- the caller falls back to its own default. + /// + private async Task GeocodeAddressAsync(Address address) + { + try + { + var result = await _geoLocationProvider.GetLatLonFromAddress( + $"{address.Address1} {address.City} {address.State} {address.PostalCode}"); + + if (String.IsNullOrWhiteSpace(result)) + return null; + + var parts = result.Split(char.Parse(",")); + + if (parts.Length != 2) + return null; + + if (double.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var latitude) && + double.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var longitude)) + return new Coordinates { Latitude = latitude, Longitude = longitude }; + } + catch (Exception ex) + { + Logging.LogException(ex, $"{nameof(GeocodeAddressAsync)} failed resolving a department address."); + } + + return null; + } + public async Task GetMapCenterCoordinatesAsync(Department department) { if (department == null) @@ -894,6 +967,102 @@ async Task getSetting() return await SaveOrUpdateSettingAsync(departmentId, enabled.ToString(), DepartmentSettingTypes.DispatchRecommendationAutoDispatch, cancellationToken); } + public async Task GetUnitStatusThresholdsAsync(int departmentId, bool bypassCache = false) + { + async Task getSetting() + { + var setting = await GetSettingByDepartmentIdType(departmentId, DepartmentSettingTypes.UnitStatusThresholds); + return setting?.Setting ?? string.Empty; + } + + string value; + if (Config.SystemBehaviorConfig.CacheEnabled && !bypassCache) + value = await _cacheProvider.RetrieveAsync(string.Format(UnitStatusThresholdsCacheKey, departmentId), getSetting, LongCacheLength); + else + value = await getSetting(); + + if (!String.IsNullOrWhiteSpace(value)) + { + try + { + var thresholds = ObjectSerialization.Deserialize(value); + + if (thresholds != null) + return thresholds.Normalize(); + } + catch (Exception) + { + // A corrupt blob falls back to "no highlighting", which is the pre-feature behaviour + // and can never make the board misleading. + } + } + + return new UnitStatusThresholds(); + } + + public async Task SaveUnitStatusThresholdsAsync(int departmentId, UnitStatusThresholds thresholds, + CancellationToken cancellationToken = default(CancellationToken)) + { + var normalized = (thresholds ?? new UnitStatusThresholds()).Normalize(); + + if (normalized.IsEmpty) + await DeleteSettingAsync(departmentId, DepartmentSettingTypes.UnitStatusThresholds, cancellationToken); + else + await SaveOrUpdateSettingAsync(departmentId, ObjectSerialization.Serialize(normalized), + DepartmentSettingTypes.UnitStatusThresholds, cancellationToken); + + return normalized; + } + + public async Task GetNewCallFieldPolicyAsync(int departmentId, bool bypassCache = false) + { + async Task getSetting() + { + var setting = await GetSettingByDepartmentIdType(departmentId, DepartmentSettingTypes.NewCallFieldPolicy); + return setting?.Setting ?? string.Empty; + } + + string value; + if (Config.SystemBehaviorConfig.CacheEnabled && !bypassCache) + value = await _cacheProvider.RetrieveAsync(string.Format(NewCallFieldPolicyCacheKey, departmentId), getSetting, LongCacheLength); + else + value = await getSetting(); + + if (!String.IsNullOrWhiteSpace(value)) + { + try + { + var policy = ObjectSerialization.Deserialize(value); + + if (policy != null) + return policy.Normalize(); + } + catch (Exception) + { + // A corrupt blob must never stop a department creating calls; fall back to stock + // behaviour (everything visible, nothing required). + } + } + + return new NewCallFieldPolicy(); + } + + public async Task SaveNewCallFieldPolicyAsync(int departmentId, NewCallFieldPolicy policy, + CancellationToken cancellationToken = default(CancellationToken)) + { + var normalized = (policy ?? new NewCallFieldPolicy()).Normalize(); + + // Nothing worth storing means stock behaviour; drop the setting rather than persisting an + // empty blob that later readers have to interpret. + if (normalized.IsEmpty) + await DeleteSettingAsync(departmentId, DepartmentSettingTypes.NewCallFieldPolicy, cancellationToken); + else + await SaveOrUpdateSettingAsync(departmentId, ObjectSerialization.Serialize(normalized), + DepartmentSettingTypes.NewCallFieldPolicy, cancellationToken); + + return normalized; + } + public async Task GetDispatchRecommendationConfigAsync(int departmentId, bool bypassCache = false) { async Task getSetting() @@ -1197,6 +1366,12 @@ private async Task InvalidateSettingCacheAsync(int departmentId, DepartmentSetti case DepartmentSettingTypes.DispatchRecommendationConfig: cacheKey = string.Format(DispatchRecommendationConfigCacheKey, departmentId); break; + case DepartmentSettingTypes.NewCallFieldPolicy: + cacheKey = string.Format(NewCallFieldPolicyCacheKey, departmentId); + break; + case DepartmentSettingTypes.UnitStatusThresholds: + cacheKey = string.Format(UnitStatusThresholdsCacheKey, departmentId); + break; } if (!string.IsNullOrWhiteSpace(cacheKey)) diff --git a/Core/Resgrid.Services/DepartmentsService.cs b/Core/Resgrid.Services/DepartmentsService.cs index e2fc7220d..d0f9174c3 100644 --- a/Core/Resgrid.Services/DepartmentsService.cs +++ b/Core/Resgrid.Services/DepartmentsService.cs @@ -285,6 +285,21 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen return await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); } + /// + /// Department membership and admin standing feed every visibility matrix (admins are always in + /// the allow list). Without a rebuild, a user added or removed today keeps yesterday's answer. + /// + private void SendMembershipVisibilityRefresh(int departmentId) + { + if (departmentId <= 0) + return; + + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnits }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnitLocations }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnel }); + _eventAggregator.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnelLocations }); + } + public async Task DeleteUserAsync(int departmentId, string userIdToDelete, string deletingUserId, CancellationToken cancellationToken = default(CancellationToken)) { var member = await _departmentMembersRepository.GetDepartmentMemberByDepartmentIdAndUserIdAsync(departmentId, userIdToDelete); @@ -311,6 +326,7 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen InvalidatePersonnelNamesInCache(departmentId); _usersService.ClearCacheForDepartment(departmentId); InvalidateDepartmentMembers(); + SendMembershipVisibilityRefresh(departmentId); return member2; } @@ -425,6 +441,7 @@ public async Task IsMemberOfDepartmentAsync(int departmentId, string userI }); InvalidateDepartmentUsersInCache(departmentId); + SendMembershipVisibilityRefresh(departmentId); return saved; } @@ -674,6 +691,7 @@ async Task getDepartmentMember() InvalidateDepartmentMemberInCache(departmentMember.UserId, departmentMember.DepartmentId); InvalidateDepartmentUserInCache(departmentMember.UserId, departmentMember.User); await InvalidateAllDepartmentsCache(departmentMember.DepartmentId); + SendMembershipVisibilityRefresh(departmentMember.DepartmentId); return saved; } diff --git a/Core/Resgrid.Services/PersonnelRolesService.cs b/Core/Resgrid.Services/PersonnelRolesService.cs index 78cda1798..437a0ee23 100644 --- a/Core/Resgrid.Services/PersonnelRolesService.cs +++ b/Core/Resgrid.Services/PersonnelRolesService.cs @@ -3,6 +3,8 @@ using System.Threading; using System.Threading.Tasks; using Resgrid.Model; +using Resgrid.Model.Events; +using Resgrid.Model.Providers; using Resgrid.Model.Repositories; using Resgrid.Model.Services; @@ -14,14 +16,33 @@ public class PersonnelRolesService : IPersonnelRolesService private readonly IPersonnelRoleUsersRepository _personnelRoleUsersRepository; private readonly IDepartmentMembersRepository _departmentMemberRepository; private readonly ISubscriptionsService _subscriptionsService; + private readonly IEventAggregator _eventAggregator; public PersonnelRolesService(IPersonnelRolesRepository personnelRolesRepository, IPersonnelRoleUsersRepository personnelRoleUsersRepository, - ISubscriptionsService subscriptionsService, IDepartmentMembersRepository departmentMemberRepository) + ISubscriptionsService subscriptionsService, IDepartmentMembersRepository departmentMemberRepository, + IEventAggregator eventAggregator) { _personnelRolesRepository = personnelRolesRepository; _personnelRoleUsersRepository = personnelRoleUsersRepository; _subscriptionsService = subscriptionsService; _departmentMemberRepository = departmentMemberRepository; + _eventAggregator = eventAggregator; + } + + /// + /// The "department admins and select roles" permission modes resolve role membership into the + /// visibility matrices at build time, so a role change has to rebuild them or the user keeps + /// yesterday's visibility. + /// + private void SendRoleVisibilityRefresh(int departmentId) + { + if (departmentId <= 0) + return; + + _eventAggregator?.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnits }); + _eventAggregator?.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewUnitLocations }); + _eventAggregator?.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnel }); + _eventAggregator?.SendMessage(new SecurityRefreshEvent() { DepartmentId = departmentId, Type = SecurityCacheTypes.WhoCanViewPersonnelLocations }); } public async Task> GetRolesForDepartmentAsync(int departmentId) @@ -68,7 +89,10 @@ public async Task GetRoleByDepartmentAndNameAsync(int departmentI { var role = await GetRoleByIdAsync(roleId); - return await _personnelRolesRepository.DeleteAsync(role, cancellationToken); + var result = await _personnelRolesRepository.DeleteAsync(role, cancellationToken); + SendRoleVisibilityRefresh(role?.DepartmentId ?? 0); + + return result; } public async Task DeleteRoleUsersAsync(List users, CancellationToken cancellationToken = default(CancellationToken)) @@ -78,6 +102,8 @@ public async Task GetRoleByDepartmentAndNameAsync(int departmentI await _personnelRoleUsersRepository.DeleteAsync(user, cancellationToken); } + SendRoleVisibilityRefresh(users?.FirstOrDefault()?.DepartmentId ?? 0); + return true; } @@ -117,6 +143,8 @@ where users.Select(x => x.UserId).Contains(rolesGroup.Key) await _personnelRoleUsersRepository.DeleteAsync(personnelRoleUser, cancellationToken); } + SendRoleVisibilityRefresh(departmentId); + return true; } @@ -140,6 +168,8 @@ where users.Select(x => x.UserId).Contains(rolesGroup.Key) } } + SendRoleVisibilityRefresh(departmentId); + return true; } diff --git a/Core/Resgrid.Services/Resgrid.Services.csproj b/Core/Resgrid.Services/Resgrid.Services.csproj index 9f3a6c997..3a45ef479 100644 --- a/Core/Resgrid.Services/Resgrid.Services.csproj +++ b/Core/Resgrid.Services/Resgrid.Services.csproj @@ -15,7 +15,7 @@ - + diff --git a/Core/Resgrid.Services/UnitsService.cs b/Core/Resgrid.Services/UnitsService.cs index 9a1017858..52bcbcdf8 100644 --- a/Core/Resgrid.Services/UnitsService.cs +++ b/Core/Resgrid.Services/UnitsService.cs @@ -76,9 +76,32 @@ public async Task> GetAllAsync() _eventAggregator.SendMessage(new DepartmentSettingsUpdateEvent() { DepartmentId = saved.DepartmentId }); + // The unit visibility matrix is keyed by unit id and by station group; a new unit is absent + // from it and a re-stationed unit is filed under the wrong group until it is rebuilt. + SendUnitVisibilityRefresh(saved.DepartmentId); + return saved; } + /// + /// Both unit matrices are derived from the department's unit list and each unit's station group, + /// so any unit create/update/delete invalidates both. + /// + private void SendUnitVisibilityRefresh(int departmentId) + { + _eventAggregator.SendMessage(new SecurityRefreshEvent() + { + DepartmentId = departmentId, + Type = SecurityCacheTypes.WhoCanViewUnits + }); + + _eventAggregator.SendMessage(new SecurityRefreshEvent() + { + DepartmentId = departmentId, + Type = SecurityCacheTypes.WhoCanViewUnitLocations + }); + } + public async Task SaveUnitLogAsync(UnitLog unitLog, CancellationToken cancellationToken = default(CancellationToken)) { return await _unitLogsRepository.SaveOrUpdateAsync(unitLog, cancellationToken); @@ -155,6 +178,7 @@ public async Task GetUnitByIdAsync(int unitId) await _limitsService.InvalidateDepartmentsEntityLimitsCache(unit.DepartmentId); _eventAggregator.SendMessage(new DepartmentSettingsUpdateEvent() { DepartmentId = unit.DepartmentId }); + SendUnitVisibilityRefresh(unit.DepartmentId); return true; } diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..f1c595245 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/Providers/Resgrid.Providers.MigrationsPg/Resgrid.Providers.MigrationsPg.csproj b/Providers/Resgrid.Providers.MigrationsPg/Resgrid.Providers.MigrationsPg.csproj index 7025c4567..d429895aa 100644 --- a/Providers/Resgrid.Providers.MigrationsPg/Resgrid.Providers.MigrationsPg.csproj +++ b/Providers/Resgrid.Providers.MigrationsPg/Resgrid.Providers.MigrationsPg.csproj @@ -25,7 +25,7 @@ - + diff --git a/Providers/Resgrid.Providers.Workflow/Resgrid.Providers.Workflow.csproj b/Providers/Resgrid.Providers.Workflow/Resgrid.Providers.Workflow.csproj index 7ab732dda..aa6e9f4f7 100644 --- a/Providers/Resgrid.Providers.Workflow/Resgrid.Providers.Workflow.csproj +++ b/Providers/Resgrid.Providers.Workflow/Resgrid.Providers.Workflow.csproj @@ -15,10 +15,10 @@ - + - + diff --git a/Repositories/Resgrid.Repositories.NoSqlRepository/Resgrid.Repositories.NoSqlRepository.csproj b/Repositories/Resgrid.Repositories.NoSqlRepository/Resgrid.Repositories.NoSqlRepository.csproj index 3d970600a..740006069 100644 --- a/Repositories/Resgrid.Repositories.NoSqlRepository/Resgrid.Repositories.NoSqlRepository.csproj +++ b/Repositories/Resgrid.Repositories.NoSqlRepository/Resgrid.Repositories.NoSqlRepository.csproj @@ -7,7 +7,7 @@ - + diff --git a/Tests/Resgrid.Tests/Models/NewCallFieldPolicyTests.cs b/Tests/Resgrid.Tests/Models/NewCallFieldPolicyTests.cs new file mode 100644 index 000000000..9f84e8eb3 --- /dev/null +++ b/Tests/Resgrid.Tests/Models/NewCallFieldPolicyTests.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using Resgrid.Model; +using Resgrid.Model.Helpers; + +namespace Resgrid.Tests.Models +{ + /// + /// The policy decides whether a call-taker can forward an incident to the field. Two rules carry + /// the weight: a department that has configured nothing behaves exactly as before, and a hidden + /// field can never be required (which would lock the department out of creating calls at all). + /// + [TestFixture] + public class NewCallFieldPolicyTests + { + private static NewCallFieldPolicy PolicyWith(params NewCallFieldRule[] rules) => + new NewCallFieldPolicy { Rules = new List(rules) }; + + [Test] + public void An_unconfigured_policy_shows_everything_and_requires_nothing() + { + var policy = new NewCallFieldPolicy(); + + policy.IsEmpty.Should().BeTrue(); + + foreach (var key in NewCallFieldKeys.All) + { + policy.IsVisible(key).Should().BeTrue($"{key} should default to visible"); + policy.IsRequired(key).Should().BeFalse($"{key} should default to optional"); + } + } + + [Test] + public void A_field_with_no_rule_keeps_the_default_even_when_others_are_configured() + { + var policy = PolicyWith(new NewCallFieldRule { Key = NewCallFieldKeys.ContactInfo, Visible = false }); + + policy.IsVisible(NewCallFieldKeys.Address).Should().BeTrue(); + policy.IsRequired(NewCallFieldKeys.Address).Should().BeFalse(); + } + + [Test] + public void A_hidden_field_is_never_required() + { + // Stored data can be inconsistent — an admin hides a field that was previously required. + // Honouring both would make call creation impossible. + var policy = PolicyWith(new NewCallFieldRule { Key = NewCallFieldKeys.IncidentId, Visible = false, Required = true }); + + policy.IsVisible(NewCallFieldKeys.IncidentId).Should().BeFalse(); + policy.IsRequired(NewCallFieldKeys.IncidentId).Should().BeFalse(); + } + + [Test] + public void Keys_are_matched_case_insensitively() + { + var policy = PolicyWith(new NewCallFieldRule { Key = "ADDRESS", Visible = false }); + + policy.IsVisible(NewCallFieldKeys.Address).Should().BeFalse(); + } + + [Test] + public void Normalize_drops_unknown_keys_and_rules_that_say_nothing() + { + var policy = PolicyWith( + new NewCallFieldRule { Key = "somethingWeRemoved", Visible = false }, + new NewCallFieldRule { Key = NewCallFieldKeys.Note, Visible = true, Required = false }, + new NewCallFieldRule { Key = NewCallFieldKeys.Address, Visible = true, Required = true }); + + policy.Normalize(); + + policy.Rules.Should().HaveCount(1); + policy.Rules[0].Key.Should().Be(NewCallFieldKeys.Address); + } + + [Test] + public void Normalize_keeps_the_last_rule_when_a_key_is_duplicated() + { + var policy = PolicyWith( + new NewCallFieldRule { Key = NewCallFieldKeys.Note, Visible = false }, + new NewCallFieldRule { Key = NewCallFieldKeys.Note, Visible = true, Required = true }); + + policy.Normalize(); + + policy.Rules.Should().HaveCount(1); + policy.Rules[0].Required.Should().BeTrue(); + policy.Rules[0].Visible.Should().BeTrue(); + } + } + + [TestFixture] + public class NewCallFieldPolicyValidatorTests + { + private static NewCallFieldPolicy Requiring(params string[] keys) + { + var policy = new NewCallFieldPolicy(); + + foreach (var key in keys) + policy.Rules.Add(new NewCallFieldRule { Key = key, Visible = true, Required = true }); + + return policy; + } + + [Test] + public void An_unconfigured_department_can_submit_an_empty_call() + { + var violations = NewCallFieldPolicyValidator.Validate(new NewCallFieldPolicy(), new NewCallFieldValues()); + + violations.Should().BeEmpty(); + } + + [Test] + public void Reports_every_required_field_left_blank() + { + var policy = Requiring(NewCallFieldKeys.Address, NewCallFieldKeys.ContactInfo, NewCallFieldKeys.DispatchList); + + var violations = NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues()); + + violations.Should().HaveCount(3); + violations.ConvertAll(x => x.Key).Should().BeEquivalentTo(new[] + { + NewCallFieldKeys.Address, + NewCallFieldKeys.ContactInfo, + NewCallFieldKeys.DispatchList + }); + } + + [Test] + public void Whitespace_does_not_satisfy_a_required_field() + { + var policy = Requiring(NewCallFieldKeys.Address); + + var violations = NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues { Address = " " }); + + violations.Should().HaveCount(1); + } + + [Test] + public void Passes_when_every_required_field_is_supplied() + { + var policy = Requiring(NewCallFieldKeys.Address, NewCallFieldKeys.DestinationPoi, NewCallFieldKeys.DispatchOn, NewCallFieldKeys.DispatchList); + + var violations = NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues + { + Address = "Nieuwstraat 14, 9620 Zottegem", + DestinationPoiId = 12, + DispatchOn = System.DateTime.UtcNow, + HasDispatchList = true + }); + + violations.Should().BeEmpty(); + } + + [Test] + public void A_zero_destination_poi_does_not_count_as_supplied() + { + var policy = Requiring(NewCallFieldKeys.DestinationPoi); + + var violations = NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues { DestinationPoiId = 0 }); + + violations.Should().HaveCount(1); + } + + [Test] + public void A_hidden_required_field_is_not_enforced() + { + var policy = new NewCallFieldPolicy(); + policy.Rules.Add(new NewCallFieldRule { Key = NewCallFieldKeys.Address, Visible = false, Required = true }); + + var violations = NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues()); + + violations.Should().BeEmpty(); + } + + [Test] + public void Describes_violations_for_an_error_body() + { + var policy = Requiring(NewCallFieldKeys.Address); + + var description = NewCallFieldPolicyValidator.DescribeViolations( + NewCallFieldPolicyValidator.Validate(policy, new NewCallFieldValues())); + + description.Should().Contain(NewCallFieldKeys.Address); + } + } +} diff --git a/Tests/Resgrid.Tests/Models/PoiIconHelperTests.cs b/Tests/Resgrid.Tests/Models/PoiIconHelperTests.cs new file mode 100644 index 000000000..ea33d0a17 --- /dev/null +++ b/Tests/Resgrid.Tests/Models/PoiIconHelperTests.cs @@ -0,0 +1,58 @@ +using FluentAssertions; +using NUnit.Framework; +using Resgrid.Model.Helpers; + +namespace Resgrid.Tests.Models +{ + /// + /// POI markers used to be sent with a null ImagePath, and every client then fell through to its + /// default marker -- the call icon, which is a flame. Customers saw hospitals drawn as structure + /// fires. These guard the mapping that replaced that behaviour. + /// + [TestFixture] + public class PoiIconHelperTests + { + [TestCase("map-icon-hospital", "hospital")] + [TestCase("map-icon-fire-station", "station")] + [TestCase("map-icon-airport", "aircraft")] + [TestCase("map-icon-bus-station", "bus")] + [TestCase("map-icon-campground", "camper")] + [TestCase("map-icon-pharmacy", "firstaid")] + [TestCase("map-icon-car-repair", "car")] + [TestCase("map-icon-plumber", "tools")] + public void ResolveIconName_maps_known_poi_classes(string poiTypeImage, string expected) + { + PoiIconHelper.ResolveIconName(poiTypeImage).Should().Be(expected); + } + + [Test] + public void ResolveIconName_accepts_a_bare_name_without_the_prefix() + { + PoiIconHelper.ResolveIconName("hospital").Should().Be("hospital"); + } + + [Test] + public void ResolveIconName_is_case_insensitive() + { + PoiIconHelper.ResolveIconName("MAP-ICON-HOSPITAL").Should().Be("hospital"); + } + + [TestCase("")] + [TestCase(" ")] + [TestCase(null)] + [TestCase("map-icon-")] + [TestCase("map-icon-something-we-have-no-asset-for")] + public void ResolveIconName_falls_back_to_a_neutral_pin(string poiTypeImage) + { + PoiIconHelper.ResolveIconName(poiTypeImage).Should().Be(PoiIconHelper.DefaultIconName); + } + + [Test] + public void ResolveIconName_never_returns_the_call_icon() + { + // The whole point: a POI must never be drawn with the flame used for active calls. + PoiIconHelper.DefaultIconName.Should().NotBe("call"); + PoiIconHelper.ResolveIconName("map-icon-unknown-thing").Should().NotBe("call"); + } + } +} diff --git a/Tests/Resgrid.Tests/Models/UnitStatusThresholdsTests.cs b/Tests/Resgrid.Tests/Models/UnitStatusThresholdsTests.cs new file mode 100644 index 000000000..9ec9e85d2 --- /dev/null +++ b/Tests/Resgrid.Tests/Models/UnitStatusThresholdsTests.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using Resgrid.Model; + +namespace Resgrid.Tests.Models +{ + /// + /// Drives the Big Board's "this unit has been sitting here too long" highlighting. The invariant + /// that matters: an unconfigured department highlights nothing, exactly as before the feature. + /// + [TestFixture] + public class UnitStatusThresholdsTests + { + private static UnitStatusThresholds With(params UnitStatusThreshold[] thresholds) => + new UnitStatusThresholds { Thresholds = new List(thresholds) }; + + [Test] + public void An_unconfigured_department_has_no_thresholds() + { + var thresholds = new UnitStatusThresholds(); + + thresholds.IsEmpty.Should().BeTrue(); + thresholds.Find((int)ActionBaseTypes.Dispatched).Should().BeNull(); + } + + [Test] + public void Finds_a_threshold_by_base_type() + { + // The customer case: dispatched for more than four minutes without reporting departed. + var thresholds = With(new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = 240, AlertSeconds = 480 }); + + var found = thresholds.Find((int)ActionBaseTypes.Dispatched); + + found.Should().NotBeNull(); + found.WarnSeconds.Should().Be(240); + found.AlertSeconds.Should().Be(480); + } + + [Test] + public void Normalize_drops_rows_that_would_never_fire() + { + var thresholds = With( + new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.OnScene, WarnSeconds = 0, AlertSeconds = 0 }, + new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = 240 }); + + thresholds.Normalize(); + + thresholds.Thresholds.Should().HaveCount(1); + thresholds.Thresholds[0].BaseType.Should().Be((int)ActionBaseTypes.Dispatched); + } + + [Test] + public void Normalize_clamps_negative_values_to_zero() + { + var thresholds = With(new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = -60, AlertSeconds = 300 }); + + thresholds.Normalize(); + + thresholds.Thresholds[0].WarnSeconds.Should().Be(0); + thresholds.Thresholds[0].AlertSeconds.Should().Be(300); + } + + [Test] + public void An_alert_that_is_not_later_than_the_warning_becomes_alert_only() + { + // Showing both levels at the same moment makes the escalation meaningless; keeping the + // alert is the safer of the two readings. + var thresholds = With(new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = 300, AlertSeconds = 300 }); + + thresholds.Normalize(); + + thresholds.Thresholds[0].WarnSeconds.Should().Be(0); + thresholds.Thresholds[0].AlertSeconds.Should().Be(300); + } + + [Test] + public void Normalize_keeps_the_last_row_when_a_base_type_is_duplicated() + { + var thresholds = With( + new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = 60 }, + new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Dispatched, WarnSeconds = 240 }); + + thresholds.Normalize(); + + thresholds.Thresholds.Should().HaveCount(1); + thresholds.Thresholds[0].WarnSeconds.Should().Be(240); + } + + [Test] + public void Normalize_of_an_all_empty_set_leaves_it_empty() + { + var thresholds = With(new UnitStatusThreshold { BaseType = (int)ActionBaseTypes.Responding, WarnSeconds = 0, AlertSeconds = 0 }); + + thresholds.Normalize(); + + thresholds.IsEmpty.Should().BeTrue(); + } + } +} diff --git a/Tests/Resgrid.Tests/Resgrid.Tests.csproj b/Tests/Resgrid.Tests/Resgrid.Tests.csproj index b648528f2..953931146 100644 --- a/Tests/Resgrid.Tests/Resgrid.Tests.csproj +++ b/Tests/Resgrid.Tests/Resgrid.Tests.csproj @@ -33,7 +33,7 @@ - + diff --git a/Tests/Resgrid.Tests/Services/AuthorizationServiceTests.cs b/Tests/Resgrid.Tests/Services/AuthorizationServiceTests.cs index e69053367..ecb64f781 100644 --- a/Tests/Resgrid.Tests/Services/AuthorizationServiceTests.cs +++ b/Tests/Resgrid.Tests/Services/AuthorizationServiceTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using FluentAssertions; using Moq; using NUnit.Framework; @@ -35,6 +35,7 @@ public class with_the_authorization_service : TestBase protected Mock _notesServiceMock; protected Mock _cacheProviderMock; protected Mock _contactsServiceMock; + protected Mock _eventAggregatorMock; protected with_the_authorization_service() { @@ -57,6 +58,7 @@ protected with_the_authorization_service() _notesServiceMock = new Mock(); _cacheProviderMock = new Mock(); _contactsServiceMock = new Mock(); + _eventAggregatorMock = new Mock(); _authorizationService = new AuthorizationService( _departmentsServiceMock.Object, @@ -77,7 +79,8 @@ protected with_the_authorization_service() _documentsServiceMock.Object, _notesServiceMock.Object, _cacheProviderMock.Object, - _contactsServiceMock.Object); + _contactsServiceMock.Object, + _eventAggregatorMock.Object); } /// diff --git a/Tests/Resgrid.Tests/Services/CalendarServiceCheckInTests.cs b/Tests/Resgrid.Tests/Services/CalendarServiceCheckInTests.cs index 38ff9ee71..3f5ae2636 100644 --- a/Tests/Resgrid.Tests/Services/CalendarServiceCheckInTests.cs +++ b/Tests/Resgrid.Tests/Services/CalendarServiceCheckInTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -304,7 +304,8 @@ private void SetupAuthService() new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object); + new Mock().Object, + new Mock().Object); } [Test] diff --git a/Tests/Resgrid.Tests/Services/DepartmentSettingsServiceMapCenterTests.cs b/Tests/Resgrid.Tests/Services/DepartmentSettingsServiceMapCenterTests.cs new file mode 100644 index 000000000..31db8853d --- /dev/null +++ b/Tests/Resgrid.Tests/Services/DepartmentSettingsServiceMapCenterTests.cs @@ -0,0 +1,150 @@ +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using Resgrid.Model; +using Resgrid.Model.Providers; +using Resgrid.Model.Repositories; +using Resgrid.Model.Services; +using Resgrid.Services; + +namespace Resgrid.Tests.Services +{ + /// + /// The department's map center drives every map in the web app and all five client apps, so the + /// rule that matters is: an operator-supplied pin is never moved by the geocoder. + /// + [TestFixture] + public class DepartmentSettingsServiceMapCenterTests + { + private const int DepartmentId = 42; + + private Mock _departmentSettingsRepository; + private Mock _addressService; + private Mock _geoLocationProvider; + private Mock _cacheProvider; + private DepartmentSettingsService _service; + + private static Address ZottegemAddress => new Address + { + Address1 = "Nieuwstraat 14", + City = "Zottegem", + State = "Oost-Vlaanderen", + PostalCode = "9620", + Country = "Belgium" + }; + + [SetUp] + public void SetUp() + { + _departmentSettingsRepository = new Mock(); + _addressService = new Mock(); + _geoLocationProvider = new Mock(); + _cacheProvider = new Mock(); + + _departmentSettingsRepository + .Setup(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((DepartmentSetting setting, CancellationToken _, bool __) => setting); + + _service = new DepartmentSettingsService( + _departmentSettingsRepository.Object, + _addressService.Object, + _geoLocationProvider.Object, + _cacheProvider.Object); + } + + private DepartmentSetting CapturedSetting() + { + DepartmentSetting captured = null; + + _departmentSettingsRepository.Verify( + x => x.SaveOrUpdateAsync(It.Is(s => AssignAndPass(s, out captured)), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + + return captured; + } + + private static bool AssignAndPass(DepartmentSetting setting, out DepartmentSetting captured) + { + captured = setting; + return true; + } + + [Test] + public async Task Stores_supplied_coordinates_verbatim_without_geocoding() + { + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, "50.8698", "3.8102", ZottegemAddress); + + result.Should().NotBeNull(); + result.Latitude.Should().BeApproximately(50.8698, 0.00001); + result.Longitude.Should().BeApproximately(3.8102, 0.00001); + + // The whole point: a hand-set pin must never trigger, or be replaced by, a geocode. + _geoLocationProvider.Verify(x => x.GetLatLonFromAddress(It.IsAny()), Times.Never); + + var saved = CapturedSetting(); + saved.SettingType.Should().Be((int)DepartmentSettingTypes.BigBoardMapCenterGpsCoordinates); + saved.Setting.Should().Be("50.8698,3.8102"); + } + + [Test] + public async Task Geocodes_the_department_address_when_both_coordinates_are_blank() + { + _geoLocationProvider.Setup(x => x.GetLatLonFromAddress(It.IsAny())).ReturnsAsync("50.8698,3.8102"); + + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, "", " ", ZottegemAddress); + + result.Should().NotBeNull(); + result.Latitude.Should().BeApproximately(50.8698, 0.00001); + result.Longitude.Should().BeApproximately(3.8102, 0.00001); + + CapturedSetting().Setting.Should().Be("50.8698,3.8102"); + } + + [TestCase("50.8698", "")] + [TestCase("", "3.8102")] + public async Task Leaves_the_stored_value_alone_when_only_one_coordinate_is_filled_in(string latitude, string longitude) + { + // Half-filled is an operator mid-edit, not an instruction to geocode. Doing nothing is the + // only reading that cannot lose their work. + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, latitude, longitude, ZottegemAddress); + + result.Should().BeNull(); + _geoLocationProvider.Verify(x => x.GetLatLonFromAddress(It.IsAny()), Times.Never); + _departmentSettingsRepository.Verify(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task Does_nothing_when_there_is_no_address_to_geocode() + { + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, null, null, null); + + result.Should().BeNull(); + _geoLocationProvider.Verify(x => x.GetLatLonFromAddress(It.IsAny()), Times.Never); + _departmentSettingsRepository.Verify(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task Stores_nothing_when_the_geocoder_cannot_resolve_the_address() + { + _geoLocationProvider.Setup(x => x.GetLatLonFromAddress(It.IsAny())).ReturnsAsync((string)null); + + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, null, null, ZottegemAddress); + + result.Should().BeNull(); + _departmentSettingsRepository.Verify(x => x.SaveOrUpdateAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task Survives_a_throwing_geocoder() + { + // A provider outage must not stop the rest of the department settings save. + _geoLocationProvider.Setup(x => x.GetLatLonFromAddress(It.IsAny())).ThrowsAsync(new System.Exception("provider down")); + + var result = await _service.SaveMapCenterCoordinatesAsync(DepartmentId, null, null, ZottegemAddress); + + result.Should().BeNull(); + } + } +} diff --git a/Web/Resgrid.Web.Eventing/Dockerfile b/Web/Resgrid.Web.Eventing/Dockerfile index f7a09a97f..e6111be5b 100644 --- a/Web/Resgrid.Web.Eventing/Dockerfile +++ b/Web/Resgrid.Web.Eventing/Dockerfile @@ -12,6 +12,10 @@ EXPOSE 80 FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab513c6ca4476ddb2f9f11adb9c04aa7c89 AS build ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj", "Web/Resgrid.Web.Eventing/"] COPY ["Providers/Resgrid.Providers.Bus.Rabbit/Resgrid.Providers.Bus.Rabbit.csproj", "Providers/Resgrid.Providers.Bus.Rabbit/"] COPY ["Core/Resgrid.Framework/Resgrid.Framework.csproj", "Core/Resgrid.Framework/"] diff --git a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj index 744eecaae..4227c30db 100644 --- a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj +++ b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj @@ -41,7 +41,7 @@ - + diff --git a/Web/Resgrid.Web.Mcp/Dockerfile b/Web/Resgrid.Web.Mcp/Dockerfile index 3ec20a5f5..70c71175e 100644 --- a/Web/Resgrid.Web.Mcp/Dockerfile +++ b/Web/Resgrid.Web.Mcp/Dockerfile @@ -13,6 +13,10 @@ EXPOSE 8080 FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab513c6ca4476ddb2f9f11adb9c04aa7c89 AS build ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj", "Web/Resgrid.Web.Mcp/"] COPY ["Providers/Resgrid.Providers.Bus.Rabbit/Resgrid.Providers.Bus.Rabbit.csproj", "Providers/Resgrid.Providers.Bus.Rabbit/"] COPY ["Core/Resgrid.Framework/Resgrid.Framework.csproj", "Core/Resgrid.Framework/"] diff --git a/Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj b/Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj index 5a5713333..c7eebb47e 100644 --- a/Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj +++ b/Web/Resgrid.Web.Mcp/Resgrid.Web.Mcp.csproj @@ -26,7 +26,6 @@ - @@ -37,7 +36,6 @@ - diff --git a/Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs b/Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs index 4ebcb9aef..d253e2f9d 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs @@ -545,6 +545,42 @@ public async Task> GetCallExtraData(int callId /// /// The cancellation token that can be used by other objects or threads to receive notice of cancellation. /// + /// + /// Gets the department's new-call field policy: which built-in fields the call form should show + /// and which it must require before the call can be created. + /// + /// + /// An empty rule list means the stock form -- every field visible, nothing extra required. + /// Clients apply this for usability; the same policy is enforced on SaveCall regardless. + /// + [HttpGet("GetNewCallFieldPolicy")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = ResgridResources.Call_View)] + public async Task> GetNewCallFieldPolicy() + { + var result = new NewCallFieldPolicyResult(); + var policy = await _departmentSettingsService.GetNewCallFieldPolicyAsync(DepartmentId); + + if (policy?.Rules != null) + { + foreach (var rule in policy.Rules) + { + result.Data.Rules.Add(new NewCallFieldRuleData + { + Key = rule.Key, + Visible = rule.Visible, + Required = rule.Visible && rule.Required + }); + } + } + + result.PageSize = result.Data.Rules.Count; + result.Status = ResponseHelper.Success; + ResponseHelper.PopulateV4ResponseData(result); + + return Ok(result); + } + [HttpPost("SaveCall")] [Consumes(MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -580,6 +616,30 @@ public async Task> SaveCall([FromBody] NewCallInput if (newCallInput.DestinationPoiId.HasValue && newCallInput.DestinationPoiId.Value > 0 && destinationPoi == null) return BadRequest(); + // The department's new-call field policy is enforced here, not only in the clients: an old + // build, an offline-queued call or a third-party integration must not be able to put an + // incomplete call in front of the crews. Departments with no policy configured are unaffected. + var fieldPolicy = await _departmentSettingsService.GetNewCallFieldPolicyAsync(effectiveDepartmentId); + var fieldViolations = NewCallFieldPolicyValidator.Validate(fieldPolicy, new NewCallFieldValues + { + Note = newCallInput.Note, + Address = newCallInput.Address, + Geolocation = newCallInput.Geolocation, + What3Words = newCallInput.What3Words, + ContactName = newCallInput.ContactName, + ContactInfo = newCallInput.ContactInfo, + ExternalId = newCallInput.ExternalId, + IncidentId = newCallInput.IncidentId, + ReferenceId = newCallInput.ReferenceId, + DestinationPoiId = newCallInput.DestinationPoiId, + IndoorMapZoneId = newCallInput.IndoorMapZoneId, + DispatchOn = newCallInput.DispatchOn, + HasDispatchList = !string.IsNullOrWhiteSpace(newCallInput.DispatchList) + }); + + if (fieldViolations.Count > 0) + return BadRequest(NewCallFieldPolicyValidator.DescribeViolations(fieldViolations)); + var call = new Call { DepartmentId = effectiveDepartmentId, @@ -1889,6 +1949,11 @@ public static CallResultData ConvertCall(Call call, List proto callResult.Priority = call.Priority; callResult.Name = StringHelpers.SanitizeHtmlInString(call.Name); + // Calls created before run cards existed have AlarmLevel 0; normalise to 1 so clients can + // treat the value as a level number rather than "0 means unknown". + callResult.AlarmLevel = Math.Max(1, call.AlarmLevel); + callResult.ActiveRunCardId = call.ActiveRunCardId; + if (!String.IsNullOrWhiteSpace(call.NatureOfCall)) callResult.Nature = StringHelpers.SanitizeHtmlInString(call.NatureOfCall); diff --git a/Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs b/Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs index 13014b70b..f5169a196 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; @@ -53,6 +53,7 @@ public class ChatController : V4AuthenticatedApiControllerbase private readonly IEventAggregator _eventAggregator; private readonly IQueueService _queueService; private readonly IUserProfileService _userProfileService; + private readonly ICallsService _callsService; public ChatController( IChatChannelService chatChannelService, @@ -68,9 +69,11 @@ public ChatController( ICacheProvider cacheProvider, IEventAggregator eventAggregator, IQueueService queueService, - IUserProfileService userProfileService) + IUserProfileService userProfileService, + ICallsService callsService) { _chatChannelService = chatChannelService; + _callsService = callsService; _chatPermissionService = chatPermissionService; _chatMessageService = chatMessageService; _chatModerationService = chatModerationService; @@ -111,6 +114,15 @@ public async Task> GetChannels(int? activeUn activeUnitId = null; var result = new GetChatChannelsResult(); + + // A call's incident channel is provisioned off the CallAddedEvent. If that event was dropped + // -- broker restart, worker down, or the call predates the chat rollout -- the call ends up + // with no conversation and the desk concludes the feature does not exist. Asking for a + // specific call's channels is the moment to heal that: Ensure is idempotent and renames in + // place, so this is a no-op whenever provisioning already worked. + if (callId.HasValue) + await EnsureIncidentChannelForCallAsync(callId.Value); + var channels = await _chatChannelService.GetChannelsForUserAsync(DepartmentId, UserId, activeUnitId, includeArchived); if (callId.HasValue && channels != null) @@ -1607,6 +1619,29 @@ private Task ChatEnabledAsync() return _featureToggleService.IsEnabledAsync(FeatureFlagKeys.ChatSystem, DepartmentId); } + /// + /// Creates the call's incident channel if provisioning never ran for it. Only active, non-deleted + /// calls in the caller's own department qualify — the same stance the provisioning listener takes + /// — so this cannot conjure a channel for a closed call or one belonging to another department. + /// Best-effort: a failure here must not blank the channel list. + /// + private async Task EnsureIncidentChannelForCallAsync(int callId) + { + try + { + var call = await _callsService.GetCallByIdAsync(callId, false); + + if (call == null || call.DepartmentId != DepartmentId || call.IsDeleted || call.State != (int)CallStates.Active) + return; + + await _chatChannelService.EnsureIncidentChannelAsync(call.DepartmentId, call.CallId, call.GetDisplayName()); + } + catch (Exception ex) + { + Logging.LogException(ex); + } + } + private async Task IsRateLimitedAsync(string action, int limitPerWindow) { if (limitPerWindow <= 0 || ChatConfig.RateLimitWindowSeconds <= 0) diff --git a/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs b/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs index 1a32d8209..774c634b1 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Resgrid.Model.Services; @@ -24,13 +24,15 @@ public class ConfigController : ControllerBase private readonly IDepartmentSettingsService _departmentSettingsService; private readonly IUserProfileService _userProfileService; private readonly IFeatureToggleService _featureToggleService; + private readonly IDepartmentsService _departmentsService; public ConfigController(IDepartmentSettingsService departmentSettingsService, IUserProfileService userProfileService, - IFeatureToggleService featureToggleService) + IFeatureToggleService featureToggleService, IDepartmentsService departmentsService) { _departmentSettingsService = departmentSettingsService; _userProfileService = userProfileService; _featureToggleService = featureToggleService; + _departmentsService = departmentsService; } #endregion Members and Constructors @@ -79,6 +81,16 @@ private async Task BuildConfigResultAsync(string key, int depar if (key == InfoConfig.DispatchAppKey) { result.Data.OpenWeatherApiKey = MappingConfig.DispatchOpenWeatherApiKey; + + // The Dispatch app geocodes addresses, what3words and plus codes on the new-call and + // edit-call screens exactly like the Responder app does; without these it fell back to + // "Google Maps API key not configured" on every lookup. + result.Data.GoogleMapsKey = !string.IsNullOrWhiteSpace(MappingConfig.DispatchAppGoogleMapsKey) + ? MappingConfig.DispatchAppGoogleMapsKey + : MappingConfig.GoogleMapsApiKey; + result.Data.W3WKey = !string.IsNullOrWhiteSpace(MappingConfig.DispatchAppWhat3WordsKey) + ? MappingConfig.DispatchAppWhat3WordsKey + : MappingConfig.What3WordsApiKey; } else if (key == InfoConfig.ResponderAppKey) { @@ -102,6 +114,13 @@ private async Task BuildConfigResultAsync(string key, int depar result.Data.MapAccessToken = mapConfig.AccessToken; result.Data.MapAttribution = mapConfig.Attribution; result.Data.IsDepartmentMapOverride = mapConfig.IsDepartmentOverride; + + // Every client map -- new-call pickers, live maps, board maps -- opens here. Without it each + // app fell back to its own hardcoded coordinates, which is how departments ended up staring + // at the wrong continent. + await PopulateMapCenterAsync(result, departmentId); + await PopulateUnitStatusThresholdsAsync(result, departmentId); + result.Data.EventingUrl = SystemBehaviorConfig.ResgridEventingBaseUrl; result.Data.PersonnelLocationStaleSeconds = MappingConfig.PersonnelLocationStaleSeconds; @@ -172,6 +191,71 @@ private async Task BuildConfigResultAsync(string key, int depar return result; } + /// + /// Loads the department's time-in-status thresholds for the board. Failure-safe: with no + /// thresholds the board simply highlights nothing, which is the pre-feature behaviour. + /// + private async Task PopulateUnitStatusThresholdsAsync(GetConfigResult result, int departmentId) + { + if (departmentId <= 0) + return; + + try + { + var thresholds = await _departmentSettingsService.GetUnitStatusThresholdsAsync(departmentId); + + foreach (var threshold in thresholds?.Thresholds ?? new System.Collections.Generic.List()) + { + result.Data.UnitStatusThresholds.Add(new UnitStatusThresholdData + { + BaseType = threshold.BaseType, + WarnSeconds = threshold.WarnSeconds, + AlertSeconds = threshold.AlertSeconds + }); + } + } + catch (System.Exception ex) + { + Resgrid.Framework.Logging.LogException(ex, + $"{nameof(PopulateUnitStatusThresholdsAsync)}: threshold lookup failed for departmentId {departmentId}."); + } + } + + /// + /// Resolves the department's default map center. Always leaves the result populated: the + /// settings service falls back from configured coordinates to the department address to a + /// system default, and a lookup failure here must not break config bootstrap for the apps. + /// + private async Task PopulateMapCenterAsync(GetConfigResult result, int departmentId) + { + result.Data.MapCenterZoomLevel = 9; + + if (departmentId <= 0) + return; + + try + { + var department = await _departmentsService.GetDepartmentByIdAsync(departmentId, false); + var coordinates = await _departmentSettingsService.GetMapCenterCoordinatesAsync(department); + + if (coordinates?.Latitude != null && coordinates.Longitude != null) + { + result.Data.MapCenterLatitude = coordinates.Latitude.Value; + result.Data.MapCenterLongitude = coordinates.Longitude.Value; + } + + var zoomLevel = await _departmentSettingsService.GetBigBoardMapZoomLevelForDepartmentAsync(departmentId); + + if (zoomLevel.HasValue && zoomLevel.Value > 0) + result.Data.MapCenterZoomLevel = zoomLevel.Value; + } + catch (System.Exception ex) + { + Resgrid.Framework.Logging.LogException(ex, + $"{nameof(PopulateMapCenterAsync)}: map center lookup failed for departmentId {departmentId}."); + } + } + private static int GetCurrentDepartmentId() { var principal = ClaimsAuthorizationHelper.GetClaimsPrincipal(); diff --git a/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs b/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs index fd82207e7..56a0f1e7c 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs @@ -47,6 +47,7 @@ public async Task> ForwardGeocode([FromQuery] try { var coordinates = await _geoLocationProvider.GetLatLonFromAddress(address); + result.Data.LookupSucceeded = true; if (!string.IsNullOrEmpty(coordinates)) { @@ -57,6 +58,7 @@ public async Task> ForwardGeocode([FromQuery] { result.Data.Latitude = lat; result.Data.Longitude = lng; + result.Data.Address = address; } } } @@ -69,6 +71,65 @@ public async Task> ForwardGeocode([FromQuery] return Ok(result); } + /// + /// Converts a what3words address ("filled.count.soap") into geographic coordinates. + /// Proxied server-side: the what3words API rejects browser origins, so the Dispatch and + /// BigBoard web builds cannot call it directly. + /// + /// what3words address, with or without the leading "///". + [HttpGet("What3WordsLookup")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Authorize(Policy = ResgridResources.Call_View)] + public async Task> What3WordsLookup([FromQuery] string words) + { + if (string.IsNullOrWhiteSpace(words)) + return BadRequest(); + + var result = new ForwardGeocodeResult(); + var normalizedWords = words.Trim().TrimStart('/'); + + try + { + var coordinates = await _geoLocationProvider.GetCoordinatesFromW3WAsync(normalizedWords); + result.Data.LookupSucceeded = true; + + if (coordinates != null && coordinates.Latitude.HasValue && coordinates.Longitude.HasValue) + { + result.Data.Latitude = coordinates.Latitude.Value; + result.Data.Longitude = coordinates.Longitude.Value; + result.Data.Address = $"///{normalizedWords}"; + } + } + catch { /* provider errors are non-fatal */ } + + result.PageSize = 1; + result.Status = ResponseHelper.Success; + ResponseHelper.PopulateV4ResponseData(result); + + return Ok(result); + } + + /// + /// Converts an Open Location Code (plus code) into geographic coordinates. Full codes resolve on + /// their own; a short code ("8FW4+Q3 Zottegem") needs the locality it was shortened against, so + /// the caller passes the whole string through. + /// + /// Plus code, optionally followed by a locality. + [HttpGet("PlusCodeLookup")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Authorize(Policy = ResgridResources.Call_View)] + public async Task> PlusCodeLookup([FromQuery] string code) + { + if (string.IsNullOrWhiteSpace(code)) + return BadRequest(); + + // Plus codes geocode as an ordinary address query; this exists as its own endpoint so the + // client can report "that is not a valid plus code" rather than "address not found". + return await ForwardGeocode(code.Trim()); + } + /// /// Converts geographic coordinates into a human-readable address string. /// @@ -86,6 +147,7 @@ public async Task> ReverseGeocode([FromQuery] { var address = await _geoLocationProvider.GetAddressFromLatLong(lat, lon); result.Data.Address = address ?? string.Empty; + result.Data.LookupSucceeded = true; } catch { /* provider errors are non-fatal */ } diff --git a/Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs b/Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs index eac6d17b3..d1169ed64 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs @@ -469,7 +469,7 @@ public async Task> GetMapDataAndMarkers() PoiTypeId = poiType.PoiTypeId, Name = poiType.Name, Color = poiType.Color, - ImagePath = null, + ImagePath = PoiIconHelper.ResolveIconName(poiType.Image), PoiImage = poiType.Image, Marker = poiType.Marker, IsDestination = poiType.IsDestination @@ -631,7 +631,7 @@ public static PoiTypeResultData ConvertPoiTypeData(PoiType poiType) PoiTypeId = poiType.PoiTypeId, Name = poiType.Name, Color = poiType.Color, - ImagePath = null, + ImagePath = PoiIconHelper.ResolveIconName(poiType.Image), PoiImage = poiType.Image, Marker = poiType.Marker, IsDestination = poiType.IsDestination @@ -651,7 +651,7 @@ public static PoiResultData ConvertPoiData(Poi poi, PoiType poiType) Latitude = poi.Latitude, Longitude = poi.Longitude, Color = poiType.Color, - ImagePath = null, + ImagePath = PoiIconHelper.ResolveIconName(poiType.Image), PoiImage = poiType.Image, Marker = poiType.Marker, IsDestination = poiType.IsDestination @@ -667,7 +667,7 @@ private static MapMakerInfoData ConvertPoiMapMarker(Poi poi, PoiType poiType) Latitude = poi.Latitude, Title = GetPoiTitle(poi, poiType), InfoWindowContent = GetPoiInfoWindowContent(poi, poiType), - ImagePath = null, + ImagePath = PoiIconHelper.ResolveIconName(poiType.Image), PoiImage = poiType.Image, Marker = poiType.Marker, Color = poiType.Color, diff --git a/Web/Resgrid.Web.Services/Controllers/v4/StatusesController.cs b/Web/Resgrid.Web.Services/Controllers/v4/StatusesController.cs index 5d5adbb07..3666c59c7 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/StatusesController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/StatusesController.cs @@ -177,11 +177,15 @@ public static StatusResultData ConvertCustomStatusData(int type, CustomStateDeta customStateResult.Type = type; customStateResult.StateId = stateDetail.CustomStateId; customStateResult.Text = stateDetail.ButtonText; - customStateResult.BColor = stateDetail.ButtonColor; + + // Resolve legacy bootstrap label classes ("label-success") to hex so clients never have to + // know about them; hex values pass through unchanged. + customStateResult.BColor = stateDetail.ButtonClassToColor(); customStateResult.Color = stateDetail.TextColor; customStateResult.Gps = stateDetail.GpsRequired; customStateResult.Note = stateDetail.NoteType; customStateResult.Detail = stateDetail.DetailType; + customStateResult.BaseType = stateDetail.BaseType; return customStateResult; } diff --git a/Web/Resgrid.Web.Services/Controllers/v4/UnitsController.cs b/Web/Resgrid.Web.Services/Controllers/v4/UnitsController.cs index b04ac6302..93bd8ea0c 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/UnitsController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/UnitsController.cs @@ -358,6 +358,7 @@ public static UnitsInfoResultData ConvertUnitsInfoResultData(Model.Unit unit, Un data.CurrentStatusId = state.State.ToString(); data.CurrentStatusTimestamp = state.Timestamp.TimeConverter(new Department() { TimeZone = timeZone }); + data.CurrentStatusTimestampUtc = state.Timestamp; data.Note = state.Note; @@ -421,7 +422,12 @@ public static UnitsInfoResultData ConvertUnitsInfoResultData(Model.Unit unit, Un if (customState != null) { data.CurrentStatus = customState.ButtonText; - data.CurrentStatusColor = customState.ButtonColor; + + // ButtonColor is either a hex value or a legacy bootstrap label class; clients cannot + // render the latter and were prefixing the former with a second '#'. Resolve to a plain + // hex colour here so every client can use the value verbatim. + data.CurrentStatusColor = customState.ButtonClassToColor(); + data.CurrentStatusBaseType = customState.BaseType; } return data; diff --git a/Web/Resgrid.Web.Services/Dockerfile b/Web/Resgrid.Web.Services/Dockerfile index e41e6f00c..8f5a0d8ca 100644 --- a/Web/Resgrid.Web.Services/Dockerfile +++ b/Web/Resgrid.Web.Services/Dockerfile @@ -13,6 +13,10 @@ EXPOSE 80 FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab513c6ca4476ddb2f9f11adb9c04aa7c89 AS build ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj", "Web/Resgrid.Web.Services/"] COPY ["Providers/Resgrid.Providers.Bus.Rabbit/Resgrid.Providers.Bus.Rabbit.csproj", "Providers/Resgrid.Providers.Bus.Rabbit/"] COPY ["Core/Resgrid.Framework/Resgrid.Framework.csproj", "Core/Resgrid.Framework/"] diff --git a/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs b/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs new file mode 100644 index 000000000..1b0d89fba --- /dev/null +++ b/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs @@ -0,0 +1,40 @@ +using System; +using Newtonsoft.Json.Converters; + +namespace Resgrid.Web.Services.Helpers +{ + /// + /// Serialises a that is known to hold a UTC instant with an explicit "Z". + /// + /// Values read back from the repositories carry , and + /// Newtonsoft's default round-trip handling writes those with no zone marker at all + /// ("2026-08-12T13:05:22"). Every JavaScript client then parses that as *local* time, so a unit + /// whose status changed a moment ago reads as hours old in any department that is not on UTC. + /// + /// Apply this to response properties whose value is a UTC instant. Do not apply it to the + /// department-local companions (e.g. CurrentStatusTimestamp) -- those really are local wall time + /// and marking them UTC would shift them the other way. + /// + public class UtcDateTimeConverter : IsoDateTimeConverter + { + public UtcDateTimeConverter() + { + DateTimeStyles = System.Globalization.DateTimeStyles.AdjustToUniversal; + DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"; + } + + public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + if (value is DateTime dateTime && dateTime.Kind != DateTimeKind.Utc) + { + // Unspecified means "we already know this is UTC, the store just did not say so". + // Local means the process timezone leaked in; converting is the honest fix. + value = dateTime.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc) + : dateTime.ToUniversalTime(); + } + + base.WriteJson(writer, value, serializer); + } + } +} diff --git a/Web/Resgrid.Web.Services/Models/v4/Calendar/GetAllCalendarItemResult.cs b/Web/Resgrid.Web.Services/Models/v4/Calendar/GetAllCalendarItemResult.cs index a7cd0cdda..aa80fed4f 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Calendar/GetAllCalendarItemResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Calendar/GetAllCalendarItemResult.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.Calendar; /// @@ -37,6 +40,7 @@ public class GetAllCalendarItemResultData /// /// Start Time in UTC /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime StartUtc { get; set; } /// @@ -47,6 +51,7 @@ public class GetAllCalendarItemResultData /// /// End Time in UTC /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime EndUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/CallNotes/CallNotesResult.cs b/Web/Resgrid.Web.Services/Models/v4/CallNotes/CallNotesResult.cs index da566482b..0aea7c93d 100644 --- a/Web/Resgrid.Web.Services/Models/v4/CallNotes/CallNotesResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/CallNotes/CallNotesResult.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.CallNotes { /// @@ -60,6 +63,7 @@ public class CallNoteResultData /// /// Timestamp of when the note as added in Utc /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime TimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/CallVideoFeeds/CallVideoFeedsResult.cs b/Web/Resgrid.Web.Services/Models/v4/CallVideoFeeds/CallVideoFeedsResult.cs index 9b0d33469..e060dab0b 100644 --- a/Web/Resgrid.Web.Services/Models/v4/CallVideoFeeds/CallVideoFeedsResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/CallVideoFeeds/CallVideoFeedsResult.cs @@ -1,6 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.CallVideoFeeds { public class CallVideoFeedsResult : StandardApiResponseV4Base @@ -27,6 +30,7 @@ public class CallVideoFeedResultData public decimal? Longitude { get; set; } public string AddedByUserId { get; set; } public string AddedOnFormatted { get; set; } + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime AddedOnUtc { get; set; } public int SortOrder { get; set; } public string FullName { get; set; } diff --git a/Web/Resgrid.Web.Services/Models/v4/Calls/CallHistoryResult.cs b/Web/Resgrid.Web.Services/Models/v4/Calls/CallHistoryResult.cs index 820122113..8a1d656d2 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Calls/CallHistoryResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Calls/CallHistoryResult.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.Calls { /// @@ -35,6 +38,7 @@ public class CallHistoryResultData /// /// Timestamp of the history event in UTC/GMT /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime TimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/Calls/CallResult.cs b/Web/Resgrid.Web.Services/Models/v4/Calls/CallResult.cs index 962827f9a..e9f5e53c2 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Calls/CallResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Calls/CallResult.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.Calls { /// @@ -170,9 +173,23 @@ public class CallResultData /// public string Type { get; set; } + /// + /// Current alarm level (1-based). Only moves above 1 when the call has been escalated through + /// a run card ("Strike Next Alarm"); clients show it so a dispatcher can see the call is + /// already running at a higher level before striking again. + /// + public int AlarmLevel { get; set; } + + /// + /// The run card driving this call's dispatch, or null when no card matched. Clients use it to + /// decide whether escalation is available at all. + /// + public int? ActiveRunCardId { get; set; } + /// /// When was the call Logged On in UTC time /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime LoggedOnUtc { get; set; } /// @@ -183,6 +200,7 @@ public class CallResultData /// /// Dispatch On /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime? DispatchedOnUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/Calls/NewCallFieldPolicyResult.cs b/Web/Resgrid.Web.Services/Models/v4/Calls/NewCallFieldPolicyResult.cs new file mode 100644 index 000000000..c95ca6066 --- /dev/null +++ b/Web/Resgrid.Web.Services/Models/v4/Calls/NewCallFieldPolicyResult.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Resgrid.Web.Services.Models.v4.Calls +{ + /// + /// The department's new-call form policy: which built-in fields the call form shows, and which it + /// requires before a call can be created. + /// + public class NewCallFieldPolicyResult : StandardApiResponseV4Base + { + /// + /// Response Data + /// + public NewCallFieldPolicyResultData Data { get; set; } = new NewCallFieldPolicyResultData(); + } + + public class NewCallFieldPolicyResultData + { + /// + /// Rules for the fields the department has configured. A field with no rule here is visible and + /// optional, which is how Resgrid behaved before this setting existed -- so an empty list means + /// "stock form" and clients should not hide or require anything. + /// + public List Rules { get; set; } = new List(); + } + + public class NewCallFieldRuleData + { + /// + /// Stable field key. See Resgrid.Model.NewCallFieldKeys for the full set. + /// + public string Key { get; set; } + + /// False hides the field from the call form entirely. + public bool Visible { get; set; } + + /// True blocks call creation until the field has a value. + public bool Required { get; set; } + } +} diff --git a/Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs b/Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs index 3c5cd23d3..2cb49919b 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs @@ -86,6 +86,29 @@ public class GetConfigResultData /// public string MapAttribution { get; set; } + /// + /// Latitude every map in every client should open on for this department. Resolved from the + /// department's configured map center, falling back to its address and finally to a system + /// default, so this is always populated. + /// + public double MapCenterLatitude { get; set; } + + /// + /// Longitude every map in every client should open on for this department. + /// + public double MapCenterLongitude { get; set; } + + /// + /// Zoom level to open department-wide maps at. Defaults to 9 when the department has not set one. + /// + public int MapCenterZoomLevel { get; set; } + + /// + /// How long a unit may sit in a status before the board flags it, keyed by the status's canonical + /// base type. Empty means the department has configured no thresholds and nothing is highlighted. + /// + public List UnitStatusThresholds { get; set; } = new List(); + /// /// How many seconds to prevent a duplicate gps location from being logged for personnel /// @@ -162,4 +185,17 @@ public class GetConfigResultData /// public bool EnableModernApplicationSounds { get; set; } } + /// One time-in-status threshold for the board's unit highlighting. + public class UnitStatusThresholdData + { + /// The ActionBaseTypes value this applies to. + public int BaseType { get; set; } + + /// Seconds after which the unit is highlighted. 0 disables the warning. + public int WarnSeconds { get; set; } + + /// Seconds after which the unit is escalated to a high-priority alert. 0 disables it. + public int AlertSeconds { get; set; } + } + } diff --git a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactCategoryResult.cs b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactCategoryResult.cs index 221140a03..5469ed924 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactCategoryResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactCategoryResult.cs @@ -1,5 +1,8 @@ using System; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.CallTypes { /// @@ -41,6 +44,7 @@ public class ContactCategoryResultData /// /// Date and time when this category was created in UTC /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime AddedOnUtc { get; set; } /// @@ -61,6 +65,7 @@ public class ContactCategoryResultData /// /// Date and time when this category was last modified, null if never edited in UTC /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime? EditedOnUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactNotesResult.cs b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactNotesResult.cs index 4c4adc5c0..3af2d8d0d 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactNotesResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactNotesResult.cs @@ -1,7 +1,9 @@ using System; using Resgrid.Web.Services.Models.v4.CallTypes; using System.Collections.Generic; -using Humanizer; + +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; namespace Resgrid.Web.Services.Models.v4.Contacts { @@ -43,12 +45,14 @@ public class ContactNoteResultData public int Visibility { get; set; } // 0 Internal, 1 Visible to Client + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime? ExpiresOnUtc { get; set; } public string ExpiresOn { get; set; } public bool IsDeleted { get; set; } + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime AddedOnUtc { get; set; } public string AddedOn { get; set; } @@ -57,6 +61,7 @@ public class ContactNoteResultData public string AddedByName { get; set; } + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime? EditedOnUtc { get; set; } public string EditedOn { get; set; } diff --git a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactResult.cs b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactResult.cs index 24dd6eb72..ce733b672 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Contacts/ContactResult.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.CallTypes { /// @@ -95,6 +98,7 @@ public class ContactResultData public bool IsDeleted { get; set; } + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime AddedOnUtc { get; set; } public string AddedOn { get; set; } @@ -103,6 +107,7 @@ public class ContactResultData public string AddedByUserName { get; set; } + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime? EditedOnUtc { get; set; } public string EditedOn { get; set; } diff --git a/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs b/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs index 54f8db09e..f6805c099 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs @@ -21,6 +21,18 @@ public class ForwardGeocodeData /// Longitude of the geocoded location, or null if not found. public double? Longitude { get; set; } + + /// + /// Normalised address for the resolved point when the provider supplies one. Clients that show + /// the resolved address fall back to the text the user typed when this is empty. + /// + public string Address { get; set; } + + /// + /// True when the lookup ran successfully and simply matched nothing, false when the lookup + /// itself failed. Clients need the difference to say "address not found" instead of "try again". + /// + public bool LookupSucceeded { get; set; } } /// @@ -41,5 +53,11 @@ public class ReverseGeocodeData { /// Human-readable address for the supplied coordinates, or empty if not found. public string Address { get; set; } + + /// + /// True when the lookup ran successfully and simply matched nothing, false when the lookup + /// itself failed. Clients need the difference to say "address not found" instead of "try again". + /// + public bool LookupSucceeded { get; set; } } } diff --git a/Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs b/Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs index 31bdbae2d..82b4ffd86 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.Messages; /// @@ -60,6 +63,7 @@ public class MessageResultData /// /// When the message was sent on (UTC Time) /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime SentOnUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/PersonnelStaffing/GetCurrentStaffingResult.cs b/Web/Resgrid.Web.Services/Models/v4/PersonnelStaffing/GetCurrentStaffingResult.cs index 6f0219aad..940a33fbd 100644 --- a/Web/Resgrid.Web.Services/Models/v4/PersonnelStaffing/GetCurrentStaffingResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/PersonnelStaffing/GetCurrentStaffingResult.cs @@ -1,5 +1,8 @@ using System; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.PersonnelStaffing { /// @@ -36,6 +39,7 @@ public class GetCurrentStaffingResultData /// /// The timestamp of the last staffing. This is converted UTC version of the timestamp. /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime TimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/PersonnelStatuses/GetCurrentStatusResult.cs b/Web/Resgrid.Web.Services/Models/v4/PersonnelStatuses/GetCurrentStatusResult.cs index 5098e1a3d..c039bdbca 100644 --- a/Web/Resgrid.Web.Services/Models/v4/PersonnelStatuses/GetCurrentStatusResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/PersonnelStatuses/GetCurrentStatusResult.cs @@ -1,5 +1,8 @@ using System; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.PersonnelStatuses { /// @@ -36,6 +39,7 @@ public class GetCurrentStatusResultData /// /// The timestamp of the last action. This is converted UTC version of the timestamp. /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime TimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/Statuses/StatusResult.cs b/Web/Resgrid.Web.Services/Models/v4/Statuses/StatusResult.cs index 481f77bd8..29f931f22 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Statuses/StatusResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Statuses/StatusResult.cs @@ -39,10 +39,17 @@ public class StatusResultData public string Text { get; set; } /// - /// Button color + /// Button color, always a CSS hex colour ("#5CB85C") /// public string BColor { get; set; } + /// + /// The canonical meaning of this status (see ). + /// Departments name and colour their statuses freely, so this is the only field a client can use + /// to answer "does this status mean available / responding / on scene". + /// + public int BaseType { get; set; } + /// /// Text color /// diff --git a/Web/Resgrid.Web.Services/Models/v4/UnitStatus/UnitStatusResult.cs b/Web/Resgrid.Web.Services/Models/v4/UnitStatus/UnitStatusResult.cs index 63831d230..79acea3c5 100644 --- a/Web/Resgrid.Web.Services/Models/v4/UnitStatus/UnitStatusResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/UnitStatus/UnitStatusResult.cs @@ -1,5 +1,8 @@ using System; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.UnitStatus { /// @@ -56,6 +59,7 @@ public class UnitStatusResultData /// /// Timestamp in Utc of this Unit State /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime TimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Models/v4/Units/UnitsInfoResult.cs b/Web/Resgrid.Web.Services/Models/v4/Units/UnitsInfoResult.cs index dd007aa6d..f51acd87e 100644 --- a/Web/Resgrid.Web.Services/Models/v4/Units/UnitsInfoResult.cs +++ b/Web/Resgrid.Web.Services/Models/v4/Units/UnitsInfoResult.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Resgrid.Web.Services.Helpers; + namespace Resgrid.Web.Services.Models.v4.Units { /// @@ -108,18 +111,28 @@ public class UnitsInfoResultData public string CurrentStatus { get; set; } /// - /// The current status/state of the Unit color + /// The current status/state of the Unit color, always a CSS hex colour ("#5CB85C"). /// public string CurrentStatusColor { get; set; } /// - /// The Timestamp of the status + /// The base type the unit's current custom status maps to (see ), + /// or null when the status has no base type. Clients need this to answer "is this unit + /// available/responding/on scene" -- the status id itself is department-specific and the unit's + /// TypeId is the unit type, not a status. + /// + public int? CurrentStatusBaseType { get; set; } + + /// + /// The Timestamp of the status, in the department's timezone. /// public DateTime CurrentStatusTimestamp { get; set; } /// - /// The Timestamp of the status in UTC/GMT + /// The Timestamp of the status in UTC/GMT. Serialised with an explicit "Z" -- without it clients + /// parsed the value as local time and reported unit statuses hours stale. /// + [JsonConverter(typeof(UtcDateTimeConverter))] public DateTime CurrentStatusTimestampUtc { get; set; } /// diff --git a/Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj b/Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj index a3b669394..3970e7fee 100644 --- a/Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj +++ b/Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj @@ -68,7 +68,6 @@ - @@ -85,7 +84,7 @@ - + all @@ -101,7 +100,6 @@ - diff --git a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml index 2f9c3b253..cec55fe4c 100644 --- a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml +++ b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml @@ -293,13 +293,21 @@ CallId to get data for - + Saves a call in the Resgrid system The cancellation token that can be used by other objects or threads to receive notice of cancellation. + + Gets the department's new-call field policy: which built-in fields the call form should show + and which it must require before the call can be created. + + + An empty rule list means the stock form -- every field visible, nothing extra required. + Clients apply this for usability; the same policy is enforced on SaveCall regardless. + @@ -775,6 +783,14 @@ Reason and optional note for the flag ChatActionResult indicating whether the flag was recorded + + + Creates the call's incident channel if provisioning never ran for it. Only active, non-deleted + calls in the caller's own department qualify — the same stance the provisioning listener takes + — so this cannot conjure a channel for a closed call or one belonging to another department. + Best-effort: a failure here must not blank the channel list. + + True when the message lives in an assistant (chatbot) conversation, where reactions, @@ -1113,6 +1129,19 @@ The key to get config data for + + + Loads the department's time-in-status thresholds for the board. Failure-safe: with no + thresholds the board simply highlights nothing, which is the pre-feature behaviour. + + + + + Resolves the department's default map center. Always leaves the result populated: the + settings service falls back from configured coordinates to the department address to a + system default, and a lookup failure here must not break config bootstrap for the apps. + + Service to generate an authentication token that is required to communicate with all other v4 services @@ -1423,6 +1452,22 @@ Address string to geocode. ForwardGeocodeResult with Latitude/Longitude, or nulls if not found. + + + Converts a what3words address ("filled.count.soap") into geographic coordinates. + Proxied server-side: the what3words API rejects browser origins, so the Dispatch and + BigBoard web builds cannot call it directly. + + what3words address, with or without the leading "///". + + + + Converts an Open Location Code (plus code) into geographic coordinates. Full codes resolve on + their own; a short code ("8FW4+Q3 Zottegem") needs the locality it was shortened against, so + the caller passes the whole string through. + + Plus code, optionally followed by a locality. + Converts geographic coordinates into a human-readable address string. @@ -5323,6 +5368,20 @@ part of the shallow /health liveness endpoint. + + + Serialises a that is known to hold a UTC instant with an explicit "Z". + + Values read back from the repositories carry , and + Newtonsoft's default round-trip handling writes those with no zone marker at all + ("2026-08-12T13:05:22"). Every JavaScript client then parses that as *local* time, so a unit + whose status changed a moment ago reads as hours old in any department that is not on UTC. + + Apply this to response properties whose value is a UTC instant. Do not apply it to the + department-local companions (e.g. CurrentStatusTimestamp) -- those really are local wall time + and marking them UTC would shift them the other way. + + Gets or sets the on authentication failed. @@ -6446,6 +6505,19 @@ Call Type + + + Current alarm level (1-based). Only moves above 1 when the call has been escalated through + a run card ("Strike Next Alarm"); clients show it so a dispatcher can see the call is + already running at a higher level before striking again. + + + + + The run card driving this call's dispatch, or null when no card matched. Clients use it to + decide whether escalation is available at all. + + When was the call Logged On in UTC time @@ -6656,6 +6728,35 @@ Response Data + + + The department's new-call form policy: which built-in fields the call form shows, and which it + requires before a call can be created. + + + + + Response Data + + + + + Rules for the fields the department has configured. A field with no rule here is visible and + optional, which is how Resgrid behaved before this setting existed -- so an empty list means + "stock form" and clients should not hide or require anything. + + + + + Stable field key. See Resgrid.Model.NewCallFieldKeys for the full set. + + + + False hides the field from the call form entirely. + + + True blocks call creation until the field has a value. + Data needed to create a new call @@ -8865,6 +8966,29 @@ The attribution for the rendered map + + + Latitude every map in every client should open on for this department. Resolved from the + department's configured map center, falling back to its address and finally to a system + default, so this is always populated. + + + + + Longitude every map in every client should open on for this department. + + + + + Zoom level to open department-wide maps at. Defaults to 9 when the department has not set one. + + + + + How long a unit may sit in a status before the board flags it, keyed by the status's canonical + base type. Empty means the department has configured no thresholds and nothing is highlighted. + + How many seconds to prevent a duplicate gps location from being logged for personnel @@ -8941,6 +9065,18 @@ enabled when either the department-wide setting or the user's profile setting is enabled. + + One time-in-status threshold for the board's unit highlighting. + + + The ActionBaseTypes value this applies to. + + + Seconds after which the unit is highlighted. 0 disables the warning. + + + Seconds after which the unit is escalated to a high-priority alert. 0 disables it. + Gets Configuration Information for the Resgrid System @@ -9537,6 +9673,18 @@ Longitude of the geocoded location, or null if not found. + + + Normalised address for the resolved point when the provider supplies one. Clients that show + the resolved address fall back to the text the user typed when this is empty. + + + + + True when the lookup ran successfully and simply matched nothing, false when the lookup + itself failed. Clients need the difference to say "address not found" instead of "try again". + + Result of a reverse geocode (coordinates → address) request. @@ -9548,6 +9696,12 @@ Human-readable address for the supplied coordinates, or empty if not found. + + + True when the lookup ran successfully and simply matched nothing, false when the lookup + itself failed. Clients need the difference to say "address not found" instead of "try again". + + A group in the Resgrid system @@ -12055,7 +12209,14 @@ - Button color + Button color, always a CSS hex colour ("#5CB85C") + + + + + The canonical meaning of this status (see ). + Departments name and colour their statuses freely, so this is the only field a client can use + to answer "does this status mean available / responding / on scene". @@ -13006,17 +13167,26 @@ - The current status/state of the Unit color + The current status/state of the Unit color, always a CSS hex colour ("#5CB85C"). + + + + + The base type the unit's current custom status maps to (see ), + or null when the status has no base type. Clients need this to answer "is this unit + available/responding/on scene" -- the status id itself is department-specific and the unit's + TypeId is the unit type, not a status. - The Timestamp of the status + The Timestamp of the status, in the department's timezone. - The Timestamp of the status in UTC/GMT + The Timestamp of the status in UTC/GMT. Serialised with an explicit "Z" -- without it clients + parsed the value as local time and reported unit statuses hours stale. diff --git a/Web/Resgrid.Web.Tts/Dockerfile b/Web/Resgrid.Web.Tts/Dockerfile index d5622cec6..d9d7da18c 100644 --- a/Web/Resgrid.Web.Tts/Dockerfile +++ b/Web/Resgrid.Web.Tts/Dockerfile @@ -11,7 +11,10 @@ FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab51 ARG BUILD_VERSION WORKDIR /src +## Directory.Build.targets carries the transitive security pins; this stage +## publishes with --no-restore, so it must be present before dotnet restore. COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Web/Resgrid.Web.Tts/Resgrid.Web.Tts.csproj", "Web/Resgrid.Web.Tts/"] COPY ["Core/Resgrid.Config/Resgrid.Config.csproj", "Core/Resgrid.Config/"] RUN dotnet restore "Web/Resgrid.Web.Tts/Resgrid.Web.Tts.csproj" diff --git a/Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs b/Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs index 90389dda2..18d1ea2e5 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net; using System.Threading; @@ -243,6 +244,9 @@ public async Task Settings() else model.Use24HourTime = false; + model.NewCallFields = await BuildNewCallFieldRowsAsync(); + model.UnitStatusThresholds = await BuildUnitStatusThresholdRowsAsync(); + var address = await _departmentSettingsService.GetBigBoardCenterAddressDepartmentAsync(DepartmentId); var zoomLevel = await _departmentSettingsService.GetBigBoardMapZoomLevelForDepartmentAsync(DepartmentId); var refreshTimer = await _departmentSettingsService.GetBigBoardRefreshTimeForDepartmentAsync(DepartmentId); @@ -569,9 +573,39 @@ await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, model.Pe await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, model.UnitsSort.ToString(), DepartmentSettingTypes.UnitsSortOrder, cancellationToken); await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, model.CallsSort.ToString(), DepartmentSettingTypes.CallsSortOrder, cancellationToken); - if (!String.IsNullOrWhiteSpace(model.MapCenterGpsCoordinatesLatitude) && !String.IsNullOrWhiteSpace(model.MapCenterGpsCoordinatesLongitude)) - await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, StringHelpers.SanitizeCoordinatesString(model.MapCenterGpsCoordinatesLatitude) + "," + StringHelpers.SanitizeCoordinatesString(model.MapCenterGpsCoordinatesLongitude), - DepartmentSettingTypes.BigBoardMapCenterGpsCoordinates, cancellationToken); + await _departmentSettingsService.SaveUnitStatusThresholdsAsync(DepartmentId, new UnitStatusThresholds + { + Thresholds = (model.UnitStatusThresholds ?? new List()) + .Select(x => new UnitStatusThreshold + { + BaseType = x.BaseType, + // Entered in minutes, stored in seconds — the board works in seconds and a + // dispatcher should never have to think in them. + WarnSeconds = Math.Max(0, x.WarnMinutes) * 60, + AlertSeconds = Math.Max(0, x.AlertMinutes) * 60 + }) + .ToList() + }, cancellationToken); + + await _departmentSettingsService.SaveNewCallFieldPolicyAsync(DepartmentId, new NewCallFieldPolicy + { + Rules = (model.NewCallFields ?? new List()) + .Select(x => new NewCallFieldRule { Key = x.Key, Visible = x.Visible, Required = x.Required }) + .ToList() + }, cancellationToken); + + // Coordinates the operator typed are stored verbatim. Leaving both blank means "work it + // out from our address" -- the service geocodes it and stores the result, so a department + // that never touches these fields still gets every map centred on itself instead of on + // the system default. A hand-set pin is never overwritten by the geocoder. + var savedMapCenter = await _departmentSettingsService.SaveMapCenterCoordinatesAsync(DepartmentId, + model.MapCenterGpsCoordinatesLatitude, model.MapCenterGpsCoordinatesLongitude, departmentAddress, cancellationToken); + + if (savedMapCenter != null) + { + model.MapCenterGpsCoordinatesLatitude = savedMapCenter.Latitude.Value.ToString(CultureInfo.InvariantCulture); + model.MapCenterGpsCoordinatesLongitude = savedMapCenter.Longitude.Value.ToString(CultureInfo.InvariantCulture); + } model.SuppressStaffingInfo = new DepartmentSuppressStaffingInfo(); @@ -953,6 +987,71 @@ public async Task CallSettings() return View(model); } + /// + /// Builds the time-in-status threshold grid. Only the base types a dispatcher would plausibly + /// time are offered -- there is no value in warning that a unit has been "available" too long. + /// + private async Task> BuildUnitStatusThresholdRowsAsync() + { + var stored = await _departmentSettingsService.GetUnitStatusThresholdsAsync(DepartmentId); + var rows = new List(); + + foreach (var baseType in TimeableUnitBaseTypes) + { + var threshold = stored.Find((int)baseType); + + rows.Add(new UnitStatusThresholdRow + { + BaseType = (int)baseType, + WarnMinutes = threshold != null ? threshold.WarnSeconds / 60 : 0, + AlertMinutes = threshold != null ? threshold.AlertSeconds / 60 : 0 + }); + } + + return rows; + } + + /// + /// Statuses where "how long has it been like this?" is an operationally useful question. The + /// customer case that drove this: a unit dispatched for more than four minutes that has not + /// reported responding. + /// + private static readonly ActionBaseTypes[] TimeableUnitBaseTypes = new[] + { + ActionBaseTypes.Dispatched, + ActionBaseTypes.Responding, + ActionBaseTypes.Enroute, + ActionBaseTypes.OnScene, + ActionBaseTypes.Staging, + ActionBaseTypes.AtPatient, + ActionBaseTypes.Transporting, + ActionBaseTypes.AtHospital, + ActionBaseTypes.Returning + }; + + /// + /// Builds the new-call field policy grid for the settings screen: every configurable built-in + /// field, pre-ticked from the department's stored policy. Fields with no stored rule default to + /// visible and optional, matching how the form behaved before the setting existed. + /// + private async Task> BuildNewCallFieldRowsAsync() + { + var policy = await _departmentSettingsService.GetNewCallFieldPolicyAsync(DepartmentId); + var rows = new List(); + + foreach (var key in NewCallFieldKeys.All) + { + rows.Add(new NewCallFieldPolicyRow + { + Key = key, + Visible = policy.IsVisible(key), + Required = policy.IsRequired(key) + }); + } + + return rows; + } + [HttpPost] [Authorize(Policy = ResgridResources.Department_Update)] public async Task CallSettings(CallSettingsView model, CancellationToken cancellationToken) diff --git a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs index 1fc784c79..6d3fe1cf4 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; @@ -222,6 +222,40 @@ public async Task NewCall() [HttpPost] [ValidateAntiForgeryToken] + + /// + /// Adds a model error for every field the department's new-call policy requires but the form + /// left blank. Keyed to the form fields so the messages land next to the inputs. + /// + private async Task ApplyNewCallFieldPolicyAsync(NewCallView model, IFormCollection collection) + { + var policy = await _departmentSettingsService.GetNewCallFieldPolicyAsync(DepartmentId); + + if (policy == null || policy.IsEmpty) + return; + + var values = new NewCallFieldValues + { + Note = model.Call?.Notes, + Address = model.Call?.Address, + Geolocation = model.Call?.GeoLocationData, + What3Words = model.What3Word, + ContactName = model.Call?.ContactName, + ContactInfo = model.Call?.ContactNumber, + ExternalId = model.Call?.ExternalIdentifier, + IncidentId = model.Call?.IncidentNumber, + ReferenceId = model.Call?.ReferenceNumber, + DestinationPoiId = model.Call?.DestinationPoiId, + HasDispatchList = collection != null && collection.Keys.Any(x => x.StartsWith("dispatch", StringComparison.OrdinalIgnoreCase)) + }; + + foreach (var violation in NewCallFieldPolicyValidator.Validate(policy, values)) + { + ModelState.AddModelError($"NewCallField_{violation.Key}", + $"{violation.Key} is required by this department before a call can be created."); + } + } + [Authorize(Policy = ResgridResources.Call_Create)] public async Task NewCall(NewCallView model, IFormCollection collection, CancellationToken cancellationToken) { @@ -234,6 +268,11 @@ public async Task NewCall(NewCallView model, IFormCollection coll if (model.Call?.DestinationPoiId.HasValue == true && model.Call.DestinationPoiId.Value > 0 && destinationPoi == null) ModelState.AddModelError("Call.DestinationPoiId", _dispatchLocalizer["InvalidDestinationPoi"].Value); + // Same policy the apps apply and the v4 API enforces: a call-taker cannot forward an + // incident to the field until the information the crews need is on it. Departments with no + // policy configured are unaffected. + await ApplyNewCallFieldPolicyAsync(model, collection); + if (ModelState.IsValid) { model.Call.ReportingUserId = UserId; diff --git a/Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs index 9062807e4..86c1126b3 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs @@ -16,7 +16,6 @@ using Resgrid.Services; using System.Collections.Generic; using System.Linq; -using NuGet.Packaging; namespace Resgrid.Web.Areas.User.Controllers { diff --git a/Web/Resgrid.Web/Areas/User/Models/DepartmentSettingsModel.cs b/Web/Resgrid.Web/Areas/User/Models/DepartmentSettingsModel.cs index b58f8f9b5..9f1953902 100644 --- a/Web/Resgrid.Web/Areas/User/Models/DepartmentSettingsModel.cs +++ b/Web/Resgrid.Web/Areas/User/Models/DepartmentSettingsModel.cs @@ -19,6 +19,18 @@ public class DepartmentSettingsModel : BaseUserModel public string MapZoomLevel { get; set; } public string RefreshTime { get; set; } + /// + /// New-call field policy, one row per configurable built-in field. Bound as a list so the admin + /// screen can render a Visible/Required grid without a per-field property explosion. + /// + public List NewCallFields { get; set; } = new List(); + + /// + /// Time-in-status thresholds, one row per canonical status meaning. Entered in minutes because + /// that is how dispatchers talk about them ("four minutes without reporting departed"). + /// + public List UnitStatusThresholds { get; set; } = new List(); + [StringLength(500, ErrorMessage = "Street address cannot exceed 500 characters.")] public string MapCenterPointAddressAddress1 { get; set; } @@ -87,4 +99,28 @@ public void SetUsers(List users, List names) } } } + /// One configurable built-in new-call field on the department settings screen. + public class NewCallFieldPolicyRow + { + /// Stable key from Resgrid.Model.NewCallFieldKeys. + public string Key { get; set; } + + public bool Visible { get; set; } = true; + + public bool Required { get; set; } + } + /// One time-in-status threshold row on the department settings screen. + public class UnitStatusThresholdRow + { + /// The ActionBaseTypes value this row configures. + public int BaseType { get; set; } + + /// Minutes before the unit is highlighted. Blank or 0 disables the warning. + public int WarnMinutes { get; set; } + + /// Minutes before the unit is escalated to a high-priority alert. Blank or 0 disables it. + public int AlertMinutes { get; set; } + } + + } diff --git a/Web/Resgrid.Web/Areas/User/Views/Department/Settings.cshtml b/Web/Resgrid.Web/Areas/User/Views/Department/Settings.cshtml index 16ec74fea..ed1ceee6a 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Department/Settings.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Department/Settings.cshtml @@ -203,10 +203,79 @@ @localizer["LongitudePlaceholderHelp"] +
+ @localizer["DefaultMapCenterHelp"] +
+
+ +
+ +
+ @localizer["NewCallFieldsHelp"] + + + + + + + + + + @for (var i = 0; i < Model.NewCallFields.Count; i++) + { + + + + + + } + +
@localizer["NewCallFieldsFieldColumn"]@localizer["NewCallFieldsVisibleColumn"]@localizer["NewCallFieldsRequiredColumn"]
+ @localizer["NewCallField_" + Model.NewCallFields[i].Key] + +
+
+
+ +
+ +
+ +
+ @localizer["UnitStatusThresholdsHelp"] + + + + + + + + + + @for (var i = 0; i < Model.UnitStatusThresholds.Count; i++) + { + + + + + + } + +
@localizer["UnitStatusThresholdsStatusColumn"]@localizer["UnitStatusThresholdsWarnColumn"]@localizer["UnitStatusThresholdsAlertColumn"]
+ @localizer["UnitStatusBaseType_" + Model.UnitStatusThresholds[i].BaseType] + +
+
+
+
@commonLocalizer["Cancel"] diff --git a/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml b/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml index f8f399c46..b03ab13c1 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml @@ -103,6 +103,9 @@ +
diff --git a/Web/Resgrid.Web/Dockerfile b/Web/Resgrid.Web/Dockerfile index 0d516642a..48fe1b8d0 100644 --- a/Web/Resgrid.Web/Dockerfile +++ b/Web/Resgrid.Web/Dockerfile @@ -31,6 +31,10 @@ RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - RUN apt-get -y install nodejs ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Web/Resgrid.Web/Resgrid.Web.csproj", "Web/Resgrid.Web/"] COPY ["Providers/Resgrid.Providers.Bus.Rabbit/Resgrid.Providers.Bus.Rabbit.csproj", "Providers/Resgrid.Providers.Bus.Rabbit/"] COPY ["Core/Resgrid.Framework/Resgrid.Framework.csproj", "Core/Resgrid.Framework/"] diff --git a/Web/Resgrid.Web/Resgrid.Web.csproj b/Web/Resgrid.Web/Resgrid.Web.csproj index cdb887299..aab929491 100644 --- a/Web/Resgrid.Web/Resgrid.Web.csproj +++ b/Web/Resgrid.Web/Resgrid.Web.csproj @@ -62,7 +62,6 @@ - @@ -81,7 +80,7 @@ - + @@ -91,7 +90,6 @@ - diff --git a/Web/Resgrid.Web/Views/Account/LogOn.cshtml b/Web/Resgrid.Web/Views/Account/LogOn.cshtml index e93f3a75d..aa3734163 100644 --- a/Web/Resgrid.Web/Views/Account/LogOn.cshtml +++ b/Web/Resgrid.Web/Views/Account/LogOn.cshtml @@ -148,6 +148,9 @@ +
@@ -200,7 +203,8 @@ 'it': '/images/flags/32/Italy.png', 'pl': '/images/flags/32/Poland.png', 'uk': '/images/flags/32/Ukraine.png', - 'ar': '/images/flags/32/Saudi-Arabia.png' + 'ar': '/images/flags/32/Saudi-Arabia.png', + 'el': '/images/flags/32/Greece.png' }; if (langCookieValue) { diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js index f55e047ed..47bca78db 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js @@ -1,4 +1,4 @@ -var resgrid; +var resgrid; (function (resgrid) { var dispatch; (function (dispatch) { @@ -339,7 +339,12 @@ var resgrid; addArchivedCall.foundLocation = foundLocation; function noLocation() { - map.panTo(new L.LatLng(-34.397, 150.644)); + // Browser geolocation was denied or unavailable. Fall back to the department's + // configured map centre -- this used to pan to a hardcoded coordinate in Wollongong, + // Australia, which every department outside NSW saw as "the map is in the wrong place". + if (centerLat && centerLng) { + map.panTo(new L.LatLng(centerLat, centerLng)); + } } addArchivedCall.noLocation = noLocation; diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js index 8fc746b6b..8ea9b14b0 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js @@ -1,4 +1,4 @@ - + var resgrid; (function (resgrid) { var dispatch; @@ -379,7 +379,12 @@ var resgrid; } newcall.foundLocation = foundLocation; function noLocation() { - map.panTo(new L.LatLng(-34.397, 150.644)); + // Browser geolocation was denied or unavailable. Fall back to the department's + // configured map centre -- this used to pan to a hardcoded coordinate in Wollongong, + // Australia, which every department outside NSW saw as "the map is in the wrong place". + if (centerLat && centerLng) { + map.panTo(new L.LatLng(centerLat, centerLng)); + } } newcall.noLocation = noLocation; function setMarkerLocation(lat, lng) { diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js b/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js index c61a6fcbb..23cd7d883 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js @@ -84,7 +84,8 @@ var resgrid; 'it': '/images/flags/32/Italy.png', 'pl': '/images/flags/32/Poland.png', 'uk': '/images/flags/32/Ukraine.png', - 'ar': '/images/flags/32/Saudi-Arabia.png' + 'ar': '/images/flags/32/Saudi-Arabia.png', + 'el': '/images/flags/32/Greece.png' }; if (langCookieValue) { diff --git a/Workers/Resgrid.TrackerGateway/Dockerfile b/Workers/Resgrid.TrackerGateway/Dockerfile index e41250628..b6a1287ad 100644 --- a/Workers/Resgrid.TrackerGateway/Dockerfile +++ b/Workers/Resgrid.TrackerGateway/Dockerfile @@ -13,6 +13,10 @@ EXPOSE 5027/tcp 5027/udp FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab513c6ca4476ddb2f9f11adb9c04aa7c89 AS build ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Workers/Resgrid.TrackerGateway/Resgrid.TrackerGateway.csproj", "Workers/Resgrid.TrackerGateway/"] COPY ["Core/Resgrid.Config/Resgrid.Config.csproj", "Core/Resgrid.Config/"] diff --git a/Workers/Resgrid.Workers.Console/Dockerfile b/Workers/Resgrid.Workers.Console/Dockerfile index 135065d1b..3110f892e 100644 --- a/Workers/Resgrid.Workers.Console/Dockerfile +++ b/Workers/Resgrid.Workers.Console/Dockerfile @@ -15,6 +15,10 @@ WORKDIR /app FROM dhi.io/dotnet:9.0.314-sdk-debian13@sha256:a3acd51de0af79878e26292b3053aab513c6ca4476ddb2f9f11adb9c04aa7c89 AS build ARG BUILD_VERSION WORKDIR /src +## Root MSBuild files must be present for the restore layer, otherwise the +## security pins in Directory.Build.targets are missing from project.assets.json. +COPY ["Directory.Build.props", "./"] +COPY ["Directory.Build.targets", "./"] COPY ["Workers/Resgrid.Workers.Console/Resgrid.Workers.Console.csproj", "Workers/Resgrid.Workers.Console/"] COPY ["Providers/Resgrid.Providers.Bus.Rabbit/Resgrid.Providers.Bus.Rabbit.csproj", "Providers/Resgrid.Providers.Bus.Rabbit/"] diff --git a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs index cfb0fbff8..5fbb204a0 100644 --- a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs +++ b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs @@ -13,7 +13,12 @@ namespace Resgrid.Workers.Framework.Logic { public class SecurityLogic { - private static TimeSpan Day30CacheLength = TimeSpan.FromDays(30); + /// + /// The matrix is a snapshot of units, groups, roles and memberships at build time; anything + /// created afterwards is missing from it. It is rebuilt on every input change now, so this is + /// only the backstop for a dropped queue message -- a day, not a month. + /// + private static TimeSpan MatrixCacheLength = TimeSpan.FromHours(24); private static string WhoCanViewUnitsCacheKey = "ViewUnitsSecurityMaxtix_{0}"; private static string WhoCanViewUnitLocationsCacheKey = "ViewUnitLocationsSecurityMaxtix_{0}"; private static string WhoCanViewPersonnelCacheKey = "ViewUsersSecurityMaxtix_{0}"; @@ -60,7 +65,7 @@ public async Task> Process(SecurityQueueItem item) async Task getWhoCanViewUnits() { var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.ViewGroupUnits); - var unitsPayload = new VisibilityPayloadUnits(); + var unitsPayload = new VisibilityPayloadUnits() { GeneratedOn = DateTime.UtcNow }; var units = await _unitsService.GetUnitsForDepartmentAsync(item.DepartmentId); if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) @@ -193,7 +198,7 @@ async Task getWhoCanViewUnits() if (Config.SystemBehaviorConfig.CacheEnabled) { await _cacheProvider.RemoveAsync(string.Format(WhoCanViewUnitsCacheKey, item.DepartmentId)); - await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitsCacheKey, item.DepartmentId), getWhoCanViewUnits, Day30CacheLength); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitsCacheKey, item.DepartmentId), getWhoCanViewUnits, MatrixCacheLength); } } else if (item.Type == SecurityCacheTypes.WhoCanViewUnitLocations) @@ -201,7 +206,7 @@ async Task getWhoCanViewUnits() async Task getWhoCanViewUnitLocations() { var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.CanSeeUnitLocations); - var unitsPayload = new VisibilityPayloadUnits(); + var unitsPayload = new VisibilityPayloadUnits() { GeneratedOn = DateTime.UtcNow }; var units = await _unitsService.GetUnitsForDepartmentAsync(item.DepartmentId); if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) @@ -334,7 +339,7 @@ async Task getWhoCanViewUnitLocations() if (Config.SystemBehaviorConfig.CacheEnabled) { await _cacheProvider.RemoveAsync(string.Format(WhoCanViewUnitLocationsCacheKey, item.DepartmentId)); - await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitLocationsCacheKey, item.DepartmentId), getWhoCanViewUnitLocations, Day30CacheLength); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitLocationsCacheKey, item.DepartmentId), getWhoCanViewUnitLocations, MatrixCacheLength); } } else if (item.Type == SecurityCacheTypes.WhoCanViewPersonnel) @@ -342,7 +347,7 @@ async Task getWhoCanViewUnitLocations() async Task getWhoCanViewUsers() { var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.ViewGroupUsers); - var usersPayload = new VisibilityPayloadUsers(); + var usersPayload = new VisibilityPayloadUsers() { GeneratedOn = DateTime.UtcNow }; var allUsers = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(item.DepartmentId); if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) @@ -481,7 +486,7 @@ async Task getWhoCanViewUsers() if (Config.SystemBehaviorConfig.CacheEnabled) { await _cacheProvider.RemoveAsync(string.Format(WhoCanViewPersonnelCacheKey, item.DepartmentId)); - await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelCacheKey, item.DepartmentId), getWhoCanViewUsers, Day30CacheLength); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelCacheKey, item.DepartmentId), getWhoCanViewUsers, MatrixCacheLength); } } else if (item.Type == SecurityCacheTypes.WhoCanViewPersonnelLocations) @@ -489,7 +494,7 @@ async Task getWhoCanViewUsers() async Task getWhoCanViewUserLocations() { var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.CanSeePersonnelLocations); - var usersPayload = new VisibilityPayloadUsers(); + var usersPayload = new VisibilityPayloadUsers() { GeneratedOn = DateTime.UtcNow }; var allUsers = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(item.DepartmentId); if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) @@ -628,7 +633,7 @@ async Task getWhoCanViewUserLocations() if (Config.SystemBehaviorConfig.CacheEnabled) { await _cacheProvider.RemoveAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, item.DepartmentId)); - await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, item.DepartmentId), getWhoCanViewUserLocations, Day30CacheLength); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, item.DepartmentId), getWhoCanViewUserLocations, MatrixCacheLength); } } diff --git a/Workers/Support/Quidjibo.Postgres/Quidjibo.Postgres.csproj b/Workers/Support/Quidjibo.Postgres/Quidjibo.Postgres.csproj index 559ba9bed..4b8637955 100644 --- a/Workers/Support/Quidjibo.Postgres/Quidjibo.Postgres.csproj +++ b/Workers/Support/Quidjibo.Postgres/Quidjibo.Postgres.csproj @@ -60,7 +60,7 @@ - + diff --git a/Workers/Support/Quidjibo.SqlServer/Quidjibo.SqlServer.csproj b/Workers/Support/Quidjibo.SqlServer/Quidjibo.SqlServer.csproj index 0735562f0..718f20a95 100644 --- a/Workers/Support/Quidjibo.SqlServer/Quidjibo.SqlServer.csproj +++ b/Workers/Support/Quidjibo.SqlServer/Quidjibo.SqlServer.csproj @@ -20,7 +20,7 @@ - + From 4f1a2da31c2c09939e3eea1ef4d47e8da7c8a239 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sat, 15 Aug 2026 08:06:37 -0700 Subject: [PATCH 2/3] RG-T131 PR#464 fixes --- .../DepartmentSettingsService.cs | 3 +- Core/Resgrid.Services/DepartmentsService.cs | 11 +- .../Resgrid.Services/PersonnelRolesService.cs | 8 +- Core/Resgrid.Services/UnitsService.cs | 11 +- Docker/resgrid.env | 74 ++- .../Web/UtcDateTimeConverterTests.cs | 113 +++++ .../Controllers/v4/ConfigController.cs | 4 + .../Helpers/UtcDateTimeConverter.cs | 43 +- .../Resgrid.Web.Services.xml | 7 + .../User/Controllers/DispatchController.cs | 26 +- .../Logic/SecurityLogic.cs | 19 + docs/.vscode/settings.json | 3 - docs/Makefile | 19 - .../offline-first-architecture.md | 456 ------------------ docs/make.bat | 35 -- docs/source/.vscode/settings.json | 3 - docs/source/_templates/layout.html | 6 - docs/source/apps/index.rst | 81 ---- docs/source/conf.py | 175 ------- docs/source/configuration/index.rst | 74 --- docs/source/contributing/index.rst | 3 - docs/source/development/index.rst | 135 ------ docs/source/docker/index.rst | 121 ----- docs/source/index.rst | 73 --- docs/source/installation/index.rst | 125 ----- docs/source/overview/index.rst | 22 - docs/source/providers/index.rst | 30 -- docs/source/setup/index.rst | 222 --------- docs/source/system/index.rst | 3 - docs/source/updating/index.rst | 16 - 30 files changed, 308 insertions(+), 1613 deletions(-) create mode 100644 Tests/Resgrid.Tests/Web/UtcDateTimeConverterTests.cs delete mode 100644 docs/.vscode/settings.json delete mode 100644 docs/Makefile delete mode 100644 docs/architecture/offline-first-architecture.md delete mode 100644 docs/make.bat delete mode 100644 docs/source/.vscode/settings.json delete mode 100644 docs/source/_templates/layout.html delete mode 100644 docs/source/apps/index.rst delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/configuration/index.rst delete mode 100644 docs/source/contributing/index.rst delete mode 100644 docs/source/development/index.rst delete mode 100644 docs/source/docker/index.rst delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/installation/index.rst delete mode 100644 docs/source/overview/index.rst delete mode 100644 docs/source/providers/index.rst delete mode 100644 docs/source/setup/index.rst delete mode 100644 docs/source/system/index.rst delete mode 100644 docs/source/updating/index.rst diff --git a/Core/Resgrid.Services/DepartmentSettingsService.cs b/Core/Resgrid.Services/DepartmentSettingsService.cs index 8a3819942..9f76b5743 100644 --- a/Core/Resgrid.Services/DepartmentSettingsService.cs +++ b/Core/Resgrid.Services/DepartmentSettingsService.cs @@ -304,7 +304,8 @@ public async Task SaveMapCenterCoordinatesAsync(int departmentId, s await SaveOrUpdateSettingAsync(departmentId, $"{sanitizedLatitude},{sanitizedLongitude}", DepartmentSettingTypes.BigBoardMapCenterGpsCoordinates, cancellationToken); - if (double.TryParse(sanitizedLatitude, out var storedLatitude) && double.TryParse(sanitizedLongitude, out var storedLongitude)) + if (double.TryParse(sanitizedLatitude, NumberStyles.Any, CultureInfo.InvariantCulture, out var storedLatitude) && + double.TryParse(sanitizedLongitude, NumberStyles.Any, CultureInfo.InvariantCulture, out var storedLongitude)) return new Coordinates { Latitude = storedLatitude, Longitude = storedLongitude }; return null; diff --git a/Core/Resgrid.Services/DepartmentsService.cs b/Core/Resgrid.Services/DepartmentsService.cs index d0f9174c3..5172c749d 100644 --- a/Core/Resgrid.Services/DepartmentsService.cs +++ b/Core/Resgrid.Services/DepartmentsService.cs @@ -253,7 +253,10 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen dm.IsHidden = false; dm.IsDisabled = false; - return await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + var saved = await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + SendMembershipVisibilityRefresh(departmentId); + + return saved; } public async Task AddExistingUserAsync(int departmentId, string userId, CancellationToken cancellationToken = default(CancellationToken)) @@ -282,7 +285,10 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen await _limitsService.InvalidateDepartmentsEntityLimitsCache(departmentId); - return await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + var saved = await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + SendMembershipVisibilityRefresh(departmentId); + + return saved; } /// @@ -365,6 +371,7 @@ private void SendMembershipVisibilityRefresh(int departmentId) }); InvalidateDepartmentUsersInCache(d.DepartmentId); + SendMembershipVisibilityRefresh(d.DepartmentId); return saved; } diff --git a/Core/Resgrid.Services/PersonnelRolesService.cs b/Core/Resgrid.Services/PersonnelRolesService.cs index 437a0ee23..21dd2b3cc 100644 --- a/Core/Resgrid.Services/PersonnelRolesService.cs +++ b/Core/Resgrid.Services/PersonnelRolesService.cs @@ -102,7 +102,13 @@ public async Task GetRoleByDepartmentAndNameAsync(int departmentI await _personnelRoleUsersRepository.DeleteAsync(user, cancellationToken); } - SendRoleVisibilityRefresh(users?.FirstOrDefault()?.DepartmentId ?? 0); + // A single call can span departments, so every department represented in the list needs a + // rebuild -- refreshing only the first user's department leaves the rest on a stale matrix. + if (users != null) + { + foreach (var departmentId in users.Where(x => x != null).Select(x => x.DepartmentId).Distinct()) + SendRoleVisibilityRefresh(departmentId); + } return true; } diff --git a/Core/Resgrid.Services/UnitsService.cs b/Core/Resgrid.Services/UnitsService.cs index 52bcbcdf8..33ae8c572 100644 --- a/Core/Resgrid.Services/UnitsService.cs +++ b/Core/Resgrid.Services/UnitsService.cs @@ -457,6 +457,7 @@ public async Task> GetAllRolesForDepartmentAsync(int departmentId throw new ArgumentException("DepartmentGroupId cannot be null", "departmentGroupId"); var units = await _unitsRepository.GetAllUnitsByGroupIdAsync(departmentGroupId); + var touchedDepartmentIds = new HashSet(); foreach (var unit in units) { @@ -464,9 +465,17 @@ public async Task> GetAllRolesForDepartmentAsync(int departmentId unit.StationGroup = null; - await _unitsRepository.SaveOrUpdateAsync(unit, cancellationToken); + var saved = await _unitsRepository.SaveOrUpdateAsync(unit, cancellationToken); + + if (saved != null) + touchedDepartmentIds.Add(saved.DepartmentId); } + // Un-stationing a unit moves it out of its station group's bucket in both unit matrices; + // without a rebuild the group's old viewers keep seeing it. + foreach (var departmentId in touchedDepartmentIds) + SendUnitVisibilityRefresh(departmentId); + return true; } diff --git a/Docker/resgrid.env b/Docker/resgrid.env index 17a544db7..418be60f0 100644 --- a/Docker/resgrid.env +++ b/Docker/resgrid.env @@ -190,7 +190,7 @@ RESGRID__ServiceBusConfig__RabbbitPassword=Resgrid321! # ----------------------------------------------- # Microsoft SQL Server Connection String for Worker Database -RESGRID__WorkerConfig__WorkerDbConnectionString=Data Source=db;Initial Catalog=ResgridWorkers;User Id=sa;Password=Resgrid123!!;MultipleActiveResultSets=True; +RESGRID__WorkerConfig__WorkerDbConnectionString=Data Source=db;Initial Catalog=ResgridWorkers;User Id=sa;Password=Resgrid123!!;MultipleActiveResultSets=True;TrustServerCertificate=True;Connection Timeout=30; # Payload key for multiple agents # Note: Change this key if you are running multiple workers @@ -210,9 +210,81 @@ RESGRID__WebConfig__IngressProxyNetwork=10.42.0.0 # CIDR for Ingress network mask RESGRID__WebConfig__IngressProxyNetworkCidr=16 +# ----------------------------------------------- +# --- Hardware GPS Tracking Section ------------- +# ----------------------------------------------- + +# The tracking gateway Compose service is opt-in via: docker compose --profile tracking up +# Keep the feature and every native listener disabled until the selected image contains +# the exact certified protocol module. Supply CredentialPepper through secret management +# before enabling tracking; do not use a checked-in default. +RESGRID__UnitTrackingConfig__Enabled=false +RESGRID__UnitTrackingConfig__HttpsIngressEnabled=false +RESGRID__UnitTrackingConfig__NativeGatewayEnabled=false +RESGRID__UnitTrackingConfig__CredentialPepper= + +# Hardware-location retention is opt-in and runs in the worker container. +RESGRID__UnitTrackingConfig__LocationRetentionWorkerEnabled=false +RESGRID__UnitTrackingConfig__LocationRetentionBatchSize=1000 +RESGRID__UnitTrackingConfig__LocationRetentionMaxRowsPerRun=100000 +RESGRID__UnitTrackingConfig__LocationRetentionHourUtc=4 + +# Native gateway limits and internal-only health/metrics port +RESGRID__UnitTrackingConfig__TcpIdleTimeoutSeconds=300 +RESGRID__UnitTrackingConfig__MaxFrameBytes=65536 +RESGRID__UnitTrackingConfig__MaxConnections=5000 +RESGRID__UnitTrackingConfig__MaxConnectionsPerIp=100 +RESGRID__UnitTrackingConfig__GracefulShutdownSeconds=30 +RESGRID__UnitTrackingConfig__InternalHealthPort=8080 + +# Native protocol ports. Protocol and transport switches remain independently disabled. +RESGRID__UnitTrackingConfig__QueclinkTcpPort=5004 +RESGRID__UnitTrackingConfig__QueclinkUdpPort=5004 +RESGRID__UnitTrackingConfig__Gt06TcpPort=5023 +RESGRID__UnitTrackingConfig__Gt06UdpPort=5023 +RESGRID__UnitTrackingConfig__TeltonikaTcpPort=5027 +RESGRID__UnitTrackingConfig__TeltonikaUdpPort=5027 +RESGRID__UnitTrackingConfig__EnableQueclink=false +RESGRID__UnitTrackingConfig__EnableQueclinkTcp=true +RESGRID__UnitTrackingConfig__EnableQueclinkUdp=false +RESGRID__UnitTrackingConfig__EnableGt06=false +RESGRID__UnitTrackingConfig__EnableGt06Tcp=true +RESGRID__UnitTrackingConfig__EnableGt06Udp=false +RESGRID__UnitTrackingConfig__EnableTeltonika=false +RESGRID__UnitTrackingConfig__EnableTeltonikaTcp=true +RESGRID__UnitTrackingConfig__EnableTeltonikaUdp=false + +# ----------------------------------------------- +# --- LocalXpose Tunnel Section (tracker-gateway) +# ----------------------------------------------- +# Exposes the raw TCP/UDP tracking ingest ports through https://localxpose.io tunnels. +# The token comes from https://localxpose.io/dashboard/access. Leave LOCALXPOSE_ENABLED +# unset/false to run without tunnels. These are consumed by the tracker-gateway +# container's localxpose-entrypoint.sh, not by Resgrid config. +# LOCALXPOSE_ENABLED=false +# LOCALXPOSE_ACCESS_TOKEN= +# LOCALXPOSE_REGION=us +# LOCALXPOSE_TARGET_HOST=localhost +# Space-separated local ports to expose over TCP tunnels. +# LOCALXPOSE_TCP_PORTS=5004 5023 5027 +# Space-separated local ports to expose over UDP tunnels (empty = none). +# LOCALXPOSE_UDP_PORTS= +# Optional fixed public endpoints, comma-separated port=endpoint pairs (reserve them first: loclx endpoint reserve --port 5023). +# LOCALXPOSE_RESERVED_ENDPOINTS=5023=us.loclx.io:5023,5027=us.loclx.io:5027 +# Seconds to wait before restarting a tunnel process that exited. +# LOCALXPOSE_RESTART_DELAY=5 + # ----------------------------------------------- # --- Asp.Net Section --------------------------- # ----------------------------------------------- ASPNETCORE_ENVIRONMENT=Production ASPNETCORE_URLS=http://0.0.0.0:80 + +# --- Chat GIF search (Giphy) ------------------- +# GIF search in chat is disabled until the provider + API key are set. +# Get a key at https://developers.giphy.com +# RESGRID__ChatConfig__GifProvider=giphy +# RESGRID__ChatConfig__GiphyApiKey=YOUR_GIPHY_KEY +# Content rating cap: g (default, workplace-safe), pg, or pg-13. +# RESGRID__ChatConfig__GifRating=g diff --git a/Tests/Resgrid.Tests/Web/UtcDateTimeConverterTests.cs b/Tests/Resgrid.Tests/Web/UtcDateTimeConverterTests.cs new file mode 100644 index 000000000..397c8d20d --- /dev/null +++ b/Tests/Resgrid.Tests/Web/UtcDateTimeConverterTests.cs @@ -0,0 +1,113 @@ +using System; +using FluentAssertions; +using Newtonsoft.Json; +using NUnit.Framework; +using Resgrid.Web.Services.Helpers; + +namespace Resgrid.Tests.Web +{ + /// + /// The converter has to produce the same UTC instant no matter which token the reader hands it + /// (String when date parsing is off, Date when it is on) and no matter what Kind the reader + /// inferred. These run green on a server in any timezone -- that is the point of them. + /// + [TestFixture] + public class UtcDateTimeConverterTests + { + private static readonly DateTime ExpectedUtc = new DateTime(2026, 8, 12, 13, 5, 22, 123, DateTimeKind.Utc); + + [TestCase("2026-08-12T13:05:22.123", TestName = "String token, no zone marker, is taken at face value as UTC")] + [TestCase("2026-08-12T13:05:22.123Z", TestName = "String token with Z stays put")] + [TestCase("2026-08-12T06:05:22.123-07:00", TestName = "String token with an offset is converted")] + [TestCase("2026-08-12T13:05:22.1230000Z", TestName = "String token with a seven digit fraction is accepted")] + public void ReadJson_WithStringToken_ReturnsUtc(string serialized) + { + // Arrange -- DateParseHandling.None forces the reader to hand over a JsonToken.String. + var settings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }; + + // Act + var payload = JsonConvert.DeserializeObject($"{{\"Timestamp\":\"{serialized}\"}}", settings); + + // Assert + payload.Timestamp.Kind.Should().Be(DateTimeKind.Utc); + payload.Timestamp.Should().Be(ExpectedUtc); + } + + [TestCase(DateTimeZoneHandling.Unspecified, TestName = "Date token the reader left Unspecified is marked UTC")] + [TestCase(DateTimeZoneHandling.Local, TestName = "Date token the reader made Local is converted to UTC")] + [TestCase(DateTimeZoneHandling.Utc, TestName = "Date token the reader already made UTC is untouched")] + [TestCase(DateTimeZoneHandling.RoundtripKind, TestName = "Date token round-tripped from a Z keeps its instant")] + public void ReadJson_WithDateToken_ReturnsUtc(DateTimeZoneHandling zoneHandling) + { + // Arrange -- DateParseHandling.DateTime makes the reader produce a JsonToken.Date. + var settings = new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.DateTime, + DateTimeZoneHandling = zoneHandling + }; + + // Act + var payload = JsonConvert.DeserializeObject("{\"Timestamp\":\"2026-08-12T13:05:22.123Z\"}", settings); + + // Assert + payload.Timestamp.Kind.Should().Be(DateTimeKind.Utc); + payload.Timestamp.Should().Be(ExpectedUtc); + } + + [TestCase(DateTimeKind.Unspecified, TestName = "An Unspecified instant from the repositories round-trips")] + [TestCase(DateTimeKind.Utc, TestName = "A UTC instant round-trips")] + public void SerializeThenDeserialize_PreservesTheInstant(DateTimeKind kind) + { + // Arrange + var original = new UtcTimestampPayload + { + Timestamp = DateTime.SpecifyKind(new DateTime(2026, 8, 12, 13, 5, 22, 123), kind) + }; + + // Act + var serialized = JsonConvert.SerializeObject(original); + var round = JsonConvert.DeserializeObject(serialized); + + // Assert + serialized.Should().Contain("2026-08-12T13:05:22.123Z"); + round.Timestamp.Kind.Should().Be(DateTimeKind.Utc); + round.Timestamp.Should().Be(ExpectedUtc); + } + + [Test] + public void SerializeThenDeserialize_WithALocalInstant_PreservesTheInstant() + { + // Arrange -- a Local value means the process timezone leaked in somewhere upstream. + var original = new UtcTimestampPayload { Timestamp = ExpectedUtc.ToLocalTime() }; + + // Act + var round = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(original)); + + // Assert + round.Timestamp.Kind.Should().Be(DateTimeKind.Utc); + round.Timestamp.Should().Be(ExpectedUtc); + } + + [Test] + public void ReadJson_WithANullValue_StaysNull() + { + // Arrange / Act + var payload = JsonConvert.DeserializeObject("{\"Timestamp\":null}"); + + // Assert + payload.Timestamp.Should().BeNull(); + } + + private class UtcTimestampPayload + { + [JsonConverter(typeof(UtcDateTimeConverter))] + public DateTime Timestamp { get; set; } + } + + private class NullableUtcTimestampPayload + { + [JsonConverter(typeof(UtcDateTimeConverter))] + public DateTime? Timestamp { get; set; } + } + } +} diff --git a/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs b/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs index 774c634b1..c602a93a2 100644 --- a/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs +++ b/Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs @@ -228,6 +228,10 @@ private async Task PopulateUnitStatusThresholdsAsync(GetConfigResult result, int /// private async Task PopulateMapCenterAsync(GetConfigResult result, int departmentId) { + // Seeded first so an unauthenticated caller or a failed lookup still ships a usable center + // instead of 0,0 -- these are the same system defaults the settings service falls back to. + result.Data.MapCenterLatitude = 39.14086268299356; + result.Data.MapCenterLongitude = -119.7583809782715; result.Data.MapCenterZoomLevel = 9; if (departmentId <= 0) diff --git a/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs b/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs index 1b0d89fba..7921105e8 100644 --- a/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs +++ b/Web/Resgrid.Web.Services/Helpers/UtcDateTimeConverter.cs @@ -19,8 +19,49 @@ public class UtcDateTimeConverter : IsoDateTimeConverter { public UtcDateTimeConverter() { - DateTimeStyles = System.Globalization.DateTimeStyles.AdjustToUniversal; + // AssumeUniversal is what makes a zone-less "2026-08-12T13:05:22" read back as the same + // instant. Without it AdjustToUniversal assumes *local* and shifts the value by whatever + // offset the server happens to run on. + DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal | + System.Globalization.DateTimeStyles.AdjustToUniversal; DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"; + Culture = System.Globalization.CultureInfo.InvariantCulture; + } + + public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var underlyingType = Nullable.GetUnderlyingType(objectType) ?? objectType; + + // The base converter reads with ParseExact against DateTimeFormat, so the "...fffZ" *write* + // format would reject every inbound string not written exactly that way -- the zone-less + // values the repositories produce, whole-second values, anything carrying an offset. Parse + // those here instead; the styles above are what make a zone-less value keep its instant. + if (reader.TokenType == Newtonsoft.Json.JsonToken.String && underlyingType == typeof(DateTime)) + { + var dateText = reader.Value?.ToString(); + + if (!String.IsNullOrWhiteSpace(dateText)) + return NormalizeToUtc(DateTime.Parse(dateText, Culture, DateTimeStyles)); + } + + return NormalizeToUtc(base.ReadJson(reader, objectType, existingValue, serializer)); + } + + /// + /// A JsonToken.Date comes back from the reader untouched, so whatever Kind the reader inferred + /// survives. Normalised the same way WriteJson does, otherwise a value only round-trips when + /// the reader happened to see a "Z". + /// + private static object NormalizeToUtc(object value) + { + if (value is DateTime dateTime && dateTime.Kind != DateTimeKind.Utc) + { + return dateTime.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc) + : dateTime.ToUniversalTime(); + } + + return value; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) diff --git a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml index cec55fe4c..19f6edbc6 100644 --- a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml +++ b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml @@ -5382,6 +5382,13 @@ and marking them UTC would shift them the other way.
+ + + A JsonToken.Date comes back from the reader untouched, so whatever Kind the reader inferred + survives. Normalised the same way WriteJson does, otherwise a value only round-trips when + the reader happened to see a "Z". + + Gets or sets the on authentication failed. diff --git a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs index 6d3fe1cf4..9e3d1311a 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs @@ -220,9 +220,6 @@ public async Task NewCall() return View(model); } - [HttpPost] - [ValidateAntiForgeryToken] - /// /// Adds a model error for every field the department's new-call policy requires but the form /// left blank. Keyed to the form fields so the messages land next to the inputs. @@ -234,6 +231,13 @@ private async Task ApplyNewCallFieldPolicyAsync(NewCallView model, IFormCollecti if (policy == null || policy.IsEmpty) return; + // The dispatch lists, protocols and linked calls are posted as one form key per selected + // item, and are only unpacked further down -- read the same keys here so the policy sees + // what was actually submitted instead of flagging every one of them as missing. + var keys = collection?.Keys?.ToList() ?? new List(); + + bool HasKeyStartingWith(string prefix) => keys.Any(x => x.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + var values = new NewCallFieldValues { Note = model.Call?.Notes, @@ -246,7 +250,19 @@ private async Task ApplyNewCallFieldPolicyAsync(NewCallView model, IFormCollecti IncidentId = model.Call?.IncidentNumber, ReferenceId = model.Call?.ReferenceNumber, DestinationPoiId = model.Call?.DestinationPoiId, - HasDispatchList = collection != null && collection.Keys.Any(x => x.StartsWith("dispatch", StringComparison.OrdinalIgnoreCase)) + IndoorMapZoneId = collection?["IndoorMapZoneId"].FirstOrDefault(), + // A pending protocol only counts when it is actually ticked, matching how the list is + // unpacked below. + HasProtocols = HasKeyStartingWith("activeProtocol_") || + keys.Any(x => x.StartsWith("pendingProtocol_", StringComparison.OrdinalIgnoreCase) && collection[x] == "1"), + HasLinkedCall = HasKeyStartingWith("linkedCall_"), + DispatchOn = model.ScheduleDispatchDate, + // Only the four prefixes the dispatch lists actually post. A bare "dispatch" prefix also + // swallows anything else the form happens to name that way. + HasDispatchList = HasKeyStartingWith("dispatchUser_") || + HasKeyStartingWith("dispatchGroup_") || + HasKeyStartingWith("dispatchUnit_") || + HasKeyStartingWith("dispatchRole_") }; foreach (var violation in NewCallFieldPolicyValidator.Validate(policy, values)) @@ -256,6 +272,8 @@ private async Task ApplyNewCallFieldPolicyAsync(NewCallView model, IFormCollecti } } + [HttpPost] + [ValidateAntiForgeryToken] [Authorize(Policy = ResgridResources.Call_Create)] public async Task NewCall(NewCallView model, IFormCollection collection, CancellationToken cancellationToken) { diff --git a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs index 5fbb204a0..d7f75133c 100644 --- a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs +++ b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs @@ -53,7 +53,26 @@ public SecurityLogic() _authorizationService = Bootstrapper.GetKernel().Resolve(); } + /// + /// A rebuild that throws must not take the caller down with it. The all-departments sweep would + /// abandon every department after the failing one, and the queue handler has no catch of its + /// own -- a single department with unparsable permission data would stall the rest. + /// public async Task> Process(SecurityQueueItem item) + { + try + { + return await ProcessInternalAsync(item); + } + catch (Exception ex) + { + Resgrid.Framework.Logging.LogException(ex); + + return new Tuple(false, ex.Message); + } + } + + private async Task> ProcessInternalAsync(SecurityQueueItem item) { bool success = true; string result = String.Empty; diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json deleted file mode 100644 index ab3eb0a76..000000000 --- a/docs/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "restructuredtext.confPath": "${workspaceFolder}\\source" -} \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 69fe55ecf..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/architecture/offline-first-architecture.md b/docs/architecture/offline-first-architecture.md deleted file mode 100644 index fa0672c61..000000000 --- a/docs/architecture/offline-first-architecture.md +++ /dev/null @@ -1,456 +0,0 @@ -# Resgrid Offline-First Architecture - -**Status:** Design / proposal -**Author:** Resgrid IC backend work (RIC-T39 follow-on) -**Last updated:** 2026-06-27 -**Applies to:** Resgrid **IC** app (new, `../IC`), Resgrid **Unit** app (existing, `../Unit`), Resgrid **Core** backend (this repo) - -> This document is the single source of truth for how the Resgrid mobile apps work -> **fully offline** and **sync back** when reconnected. It is written so the **Unit** -> app team can implement the identical pattern — both apps share one design. - ---- - -## 1. Goal & scenario - -A responder logs in and **syncs at the start of a shift** (online, usually on station -WiFi) and pulls down everything they need. They then drive to an incident where there is -**no cell or WiFi**. The app must remain **useful offline** — they can view calls / the -command board / roster / maps, and they can **take actions** (set status, set location, -check in, assign resources, complete objectives, add annotations, etc.). Those actions are -**captured locally** and **synced back** to Resgrid automatically when the device returns -to connectivity (at the incident if cell returns, or back at station/office). - -**Hard requirements** - -1. Full read functionality offline for the shift's working data set. -2. Full write capture offline, replayed on reconnect, with no lost or duplicated work. -3. **Offline mapping** — map tiles for the operational area must be available with no network. -4. One design, documented, implementable in **both IC and Unit**. - -**Non-goals (v1)** - -- Real-time multi-device collaborative editing while offline (no CRDTs). Resgrid writes are - overwhelmingly *additive, timestamped intent-events*, not concurrent edits to a shared document. -- Offline geocoding / turn-by-turn routing (vector search + routing need network; we cache - tiles and known POIs/destinations only). Documented as a limitation. - ---- - -## 2. Why this design (decision record) - -**Decision:** *Evolve* the persistence + outbox stack the Unit app already uses -(**Zustand + MMKV + an offline event queue**) into a complete sync layer. Do **not** -introduce a new on-device database or a managed sync engine for v1. - -The Unit app is already ~40% of the way here. State of the world today (`../Unit`): - -| Capability | Today | Gap to close | -|---|---|---| -| Persistence | Zustand 4.5.7 + **MMKV 3.1.0** (encrypted), `zustandStorage` adapter | Most domain stores (e.g. `calls`) are **not persisted** → nothing to read offline | -| Write outbox | **Exists**: `src/stores/offline-queue/store.ts` (4 event types, retry/backoff) drained by `src/services/offline-event-manager.service.ts` | Only 4 event types; **no ordering, no idempotency, no dependencies**; relies on backend being idempotent | -| Read cache | `src/api/common/cached-client.ts` + `cache-manager.ts`, 5-min TTL in MMKV | **Does not serve stale data when offline** — returns only *fresh* entries. Biggest single blocker | -| Maps | **@rnmapbox/maps 10.2.10** (Mapbox, native v11.16.2) | `offlineManager.createPack()` supported but unused | -| Realtime | `@microsoft/signalr 8.0.17`, 2 hubs (update + geo) | Needs reconnect reconciliation | -| Auth offline | Tokens in MMKV, `offline_access` scope, network-vs-401 already distinguished in `client.tsx` | Mostly fine | - -**Alternatives considered and rejected for v1:** - -- **Embedded SQLite (op-sqlite + Drizzle).** More robust at scale, but the per-shift/per-incident - working set is hundreds–low-thousands of records, not millions; SQL's query/row-write edge is - marginal here, and retrofitting both apps' domain stores onto a new persistence layer is a large - migration. **Kept as a documented upgrade path** for individual hot datasets (see §11). -- **Managed sync engine (PowerSync / ElectricSQL / WatermelonDB / RxDB).** Least custom sync code, - but PowerSync/Electric sync from the *database* via Postgres CDC and route writes around your - service layer — that fights Resgrid's heavy server-side business logic (billing, workflows, - accountability) and its dual SQL Server + PostgreSQL backends. WatermelonDB/RxDB still require a - full persistence-layer rewrite in both apps. Rejected. - -**Consequences:** lowest risk, fastest to ship in both apps, one mental model. The cost is that we -own the sync logic (orchestrator, conflict rules, idempotency) — which this document specifies. - ---- - -## 3. Architecture overview - -``` -┌──────────────────────────── Device (IC app / Unit app) ────────────────────────────┐ -│ │ -│ UI (React / Expo Router) │ -│ │ reads (reactive) ▲ optimistic apply │ -│ ▼ │ │ -│ Zustand stores ──── persist ────► MMKV (encrypted) ◄── stale-while-offline reads │ -│ │ │ -│ │ write intent │ -│ ▼ │ -│ Outbox (offline-queue store, MMKV) ── ordered, idempotent ──┐ │ -│ │ │ -│ Sync Orchestrator ◄─────────────────────────────────────────┘ │ -│ • fullSync() (shift start) │ -│ • drain() (replay outbox on reconnect) │ -│ • pullDelta() (reconcile server-authoritative truth) │ -│ • map pre-download (offlineManager.createPack) │ -│ │ │ -│ NetInfo (online/offline) SignalR (live updates when online) │ -└─────┼────────────────────────────────────┼─────────────────────────────────────────┘ - │ REST v4 │ CQRS push (callsUpdated, incidentCommandUpdated…) - ▼ ▼ -┌──────────────────────────── Resgrid Core backend (this repo) ───────────────────────┐ -│ v4 REST controllers + Services + Repos CQRS/SignalR (ICoreEventService) │ -│ NEW: delta endpoints, idempotent creates, tombstones, sync bundle (see §9) │ -└──────────────────────────────────────────────────────────────────────────────────┘ -``` - -Three sync phases, described in §6–§8: **(1) shift-start hydration**, **(2) offline operation**, -**(3) reconnect sync-back**. - ---- - -## 4. Local persistence model - -**Keep MMKV + Zustand `persist`.** Every store that holds data needed offline must: - -1. Wrap its store in `persist(...)` using the existing `zustandStorage` adapter - (`src/lib/storage/index.tsx` → `createJSONStorage(() => zustandStorage)`). -2. Use `partialize` to persist only durable server data + sync metadata (exclude transient flags - like `isLoading`). -3. Carry per-store sync metadata: `lastSyncAt: number`, and where applicable a server `syncToken`/ - high-water timestamp for delta pulls. - -> **Action item:** the `calls` store (and any other currently-ephemeral domain store) must be -> converted to `persist`. Today `src/stores/calls/store.ts` re-fetches on launch and has nothing to -> show offline. This is the first concrete code change in the Unit app and the default for every IC -> store. - -**Stale-while-offline (critical fix).** Upgrade `cached-client.ts` so that when -`NetInfo.isConnected === false` (or a request fails with a network error), it **returns the last -cached value even if its TTL expired**, flagged as stale. Today it only returns *fresh* entries, so -offline reads return nothing. The cache becomes the read fallback; persisted Zustand stores are the -primary offline read source. - -Read precedence offline: **persisted store → stale cache → empty/typed-default** (never throw). - -**Storage budget.** MMKV holds JSON; keep persisted payloads to the working set. Prune on incident -close (drop closed-incident command boards, their map packs, completed/uploaded outbox entries). - ---- - -## 5. The outbox (generalized write model) - -Generalize the existing `offline-queue` from 4 hard-coded event types to a **typed intent-event** -that can represent any mutation in either app. - -### 5.1 Event contract - -```ts -interface QueuedEvent { - id: string; // client UUID — ALSO the idempotency key sent to the server - type: QueuedEventType; // discriminator → handler + endpoint (see registry below) - entityType: string; // 'IncidentCommand' | 'UnitStatus' | 'CheckIn' | 'Annotation' | … - op: 'create' | 'update' | 'delete' | 'action'; - payload: Record; // request body; for creates, INCLUDES the client-generated GUID PK - dependsOn?: string; // id of a prior queued event that must COMPLETE first (FK ordering) - clientCreatedAt: number; // ms — authoritative timestamp for last-write-wins - // existing fields retained: - status: 'pending' | 'processing' | 'failed' | 'completed'; - retryCount: number; maxRetries: number; - createdAt: number; lastAttemptAt?: number; nextRetryAt?: number; error?: string; -} -``` - -This is a superset of today's shape — existing events migrate by adding `entityType`/`op`/ -`clientCreatedAt`/`dependsOn`. Persistence (MMKV key `offline-queue-storage`, `partialize`) is unchanged. - -### 5.2 Handler registry - -Replace the per-type `switch` in `offline-event-manager.service.ts` with a registry mapping -`QueuedEventType → { apiCall, onSuccess(serverResult), isIdempotent }`. Adding a new offline -mutation = one registry entry, in either app. Today's 4 handlers (UNIT_STATUS, LOCATION_UPDATE, -CALL_IMAGE_UPLOAD, CHECK_IN) become registry entries. - -### 5.3 Drainer changes (correctness) - -The current drainer (`Promise.allSettled`, 3 concurrent, **no ordering**) is unsafe once events have -dependencies (e.g. *assign resource* offline before *establish command* has synced). Required changes: - -- **Order by `createdAt`** and **honor `dependsOn`**: an event whose dependency is not yet - `completed` is skipped this pass (stays pending). Independent events may still run concurrently. -- **Idempotency on replay** (see §5.4) so a partial success + retry never double-applies. -- Keep: netinfo gate, AppState trigger + 10 s interval, exponential backoff, max-retries → `failed` - with manual retry. Add a **dead-letter** surfacing UI for permanently failed events (today they - silently sit in `failed`). - -### 5.4 Idempotency (the key to safe replay) - -Two mechanisms, chosen per entity: - -- **Client-generated GUID PK for creates.** Resgrid entities already use string-GUID PKs that the - *service* sets via `Guid.NewGuid().ToString()`. Change create endpoints to **honor a client-supplied - id when present** and **upsert by PK**. The client generates the GUID offline, stores it locally, - and includes it in the payload → replaying the create is a no-op the second time. This makes - additive records (annotations, objectives, resource/role assignments, ad-hoc resources) safe. -- **Idempotency key for actions.** Append/telemetry actions (check-in, set status, set location) - send `id` (the event UUID) as an idempotency key; the server dedups on - `(DepartmentId, UserId, IdempotencyKey)` within a window, or simply accepts last-write-wins by - `clientCreatedAt` (a replayed status with the same timestamp is harmless). - -### 5.5 Optimistic apply - -On a write the app: (1) generates the GUID/event, (2) **applies the change to the Zustand store -immediately** (so the UI updates offline) with a `_pendingSync` flag, (3) enqueues the outbox event. -On successful drain, clear `_pendingSync` and reconcile with the server result. On permanent failure, -mark the local record `_syncError` and surface it (do **not** silently roll back field work). - ---- - -## 6. Phase 1 — Shift-start hydration (online) - -`SyncOrchestrator.fullSync()`, run on login / "Sync now": - -1. **Bulk pull** all working data in parallel and persist to stores. For IC: active calls + call - metadata, command boards for active incidents, lanes/resources/objectives/timers/roles/annotations, - accountability/check-in status, mutual-aid + ad-hoc resources, department units/personnel/statuses, - groups, POIs/destinations, protocols, contacts, config. For Unit: its existing `init()` set, now - persisted, plus routes/protocols/weather. -2. **Download offline map packs** (see §10) for the operational area and any active-incident bounds. -3. Record `lastSyncAt` / per-store high-water timestamps; show progress + a "Synced ✓ (time)" state. -4. Pre-warm SignalR so live updates keep the cache hot while still online. - -### 6.1 Authoritative shift-start manifest (what the app MUST pull to be field-ready) - -The app is "field-ready" — able to **start and run** an incident fully offline — once it has pulled ALL of -the following. Each row names its delivery endpoint. Three Sync endpoints now exist -(`Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs`); everything else rides existing v4 `GetAll*` -endpoints. This list is the single source of truth — if a dataset isn't here with a delivery mechanism, the -app is not field-complete. - -**A. Reference data (slowly-changing) — `GET /api/v4/Sync/Reference`** (one call → `SyncReferenceData`): -call types, call priorities, command-definition **templates** (needed to establish/seed command), units + -unit types, **personnel roster** (SAFE projection — name / mobile / primary group / current state, NO -credentials), groups, POIs + POI types, dispatch protocols, check-in timer configs, personnel + unit custom -statuses, resolved feature flags. Pull once per shift / on manual refresh; cache aggressively. - -**B. Live incident state — `GET /api/v4/Sync/Bundle`** (one call → `IncidentCommandBundle`): a render-ready -board per ACTIVE incident (lanes, resources, objectives, timers, roles, annotations) **including computed -accountability / PAR**, plus active ad-hoc resources, plus a `ServerTimestampMs` cursor. -`?includeAccountability=false` skips the per-incident PAR computation for departments with very many open -incidents. - -**C. Incremental catch-up — `GET /api/v4/Sync/Changes?since={cursor}`** (→ `IncidentCommandChanges`): the -row-based delta of incident-command rows (incl. tombstones / closed / released) changed since the cursor, for -reconnect reconciliation. `since=0` is a full row pull. - -**D. Delivered by other means (existing v4 endpoints — documented, intentionally not re-bundled):** the Call -list + metadata (Calls API — incidents are CAD-dispatched), mutual-aid assignable resources -(`MutualAidController`, per-call), indoor maps / custom GIS layers (Mapping API), detailed per-user -roles/certs (Personnel API), and Mapbox offline tile packs (§10, client-side download). The app pulls these -alongside A–C at shift start. - -> Delivery split rationale: **A** is static/cacheable, **B** is live and bounded by active-incident count, -> **C** is the reconnect delta. Keeping them separate keeps each call cacheable/paginatable and avoids one -> mega-endpoint that couples every subsystem and is impossible to keep performant. - ---- - -## 7. Phase 2 — Offline operation - -- **Reads**: served from persisted Zustand stores; `cached-client` returns stale-when-offline. - NetInfo drives a global "Offline" banner + per-record "pending sync" chips. -- **Writes**: optimistic apply (§5.5) → outbox enqueue. The UI never blocks on the network. -- **Maps**: render from the downloaded offline pack; user location from GPS (works offline); - annotations/markers are local store data. -- **Auth**: tokens already persist in MMKV; refresh fails gracefully offline (existing - network-vs-401 logic in `client.tsx`). Ensure the access token's absence/expiry while offline does - **not** force logout — gate logout on a *non-network* 401 only (already the case) and allow the app - to operate read/write-to-outbox with an expired token; the outbox drains after a successful refresh - on reconnect. - ---- - -## 8. Phase 3 — Reconnect sync-back - -NetInfo offline→online transition triggers, in order: - -1. **Refresh auth** if needed (existing flow). -2. **Drain the outbox** (§5.3) — ordered, dependency-aware, idempotent. Each success updates the - local record with the server's canonical result and clears `_pendingSync`. -3. **Pull delta / reconcile** (§9): fetch server changes since `lastSyncAt` and merge using the - conflict policy (§8.1). This catches edits made by *others* (dispatch, other responders) while - this device was offline. -4. **Reconnect SignalR** and resume live updates; set `lastSyncAt = now`. - -### 8.1 Conflict-resolution policy (per entity class) - -| Class | Examples | Policy | -|---|---|---| -| **Telemetry / time-series** | unit/personnel status, location, check-in | **Server-authoritative, accept by timestamp (LWW).** Replays harmless. | -| **Additive records** | annotations, timeline/log entries, objectives, resource & role assignments, ad-hoc resources, call notes/images | **Client GUID PK → idempotent create.** No real conflict; duplicates impossible via PK. | -| **Mutable singletons** | the IncidentCommand, lane structure, action plan, call fields | **LWW by `ModifiedOn`**; last writer wins, prior value recorded to the timeline. The one-active-command unique index (M0077) already prevents the worst split-brain. | -| **Deletes** | removed lanes/assignments/resources | **Tombstones** (soft-delete `DeletedOn`) so delta pull propagates the removal to all clients. | - -Field-ops reality: contention is rare because each responder mostly writes *their own* status/location/ -check-in and *adds* records. LWW + idempotent-additive covers the overwhelming majority; the timeline -preserves an audit trail when a singleton is overwritten. - ---- - -## 9. Backend changes required (Core repo) - -These land in this repo as the next backend chunk (migrations start at **M0081**, never renumber — -see the IC backend state doc). All are additive and apply to **both** SQL Server and PostgreSQL. - -1. **Consistent change-tracking columns.** Ensure offline-relevant entities have - `CreatedOn`, `ModifiedOn` (set on every write), and `DeletedOn` (soft-delete/tombstone). Many IC - entities have `CreatedOn`/`Timestamp` already; audit and backfill the rest. -2. **Idempotent creates.** v4 create endpoints honor a **client-supplied GUID PK** and upsert by PK - (today the services overwrite with `Guid.NewGuid()`). Guard for cross-department ownership exactly - as the existing IC create paths do. -3. **Action idempotency keys.** Check-in / status / location endpoints accept an `IdempotencyKey` - (the outbox event id) and dedup on `(DepartmentId, UserId, IdempotencyKey)` within a window — or - rely on LWW-by-timestamp where natural. -4. **Delta endpoint — DONE.** `GET /api/v4/Sync/Changes?since={epochMs}` (`SyncController.Changes`) returns - every change-tracked incident-command row (incl. tombstones / closed / released) changed since the cursor, - scoped to the caller's department, with a `ServerTimestampMs` high-water cursor; `since=0` = full row pull. - (Covers the IC command working set; broader-domain deltas remain future work.) -5. **Shift-start aggregates — DONE.** - - `GET /api/v4/Sync/Bundle` (`SyncController.Bundle` → `IIncidentCommandService.GetBundleForDepartmentAsync`): - a render-ready board per ACTIVE incident incl. computed accountability/PAR + active ad-hoc + cursor. - **Performance:** assembled with ONE scan per board table (grouped by CallId in memory) — O(tables), not - O(active incidents × department size) — and READ-ONLY (no PAR write-sweep / SignalR storm). The - per-incident PAR read is skippable with `?includeAccountability=false` for very busy departments. - - `GET /api/v4/Sync/Reference` (`SyncController.Reference` → `ISyncService.GetReferenceDataAsync`): the - reference manifest (§6.1.A). Personnel is a SAFE projection (`ReferencePersonnel`) — never raw - `IdentityUser` / `UserProfile` (which carry password hashes + contact-verification codes + CalendarSyncToken); - groups are projected to `ReferenceGroup` to drop the member `IdentityUser` navs. - -> CQRS/SignalR already publishes live updates including `IncidentCommandUpdated = 22` -> (`ICoreEventService.IncidentCommandUpdatedAsync`). The delta endpoints are the *catch-up* path for -> what was missed while offline; SignalR is the *live* path while online. Keep both. - ---- - -## 10. Offline mapping (@rnmapbox/maps) - -Mapbox is already the map engine (`@rnmapbox/maps 10.2.10`, native v11). Use its **offline pack** -API — no map-library change. - -```ts -await offlineManager.createPack({ - name: `incident-${callId}`, - styleURL: StyleURL.Street, // the style already in use - minZoom: 10, - maxZoom: 16, // cap to bound size; see caveats - bounds: [[swLng, swLat], [neLng, neLat]], -}, onProgress, onError); -``` - -**What to pre-download at shift start (§6):** - -- **Operational area pack** — wide bounds (department/station AO), lower max zoom (~13–14) for - context. -- **Per-incident pack(s)** — tight bounds around each active call location, higher max zoom (~16) for - tactical detail. Created on incident establish; deleted on incident close to reclaim space. - -**Caveats (from the rnmapbox issue tracker — design around these):** - -- The **Mapbox access token must be set before `createPack`** or iOS can crash. Sequence map init - before any offline download. -- **Tile-count limits / Mapbox ToS** apply (`setTileCountLimit`); do not bypass. Cap `maxZoom` and - bounds so a pack stays within budget; surface download size to the user. -- Historical Android bugs around zoom > 15 and pack usability — validate on target devices; keep - `maxZoom ≤ 16` unless verified. -- All `offlineManager` methods are async (SQLite-backed); show progress and handle partial downloads - / resume. - -**Limitations (documented):** offline geocoding/search and routing are **not** available without -network — cache known POIs/destinations/routes as store data instead. Satellite/imagery packs are -large; default to the vector street style offline. - ---- - -## 11. Reuse across IC and Unit (shared module) - -Both apps must run the *same* implementation. Neither app is a monorepo today. - -**Recommended:** extract the sync layer into a single shared TypeScript module with a stable public -API, consumed by both apps: - -- `SyncOrchestrator` (`fullSync`, `drain`, `pullDelta`, map pre-download) -- the generalized outbox store + handler-registry types -- the `zustandStorage`/MMKV persist helpers (already identical in spirit) -- the stale-while-offline `cached-client` wrapper -- the conflict-resolution helpers + entity policy table -- the Mapbox offline-pack helper - -Packaging options, in order of preference: -1. **Private package** `@resgrid/offline-sync` (local `file:`/workspace link or private registry) — - one source of truth, versioned. -2. A shared folder consumed by both via path alias / git submodule. -3. **Copy with a documented contract** (this doc) — acceptable for v1 if packaging is deferred, as - long as both copies stay in lockstep. - -App-specific pieces stay in each app: the **entity registry** (which stores/endpoints exist), the -**data scope** to hydrate, and the **map bounds** logic. The orchestrator, outbox, conflict engine, -and map helper are shared verbatim. - ---- - -## 12. Per-app offline data scope - -**IC app** — "useful offline at an incident": the command board for active incidents (lanes, -resources, objectives, timers, roles, annotations, timeline), accountability/PAR status, mutual-aid + -ad-hoc resources, department units/personnel/statuses, call detail + metadata, POIs/destinations, -protocols, maps for the incident area. Offline writes: establish/transfer/close command, lane CRUD, -resource assign/move/release, role assign/remove, objective complete, timer start/ack, annotations, -check-ins, status/location. - -**Unit app** — its current `init()` data made durable: active calls + metadata, units, personnel + -statuses + staffing, dispatches, contacts, groups, POIs, routes, protocols, weather. Offline writes: -the existing 4 (status, location, call image, check-in) plus call notes/files/close as needed. - ---- - -## 13. Implementation sequencing - -1. **Backend (this repo):** change-tracking columns + idempotent creates + action idempotency keys - (migrations M0081+), then delta endpoint(s). Each is additive and independently shippable. -2. **Shared sync module:** generalize the outbox + handler registry; build `SyncOrchestrator`; - upgrade `cached-client` to stale-while-offline; Mapbox offline-pack helper. -3. **Unit app adoption (lower risk, proves the pattern):** persist the ephemeral stores; wire the - orchestrator; pre-download maps; move the 4 existing events onto the generalized outbox. -4. **IC app (Part B):** built offline-first from the start on the shared module. -5. **Reconnect reconciliation + SignalR catch-up:** delta pull on reconnect; conflict policy; dead-letter UI. - ---- - -## 14. Risks & open questions - -- **Backfilling `ModifiedOn`/tombstones** across legacy Resgrid entities is the largest backend - effort; scope delta sync to offline-relevant entities first. -- **Idempotent creates** require auditing every targeted v4 create endpoint to honor a client PK - safely (ownership guards must hold). -- **Mapbox tile budget / ToS** for fleet-wide offline downloads — confirm licensing/limits with - Mapbox for the expected device count and AO sizes. -- **MMKV size ceiling** for very large datasets (long message/call history) — the documented upgrade - path is op-sqlite for *that dataset only*, behind the same store interface. -- **Clock skew** affects LWW — prefer server `ModifiedOn` for singletons; use `clientCreatedAt` only - for the device's own telemetry. -- **Long-offline token expiry** — verify the refresh-token lifetime (`offline_access`) exceeds a - realistic offline shift; if not, allow a longer-lived refresh for the mobile scope. - ---- - -## 15. References - -- Unit app stack: Zustand+MMKV stores (`src/stores/*`), outbox (`src/stores/offline-queue/store.ts`, - `src/services/offline-event-manager.service.ts`), cache (`src/api/common/cached-client.ts`, - `src/lib/cache/cache-manager.ts`), client (`src/api/common/client.tsx`), maps - (`src/components/maps/*`), SignalR (`src/services/signalr.service.ts`). -- IC backend (this repo): `Core/Resgrid.Model/IncidentCommand/*`, `Core/Resgrid.Services/IncidentCommandService.cs`, - v4 controllers under `Web/Resgrid.Web.Services/Controllers/v4/`, CQRS `CqrsEventTypes.IncidentCommandUpdated = 22`. -- Mapbox offline: rnmapbox `offlineManager` docs — https://rnmapbox.github.io/docs/components/offlineManager -- Local-first / sync background: PowerSync RN local-DB options - (https://powersync.com/blog/react-native-local-database-options), Expo local-first guide - (https://docs.expo.dev/guides/local-first/), RxDB RN (https://rxdb.info/react-native-database.html). -- Sync patterns: outbox + idempotency (https://microservices.io/patterns/data/transactional-outbox.html), - offline conflict resolution (https://www.sachith.co.uk/offline-sync-conflict-resolution-patterns-architecture-trade%E2%80%91offs-practical-guide-feb-19-2026/). diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 4d9eb83d9..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/source/.vscode/settings.json b/docs/source/.vscode/settings.json deleted file mode 100644 index d5d660bd6..000000000 --- a/docs/source/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "restructuredtext.confPath": "${workspaceFolder}" -} \ No newline at end of file diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html deleted file mode 100644 index cc05e5b99..000000000 --- a/docs/source/_templates/layout.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "!layout.html" %} - - {% block menu %} - {{ super() }} - Keyword Index - {% endblock %} \ No newline at end of file diff --git a/docs/source/apps/index.rst b/docs/source/apps/index.rst deleted file mode 100644 index d2fdad194..000000000 --- a/docs/source/apps/index.rst +++ /dev/null @@ -1,81 +0,0 @@ -####### -Apps -####### - -Below you will find the high level documentations and information for the external Resgrid applications. We have a number of external applications that run the gambit from mobile applications to web apps to desktop apps. - -.. _resgrid_relay: - -Resgrid Relay -**************************** - -`Resgrid Relay `_ allows you to listen to an audio feed, i.e. from a scanner, and monitor for tones and record the audio and dispatch personnel via Resgrid. - -.. note:: You can download releases for Resgrid Relay on it's `Github Releases page `_ - -Before you begin you will need the following: - -* The tones frequency(s) you want to monitor in integer\number format i.e. 153 -* A Windows 7 or newer computer with an Audio Line In connection or a usb audio sound card like `StarTech.com 7.1 USB Sound Card `_ -* A scanner like `WS1065 `_ with an Audio Line Out connection - -Installing Resgrid Relay -======================= -Download the latest release from Githib releases page. This will be a zip file that you need to unzip the file on your computer `How to Unzip Files on Windows `_ and we recommend putting the resulting folder in C:\\ResgridRelay. - -From Windows Explorer run the - -Using Notepad - - -.. list-table:: IIS Options - :header-rows: 1 - - * - Section - - Sub Section - - Option - * - Web Management Tools - - - - IIS Management Console - * - World Wide Web Services - - Application Development Features - - .Net Extensibility 3.5 - * - World Wide Web Services - - Application Development Features - - .Net Extensibility 4.7 - * - World Wide Web Services - - Application Development Features - - ASP.NET 3.5 - * - World Wide Web Services - - Application Development Features - - ASP.NET 4.7 - * - World Wide Web Services - - Application Development Features - - ISAPI Extensions - * - World Wide Web Services - - Application Development Features - - ISAPI Filters - * - World Wide Web Services - - Common HTTP Features - - Default Document - * - World Wide Web Services - - Common HTTP Features - - HTTP Errors - * - World Wide Web Services - - Common HTTP Features - - HTTP Redirection - * - World Wide Web Services - - Common HTTP Features - - Static Content - * - World Wide Web Services - - Performance Features - - Dynamic Content Compression - * - World Wide Web Services - - Performance Features - - Static Content Compression - * - World Wide Web Services - - Security - - Basic Authentication - * - World Wide Web Services - - Security - - IP Security \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index a32b31cb2..000000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = u'Resgrid Core' -copyright = u'2019, Resgrid, LLC.' -author = u'Resgrid, LLC.' - -# The short X.Y version -version = u'' -# The full version, including alpha/beta/rc tags -release = u'' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'ResgridCoredoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'ResgridCore.tex', u'Resgrid Core Documentation', - u'Resgrid, LLC.', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'resgridcore', u'Resgrid Core Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'ResgridCore', u'Resgrid Core Documentation', - author, 'ResgridCore', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - -# -- Extension configuration ------------------------------------------------- diff --git a/docs/source/configuration/index.rst b/docs/source/configuration/index.rst deleted file mode 100644 index f942af68c..000000000 --- a/docs/source/configuration/index.rst +++ /dev/null @@ -1,74 +0,0 @@ -####### -Configuration -####### - - -In this section we have all the configuration option for the system broken out into their functional area. - - - -Department Settings -************** - - -.. _stations_groups: - -Stations & Groups -======================= - -.. _call_import: - -Call Import Settings -======================= - -.. _custom_statuses: - -Custom Statuses -======================= - -.. _text_messaging: - -Test Messaging -======================= - -.. _templates: - -Templates -======================= - -.. _protocols: - -Protocols -======================= - -.. _types: - -Types -======================= - -.. _distribution_lists: - -Distribution Lists -======================= - -.. _security_permissions: - -Security & Permissions -======================= - -.. _subscription_billing: - -Subscription & Billing -======================= - -Calls & Dispatch -************** - -The core part of the Resgrid system is the Calls (Dispatch) facilities. Calls can be created in the system by hand, through external applications or tools, like the Resgrid Responder app or Dispatch app, can be created by importing emails or data payloads like JSON or XML, or even directly by api call. - -.. _dispatch_protocols: - -Protocols -======================= - -Protocols also known as Dispatch Protocols, are department defined procedures, actions, SOPs, etc that should be followed during an operation. For example the most common Protocol in the Resgrid system is the Dispatch version that activates when a new call is created in the system. diff --git a/docs/source/contributing/index.rst b/docs/source/contributing/index.rst deleted file mode 100644 index e3082df0f..000000000 --- a/docs/source/contributing/index.rst +++ /dev/null @@ -1,3 +0,0 @@ -####### -Contributing -####### \ No newline at end of file diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst deleted file mode 100644 index e03783192..000000000 --- a/docs/source/development/index.rst +++ /dev/null @@ -1,135 +0,0 @@ -####### -Development -####### - -In this section we will go over all the steps needed to develop against the Resgrid solution. - -.. _development_prerequisites: - -Prerequisites & Dependencies -**************************** - -The following server dependencies need to be installed, configured and functional: - -.. note:: Please ensure your Windows system is up to date with all Windows and Microsoft updates before installing the Resgrid System. - -* `.Net Framework `_ .NET Framework 4.6.2 (Developer Pack) -* `RabbitMQ Server `_, version 3.6.0 or newer -* `Microsoft SQL Server `_, version 12.0 (SQL 2014) or newer -* `Microsoft IIS `_ version installed on Windows 8 or newer or Windows Server 2012 or newer -* `Docker for Windows Desktop `_ Docker for Windows Desktop -* `Microsoft Visual Studio 2017 `_ Community or Higher -* `Google Chrome `_ Chrome 71 or newer -* `Elastic ELK `_ 6.6.0 or newer - -.. note:: If your not running a Professional (Pro) version of Windows you may not be able to install Docker for Windows Desktop. You will get an error opening up the ResgridCore solution with Visual Studio but your can just unlock the docker project under the Docker solution folder. - -Getting the Code -**************************** - -You can download the Resgrid Core source from our GitHub page `Resgrid Core Github `_. - -Opening in Visual Studio -**************************** - -Open the ResgridCore.sln file in your version of Microsoft Visual Studio 2017. You will be prompted by a "Security Warning" dialog box, you can confirm for every project, but if you uncheck "Ask me for every project in this solution" you only need to be prompted once. - -If you get an error opening the solution up Visual Studio this could be because you don't have Docker installed (it can't be installed on all versions of Windows) so you can expand the Docker folder in the Solution Explorer right click "docker-compose" and click "Unload Project". This will allow you to open and compile the solution without any error. If you do have Docker installed on your computer, ensure that it's running. - -Open up the Web folder in the "Solution Explorer" and right click the "Resgrid.WebCore" project and select "Set as Startup Project". This will mean that when you run or debug the solution a web browser will open up defaulting you to the Web project. - -Restoring Dependencies -======================= - -Once you have the solution open correctly you need to download all the dependencies for the project. Right click the "ResgridCore" solution and click "Restore Nuget Packages", this will download all the .Net dependencies for the solution. - -Next you need to restore the bower and npm dependencies for the Resgrid.WebCore project. Expand the Web folder in the "Solution Explorer" and expand the "Resgrid.WebCore" project, At the root of that project there are 2 files; bower.json and package.json that we will be working with. Right click "bower.json" and select "Restore Packages" this will download all the bower dependencies. Next right click "package.json" and select "Restore Packages". - -Solution -**************************** - -Folders -======================= - -.. list-table:: Solution Folders - :header-rows: 1 - - * - Folder - - Description - * - Common - - Contains common files that may be included in other projects, like the AssemblyInfo file - * - Core - - Central libraries utilized throughout the system - * - Docker - - Projects related to setting up and managing Docker - * - Documentation - - Notes and Documentation - * - Providers - - High level external integrations, like Geolocation or Text Messaging - * - Repositories - - Data Storage Repo - * - Tests - - Unit and Integration Tests - * - Tools - - Non-Web UI tools and applications - * - Web - - The main applications, the web application and the services (api) application - * - Workers - - Backend workers - -Projects -======================= - -.. list-table:: Solution Projects - :header-rows: 1 - - * - Project - - Description - * - Resgrid.Config - - Primary system configuration options controlling the entire system - * - Resgrid.Framework - - Shared helpers and common functions, like error logging that are used in every layer - * - Resgrid.Model - - Data model objects, event objects, interfaces for services\providers and system metadata, like enumerations. - * - Resgrid.Services - - Business logic layer services, both discrete and composite - * - Resgrid.Providers.AddressVerification - - External address verification providers - * - Resgrid.Providers.Audio - - External audio manipulation providers - * - Resgrid.Providers.Bus - - Azure Service Bus and System Eventing - * - Resgrid.Providers.Bus.Rabbit - - RabbitMQ bus provider - * - Resgrid.Providers.Cache - - Redis and Internal (In Memory) caching provider - * - Resgrid.Providers.Claims - - Rights and Claims system for the Web Application - * - Resgrid.Providers.EmailProvider - - External email providers (Postmark) - * - Resgrid.Providers.Firebase - - Firebase external provider used for the real-time database (Chatting) - * - Resgrid.Providers.GeoProvider - - Geolocation provider for getting Latitude and Longitude for Addresses and vice versa - * - Resgrid.Providers.Marketing - - External provider for working with an email marking system - * - Resgrid.Providers.NumberProvider - - Number, SMS\MMS provider (Twilio and Nexemo) - * - Resgrid.Providers.PdfProvider - - External PDF integration provider - * - Resgrid.Providers.AddressVerification - - Address verification, testing if address are correct - * - Resgrid.Repositories.DataRepository - - Primary Data Store, SQL Server both Entity Framework and Dapper - * - Resgrid.Tests - - Unit Testing - * - Resgrid.Console - - CLI Application for interacting with the Resgrid system - * - Resgrid.Web.Services - - RESTful APIs (Services) - * - Resgrid.WebCore - - Primary Web Application (User Interface\Website) that users will interact with - * - Resgrid.Workers.Console - - CLI Application that needs to be running at all times, contains back end workers for the Message Bus - * - Resgrid.Workers.Framework - - Logic for the async workers that the Workers.Console runs\monitors \ No newline at end of file diff --git a/docs/source/docker/index.rst b/docs/source/docker/index.rst deleted file mode 100644 index 990b362a2..000000000 --- a/docs/source/docker/index.rst +++ /dev/null @@ -1,121 +0,0 @@ -####### -Docker -####### - -In this section we will go over setup of the Resgrid system using Docker containers. - -.. important:: Resgrid requires working **RabbitMQ**, **Redis** and **SQL** servers, more info in :ref:`installation_prerequisites` below and currently only runs on Microsoft Windows operating systems - -This documentation is for installation of Resgrid from compile source. If you want to install Resgrid from Docker containers please review that section instead. - -.. _requirements: - -Requirements Notice -**************************** - -It is highly recommended that Resgrid is installed and setup by an IT Professional. There is a large amount of system configuration, tweaking and setup that is required to be done before you install Resgrid. Below is a list of technologies that you should have skilled professionals available to you or requisite knowledge before installing Resgrid. Resgrid does not provide support or configuration guidance for those systems outside of the minimum needed to get the system functional. The steps outlined below will get the system in a bare minimum functional state to ensure it's working on your enviroment, to be production ready will reqire more effort then is outlined in this documentation. - -* Windows or Linux -* Docker, Kubernetes, Rancher, K8s -* SQL Server or PostgreSQL -* DNS, hostname mapping, proxy configuration -* RabbitMQ -* Redis -* Elastic -* Mail Server SMTP, POP3 -* Firewall and system hardning - -.. _docker_container_images: - -Docker Container Images -**************************** - -Resgrid is split into 3 distinct Docker containers. All of our container images are available under the Resgrid, LLC organization on the `Docker Hub `_. - -**resgridwebcore** -This is the web application docker image and is used to host the website application that users will interact with. - -Docker Pull Command:: - - docker pull resgridllc/resgridwebcore - -**resgridwebservices** -This is the web api that is used by the website and applications to communicate with the Resgrid system - -Docker Pull Command:: - - docker pull resgridllc/resgridwebservices - -**resgridworkersconsole** -This is the backend workers that are used to process operations from RabbitMQ or scheduled tasks. - -Docker Pull Command:: - - docker pull resgridllc/resgridworkersconsole - -.. _settings: - -Settings -**************************** - -To configure the Resgrid system in a Docker or Kubernetes context we recommend using environment variables. To see all the config options availabe you can take a look at our Github repo , every `static` class in the Resgrid.Congfig project can be set by an environment variable. - -The pattern for how Resgrid processes environment is as follows: - -RESGRID__{CLASSNAME}__{PROPERTYNAME} - -Resgrid at the start of the name must be in all caps, there are two (2) underscores seperating the parts, in between RESGRID and classname and classname and popertyname. - -.. list-table:: Resgrid Environment Variables - :header-rows: 1 - - * - Variables - - Required - - Description - * - RESGRID__CacheConfig__RedisConnectionString - - Yes - - The full connection string to the Redis server or cluster - * - RESGRID__DataConfig__ConnectionString - - Yes - - The connection string to the Microsoft SQL Server - * - RESGRID__ExternalErrorConfig__ExternalErrorServiceUrl - - No - - Url for Sentry.io error reporting - * - RESGRID__InboundEmailConfig__DispatchDomain - - No - - Domain name to put at the end of the dispatch email address - * - RESGRID__InboundEmailConfig__GroupMessageDomain - - No - - Domain name to put at the end of the group message email address - * - RESGRID__InboundEmailConfig__GroupsDomain - - No - - Domain name to put at the end of the group dispatch email address - * - RESGRID__InboundEmailConfig__ListsDomain - - No - - Domain name to put at the end of the distribution list email address - * - RESGRID__ServiceBusConfig__RabbbitExchange - - Yes - - RabbitMQ exchange name (can be blank) - * - RESGRID__ServiceBusConfig__RabbitHostname - - Yes - - Hostname or IP Address of the RabbitMQ server or cluster - * - RESGRID__ServiceBusConfig__RabbitUsername - - Yes - - Login for RabbitMQ that has permissions to create queues and publish and recieve messages - * - RESGRID__ServiceBusConfig__RabbbitPassword - - Yes - - Password for the RabbitMQ login - * - RESGRID__SystemBehaviorConfig__ApiTokenEncryptionPassphrase - - Yes - - Passphrase to encrypt API tokens with - * - RESGRID__SystemBehaviorConfig__DoNotBroadcast - - Yes - - True/False prevents any communications from being sent if set to True - * - RESGRID__SystemBehaviorConfig__ResgridApiBaseUrl - - Yes - - URL for the Resgrid API for this Resgrid install - * - RESGRID__SystemBehaviorConfig__ResgridBaseUrl - - Yes - - Base url to access the web install of Resgrid - -.. note:: The above is only a partial list to get the Resgrid system functional. You may need to set others to get the system fully operational within your environment. At a minimum, Microsoft SQL Server, Redis and RabbitMQ are required as well as setting the ResgridAPI url and ResgridBase web url. diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 85cef55a1..000000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,73 +0,0 @@ -################################## -Resgrid Core: The Complete Open Source Computer Aided Dispatch System -################################## - -`Resgrid `_ is a computer aided dispatch, management and logistics for first responders, disaster response, emergency management and companies. - -Originally started as a hosted only solution in 2014, the Resgrid system as processed hundreds of thousands of calls, messages, statuses and staffing updates and much more. With over 4,000 departments signed up Resgrid is the only open source computer aided dispatch system able to run at scale. - -Resgrid is written on the Microsoft .Net and .Net Core Frameworks utilizing Microsoft SQL Server as the primary data repository. - -Features -******** -* Personnel Management: Define personnel, contact information, details, certification, roles, status and availability for all personnel -* Unit Support: Support for apparatuses and groups of personnel working as a single unit (i.e. a USAR team) with AVL, accountability and logging -* Groups and Locations: Create groups and locations and assign personnel or units underneath for management of large or disperse organizations -* Computer Aided Dispatch: Create Calls\Incidents and dispatch personnel, units, roles or groups to respond to those incidents both manual and automatic dispatches are supported -* Messaging: Built in message system to allow for targeted and dynamic communications to personnel -* Chat: Embedded P2P, Groups, Dispatch to Unit and Command chat system to enable very quick text based communications -* Duty Shift System: Create both Assigned Shifts and Signup Shifts to manage a static or dynamic workforce, switch swapping and trading support with attendance validation -* Learning Management: Design Trainings with text based materials, attach documents or presentations or link to external videos and assign questions to validate understanding of material -* Run Logs and Logging: Record actions of a call, training and meetings to keep tract of actions and events, hours, personnel and units involved -* Reporting** Generate reports for run logs, calls, training, meetings and more. Ability to use Reporting to create exports to integrate with 3rd party systems -* Calendar System: Create calendar entries and setup RSVP style events to keep personnel engaged and informed about activities and events -* Inventory Management: Track any kind of inventory both perishable (like medicine) and durable (like hand tools) equipped on apparatus, issued to personnel or stored at locations -* Document Storage: Upload and serve documents at a department or group level to members of your organization allowing a centralized place to serve documents from -* Notifications Service: Flexible notification system to alert of low personnel role or unit availability, staffing or status changes or any other system generated event -* Department Linking: Create powerful department links to allow for multiple independent organizations (i.e. mutual aid agreements or centralized dispatch center) to cooperate -* Mobile Apps: Apps available on Google Play and Apple App Store that can work with any standard installation. For Personnel, Units, Stations and Commanders. -* API: Included API with information about calls allow for easy extension and interaction without having to change code in the Resgrid Core codebase - -Getting started -*************** -.. hlist:: - :columns: 2 - - * :doc:`/installation/index` -- Install and run the Resgrid system - * :doc:`/docker/index` -- Install and run the Resgrid system with Docker - * :doc:`/development/index` -- Start developing with Resgrid - * :doc:`/contributing/index` -- Contribution guidelines and information - * :doc:`/system/index` -- Overview of the Resgrid system and features - * :doc:`/setup/index` -- Walkthrough on setting up your department - * :doc:`/configuration/index` -- All configuration options for your department - * :doc:`/apps/index` -- Documentation for our external applications - -Links -***** - -* `Resgrid Homepage and Hosted Solution `_ -* `Documentation `_ -* `Source code `_ - -Documentation Contents -************* -.. toctree:: - :maxdepth: 2 - - /overview/index - /installation/index - /docker/index - /setup/index - /configuration/index - /providers/index - /system/index - /apps/index - /development/index - /contributing/index - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst deleted file mode 100644 index 5099e26ad..000000000 --- a/docs/source/installation/index.rst +++ /dev/null @@ -1,125 +0,0 @@ -####### -Installation -####### - -In this section we will go over all the steps needed to get Resgrid running on your own environment. - -.. _requirements: - -Requirements Notice -**************************** - -It is highly recommended that Resgrid is installed and setup by an IT Professional. There is a large amount of system configuration, tweaking and setup that is required to be done before you install Resgrid. Below is a list of technologies that you should have skilled professionals available to you or requisite knowledge before installing Resgrid. Resgrid does not provide support or configuration guidance for those systems outside of the minimum needed to get the system functional. The steps outlined below will get the system in a bare minimum functional state to ensure it's working on your enviroment, to be production ready will reqire more effort then is outlined in this documentation. - -* Windows or Linux -* Docker, Kubernetes -* SQL Server or PostgreSQL -* DNS, hostname mapping, proxy configuration -* RabbitMQ -* Redis -* Elastic -* Mail Server SMTP, POP3 -* Firewall and system hardning - -.. _system_requirements: - -System Requirements -**************************** - -The all-in-one docker installation is suitable for a deparment of around 50 personnel on a machine with 32GB of RAM, 500GB of storage and a 8 logical processors. But depending on call volume or user ineraction patterns may require more. - -We do not recommend that mission critial systems be installed on a single machine. Resgrid is split into multiple containers to allow for multiple machines to be used. - -A mission-critial production environment will require a minimum of 10 servers: -* 2 Load Balanced Web servers -* 2 Load Balanced API servers -* 1 Microsoft Sql Server -* 1 Worker server -* 1 Events server -* 1 Redis server -* 1 RabbitMQ server -* 1 Elasticsearch server (ELK) - -Sizing of these servers will depend on your departments amount of users and call volume. - -.. _installation_prerequisites: - -Prerequisites & Dependencies -**************************** - -To run the Resgrid containers you will need the following: - -* Docker -Install `Docker `, either using a native package or Docker Desktop. - -.. note:: All Resgrid container images are based on Linux, users of Docker for Windows will need to ensure that `Docker is using Linux containers `. - -* A minimum of 24GB RAM assigned to Docker - - With Docker for Mac, the amount of RAM dedicated to Docker can be set using the UI: see `How to increase docker-machine memory Mac `. - - In Docker Desktop for Windows, `use the *Advanced* tab to adjust limits on resources available to Docker `. - -* A limit on mmap counts equal to 262,144 or more - - On Linux, use `sysctl vm.max_map_count` on the host to view the current value, and see `Elasticsearch's documentation on virtual memory ` for guidance on how to change this value. Note that the limits **must be changed on the host**; they cannot be changed from within a container. - - If using Docker for Mac, then you will need to start the container with the `MAX_MAP_COUNT` environment variable (set to at least 262144 (using e.g. `docker`'s `-e` option) to make it sets the limits on mmap counts at start-up time. - -* Docker Compose -Install `Docker Compose ` - -* Open Ports 5151 through 5165 -* SMTP Server for sending email - -.. note:: Any correctly configured SMTP server will work if it's local or not. If you have an SMTP server provided by your ISP or provider that will also work. - -.. _docker_compose: - -Docker Compose Setup -**************************** - -Download and Extract Package -================ - -Download the resgrid.tgz Asset file from the latest `Resgrid GitHub Release `:: - - wget https://github.com/Resgrid/Core/releases/download/vXX.XX.XX/resgrid.tgz - -.. note:: Esnure you replace vXX.XX.XX in that url to the version number of the Github release you are trying to download. - -Extract the tgz package file:: - - tar -xvzf resgrid.tgz - -You should now have a folder called resgrid in your current directory. - -Setting Enviorment Variables -================ - -Resgrid's docker containers are configured using enviorment variables defined in the ``resgrid.env`` file within the resgrid folder. Edit this file and configure the variables as needed for your enviorment. Please pay speical attention to the the (required) variables. - -Run the Docker Compose -================ -Once you have setup the enviorment variables you can now run the docker compose file.:: - - docker-compose up - -That will run the ineractive version of the containers, Crtl+C will stop the containers. - -If you want to run the containers in the background, use the `-d` option:: - - docker-compose up -d - -The Resgrid system will take about 5 minutes to start up fully, this is due to the startup order of the containers. The last container to startup will be the `web` container, once that one is ready, you can now access the system. - -Important Note About Support -**************************** - -Resgrid is a complex system that can scale from a single instance to dozens of systems to service thousands of users. These installation setups get your system into a state where you can test and validate locally on the install system. To get Resgrid up and running to service non-local users you will need to reconfigure and harden the system. To complete those steps and configuration the system to your orginizational needs you will require an IT professional. We do not provide installation support outside this guide via our Github page. - -Initial Web Login -**************************** - -Once you have completed the steps above you will be able to log into the web applications user interface. Open up a web browser and navigate to http://localhost:5151, you will then be prompted by the login screen. Your default administrator credentials are **admin/changeme1234**. Once you log into the system it's recommended that you change your admin password from the Edit Profile page by clicking on the Administrator name in the upper left hand corner. - diff --git a/docs/source/overview/index.rst b/docs/source/overview/index.rst deleted file mode 100644 index 5e17dddc4..000000000 --- a/docs/source/overview/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -####### -Overview -####### - - -Installation Types -************** - -`Resgrid `_ is open source software, so you can download the Resgrid source from `GitHub `_. We also provide a hosted version of Resgrid with a free plan and paid plan levels `Resgrid `_. - -Support -************** - -You can report a bug or request a feature for Resgrid by opening up an issue on Github `GitHub Issues `_. If your a customer of the Hosted Version of Resgrid, please use the support system within the application there. If your having issues with your OnPrem installation of Resgrid you can engage Resgrid for professional support services. - - -Professional Support -****************** - -We offer professional support for Resgrid, as well as remote consulting and engineering. - -You can contact us at shawn@resgrid.com to learn more, or visit us at https://resgrid.com. diff --git a/docs/source/providers/index.rst b/docs/source/providers/index.rst deleted file mode 100644 index 9626559f2..000000000 --- a/docs/source/providers/index.rst +++ /dev/null @@ -1,30 +0,0 @@ -####### -Providers -####### - -`Resgrid `_ utilizes a number of providers, or 3rd party services, to perform essential functions, like sending push notifications. This section will detail all 3rd party - -.. important:: All of the 3rd party services, for example for sending Text Messages (SMS\MMS) require you to sign up and create an account with them. - - -Service Bus -************** - - -Push Notifications -************** - - -Outbound Text Messaging -************** - - - -Inbound Text Messaging -************** - - - -Telephone Voice Calling -************** - diff --git a/docs/source/setup/index.rst b/docs/source/setup/index.rst deleted file mode 100644 index 0418613d3..000000000 --- a/docs/source/setup/index.rst +++ /dev/null @@ -1,222 +0,0 @@ -####### -Setup -####### - -This is the guide to getting your Department configured in the resgrid system. This setup guide is a high level overview of the settings to get your department up and running with Resgrid both the OnPrem and Hosted versions of the system. - -.. note:: Only one person in your department or organization needs to sign up for a new Resgrid department, when you create an account from the Resgrid homepage this happens automatically. If this is an on-premises installation, use the default department information to log in. - -Getting Started -************** - -Once you have your first user registered with Resgrid (or using the default OnPrem department) you can log into the system. The first page you will land on will be the dashboard page or home page. Here is a list of your personnel, groups and their statuses. You can also set your status and staffing level on the widgets on the right hand side. In the top of the screen you can click your name in the left hand side to view or update your profile. The 2 icons in the middle are your active calls and your inbox messages. The menus on the right are your Department Menu (it's the name of your department) and is the primary location to configure your department, a help menu and a logout button. - - -.. list-table:: Department Menu Items - :header-rows: 1 - - * - Item - - Visibility - - Description - * - Department Settings - - Admins Only - - General Department settings like name, main address, time zone and resets. - * - Stations & Groups - - Admins Only - - Create and Manage Groups and Membership in those Groups - * - Call Import Settings - - Admins Only - - Call Email Import Settings and Call Auto Close (Prune) Settings - * - Custom Statuses - - Admins Only - - Create Department Specific Personnel and Unit Statuses and Staffing Levels - * - Text Messaging - - Admins Only - - Type of Inbound Text Messages allowed (Call or Command) - * - Api Settings - - Admins Only - - Allocate a System level API Key and Key for Active Calls RSS Feed - * - Types - - Admins Only - - Manage Department Wide Types (Call Types, Call Priorities, Unit Types, Certifications) - * - Distribution Lists - - Admins Only - - Create and Manage Membership is custom defined email distribution lists - * - Security & Permissions - - Admins Only - - Configure Permissions (i.e. who can create calls) for the department and view Audit Logs - * - Subscription & Billing - - Admins Only - - Manage your departments subscription and billing information. (Hosted Version only) - * - Orders - - Everyone - - Your Resource Order Requests and those from other departments (i.e. Mutual Aid) - * - Links - - Everyone - - Links with other Departments in Resgrid (i.e. sister stations) - * - Notifications - - Everyone - - Create and Manage your custom notifications (i.e. be notified if a unit goes out of service) - * - Commands - - Everyone - - Create and manage Command definitions for call types (for the Commander app) - * - BigBoard - - Everyone - - A configurable dashboard where you can see your departments info at a glace on a TV\Monitor - -.. note:: Main administrative operations (both department and group) are only available to be performed via the website. The Mobile application (like Resgrid Responder) are intended for personnel specific functions, like setting ones own status or staffing level, or viewing call information. - -Department Settings -************** - -After you log into the website, click your department menu, it's the name of your Department in the upper right hand corner of the web application next to the Help menu. The menu will drop down and expose a list (detailed above), click on Department Settings. - -.. list-table:: Department Settings Options - :header-rows: 1 - - * - Setting - - Description - * - Department Name - - The name of your department or how you want it displayed in Resgrid - * - Time Zone - - The primary time zone your department is in. If your department is in multiple time zones, this should be the time zone of your headquarters or main office. - * - Use 24-Hour Time - - Do you want time displayed in 24 hour format for your department? For example 8:00PM is 2000 in 24 hour format. - * - Managing User - - This is the 'master' admin for the Department and that user cannot be removed from the system. - * - Disable Auto-Available - - By default the Resgrid system will ignore personnel statuses (instead just showing "Standing By") submitted after an hour when displaying their current status in a list. Checking this box will disable that feature. - -The Address section should be your home office, headquarters or main location. This will be used as the default center for most of the maps. Centers can also be adjusted in each map as well. - -Personnel Staffing Reset will add a Staffing level (selected in 'Reset Staffing Level To') for every user in the Department at the specified time. For example if you want to ensure all personnel staffings are up to date every day at 0800, you can have the system set a default staffing, like Unavailable, at 0200 so you know if there is a different staffing it's accurate as of that morning. - -Personnel Status Reset will add a Status (selected in 'Reset Status To') for every user in the Department at the specified time. For example if you want to ensure all personnel statuses are up to date every day at 0800, you can have the system set a default status, like Standing By, at 0200 so you know if there is a different status it's accurate as of that morning. - -Force Department Update will clear out all of the in memory cached information for your department. This is a scheduled job and can take up to 15 minutes to process. Resgrid caches a lot of static data, like groups and group names, personnel names, etc that don't change frequently and store them in memory for fast access. Forcing a Department Update will clear that cache and force it to be pulled from the Database. This will negatively impact your performance until all data is recached. - -.. warning:: Forcing a Department Update to clear the cache should only be used it some data, i.e. a group name or personnel name, is not updated for EVERYONE, not just one computer or phone. That device may have cached the output or call itself. This operation will slow down the system for ALL USERS until the cache is rebuilt. - -Creating Groups -************** - -After you log into the website, click your department menu, it's the name of your Department in the upper right hand corner of the web application next to the Help menu. The menu will drop down and expose a list (detailed above), click on Stations and Groups. - -Resgrid has 2 types of groups Station and Organizational. Station groups require a physical address and are the only group types allow to have Units under them. A Station group is intended to denote a physical location that personnel or units may be responding out of, or responding to (i.e. to pick up some equipment or staff). Organizational groups have no physical location and are intended to allow users to be grouped together. For example you can use Organizational groups like East or West denote which users are in those response areas. - -.. note:: Users can only be in 1 group at a time, but a user can be a part of many roles. Ideally you would use Groups to define something static like Stations, Districts, Response Areas, etc and use Personnel Roles to define more dynamic information like if a person is a Paramedic or HAZMAT Technician. - -On the Department Groups list you'll see columns calls "Dispatch Email" and "Message Email". These are unique email addresses for those groups. The "Dispatch Email" address will create a call for that specific group and dispatch all personnel and units under that group. The "Message Email" address will create a in-system message in Resgrid to all personnel in that group. - -.. _organizational_groups: - -Organizational Groups -======================= - -Organizational groups are intended to organize groups of users. This group type can only have personnel assigned to it. - - -.. _station_groups: - -Station Groups -======================= - -Station groups can have personnel and units assigned to them and must have a physical address. This address could be a building or open staging area. - - -.. _add_group_page: - -Add Group Page -======================= - -.. list-table:: Creating a Group Options - :header-rows: 1 - - * - Setting - - Description - * - Group Type - - The type of group you want to create Organizational or Station - * - Group Name - - The name of your group - * - Parent Group - - You can have a group under a group, if you want this newly created group to show up underneath another group select the parent group here - * - Address - - Optional. If you have selected a Station group you need to supply a physical address for this group. An example would be a Fire Station, Staging Area, Ambulance Bay, etc. - * - Group Admins - - These are the administrators for this group. Group Admins can modify personnel in the group, for example updating their profile - * - Group Users - - Personnel that are in the group - -.. note:: You do not have to add personnel here, you can leave both Group Admins and Group Users blank and add users to the groups when you add the users or edit their profiles. For very large groups the list of personnel in the group will be too big to maintain here and is best maintained at the personnel profile level. - -Personnel Roles -************** - -After you log into the website, click the Personnel module from the left hand module list, it's under your name, Home and Calls buttons. This will take you the Personnel Section, there is a blue button on the right hand side of the screen, below the top bar with "Help" and "Log out" named "Manage Roles", this is where you can administer your Personnel Roles. - -Personnel can be in any number of Roles; roles are used to define attributes of your personnel, for example it could be an MOS or Job Function like Firefighter, Paramedic, Officer, etc or could be ranks or roles within your organization like Manager, Supervisor or Coordinator. Roles allow you to filter personnel when making calls, sending messages, assigning trainings and the like. For example say you have a training for your EMT's, you can have roles for your EMT's, AEMT's and Paramedics and assign the training just to them. - -Once you create the role, you can click "Edit" in the roles list to assign personnel to that role. Additionally the recommended way to assign personnel to roles is via the personnel profile view (accessible by editing the person from the Personnel list, or clicking their name on the dashboard). When adding a person to the system you can assign roles at that time as well. - -Adding Personnel -************** - -After you log into the website, click the Personnel module from the left hand module list, it's under your name, Home and Calls buttons. This will take you the Personnel Section, from here you can add personnel in 2 ways, manually or via an invite. - -Add a Single Person -======================= - -Clicking the turquoise "Add Person" button on the Personnel list page will allow you to add one user one by one. This is the preferred way to add personnel into the system by Department or Group Admins as it allows you to specify all the information for the user at the time of entry. - -.. list-table:: Creating a Group Options - :header-rows: 1 - - * - Setting - - Description - * - UserName - - The Username that the user will use to log into the system with - * - Password - - The password that the user will use to log into the system - * - Confirm Password - - Ensure the password is correct - * - ID - - Optional, the Identification number for the person. This could be a badge or employee number. - * - First Name - - The users First Name - * - Last Name - - The Users Last Name - * - Email Address - - Email address for the user, this email address is used for communication and is the "Forgot Account" email address. - * - Group - - The Group (Station or Organization) that the user should be placed under - * - Is Group Admin - - Do you want this user to be a Group Admin for the group they are assigned - * - Roles - - Personnel Roles that are applicable for the user - * - Mobile Number - - The mobile\cell phone number for the user - * - Mobile Carrier - - The mobile carrier for the users mobile\cell phone. This is required as Resgrid will route text messages directly to the carrier for the cell phone. - * - Call Options - - How do you want this user to be communicated to for Dispatch\Calls - * - Message Options - - How do you want this user to be communicated to for Messages - * - Notification Options - - How do you want this user to be communicated to for Notifications - * - Notify User - - Do you want Resgrid to email the user with their account information - -Send Out Invites -======================= - -On the Personnel list page you can click the green "Manage Invites" button to invite personnel by sending out an invite email to their email address. On this page you will see the email address you have sent invites too and when you sent that invite. Also on this list you can see if the user has completed the invite and resend the invite if the user has not completed it. - -To send invites to email addresses you can enter them in, one or many at a time, in the "Email Addresses" textarea inside the "Send Invites" card. Email addresses in this textarea need to be comma "," separated. For example "user1@yourcompany.local, user2@yourcompany.local, user3@yourcompany.local" without the double quotes. Once your list is populated you can click the blue "Send Invites" button. - -.. note:: It's recommended to send 20 invites or less at a single time to ensure the POST request length is not too large which could cause failures for browsers with a poor connection. If an email address supplied in the textarea doesn't appear in the list there was an error processing that email address and an invite was NOT sent to that user. - -Units Metadata -************** - -Adding Units -************** \ No newline at end of file diff --git a/docs/source/system/index.rst b/docs/source/system/index.rst deleted file mode 100644 index b28bc1ae2..000000000 --- a/docs/source/system/index.rst +++ /dev/null @@ -1,3 +0,0 @@ -####### -System -####### \ No newline at end of file diff --git a/docs/source/updating/index.rst b/docs/source/updating/index.rst deleted file mode 100644 index 2f0a074e8..000000000 --- a/docs/source/updating/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -####### -Updating -####### - -This section covers how to update, or upgrade, and existing installation of Resgrid. - -.. important:: You should take backups of your Resgrid installation and database's before performing an upgrade. We recommend storing those backups off the server you are working on, for example on another server or storage location. - -.. warning:: You cannot reverse an upgrade once it's been performed! - -Getting the new version -**************************** - -Download the latest stable release from the `Resgrid Core Github Releases `_ page. Pre-release or Beta versions will also be available for download but should not be used in production systems. Instead should only be used for testing or evaluating new features or functionality. - -Extract the zip file to a temporary location. From e534b848db6fd1aeea7b7019e5ba198d4bb02e5e Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sat, 15 Aug 2026 09:13:50 -0700 Subject: [PATCH 3/3] RG-T131 PR#464 fixes --- Core/Resgrid.Services/DepartmentsService.cs | 10 ++++++ .../Logic/SecurityLogic.cs | 33 ++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Core/Resgrid.Services/DepartmentsService.cs b/Core/Resgrid.Services/DepartmentsService.cs index 5172c749d..8dd002864 100644 --- a/Core/Resgrid.Services/DepartmentsService.cs +++ b/Core/Resgrid.Services/DepartmentsService.cs @@ -254,6 +254,11 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen dm.IsDisabled = false; var saved = await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + + // The member row just changed its deleted/hidden/disabled flags and the department's user + // list gained a name back -- both are cached reads that would otherwise serve the old answer. + InvalidateDepartmentUsersInCache(departmentId); + InvalidateDepartmentMemberInCache(userId, departmentId); SendMembershipVisibilityRefresh(departmentId); return saved; @@ -286,6 +291,11 @@ public async Task GetUserIdForDeletedUserInDepartmentAsync(int departmen await _limitsService.InvalidateDepartmentsEntityLimitsCache(departmentId); var saved = await _departmentMembersRepository.SaveOrUpdateAsync(dm, cancellationToken); + + // A lookup for this user/department pair may already have been cached as "not a member", + // and the department's user list does not have the new name on it yet. + InvalidateDepartmentUsersInCache(departmentId); + InvalidateDepartmentMemberInCache(userId, departmentId); SendMembershipVisibilityRefresh(departmentId); return saved; diff --git a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs index d7f75133c..31edeffbf 100644 --- a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs +++ b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs @@ -659,22 +659,39 @@ async Task getWhoCanViewUserLocations() return new Tuple(success, result); } + /// + /// Every department is still attempted when one fails -- a single department with unusable + /// permission data must not cost the rest of the system its rebuild. The result reports whether + /// anything failed, so a caller can tell a clean sweep from a partial one; without that the + /// sweep looks successful no matter how many matrices were left stale. + /// public async Task> UpdatedCachedSecurityForAllDepartments() { - bool success = true; - string result = String.Empty; - var departments = await _departmentsService.GetAllAsync(); + var failures = new List(); + + async Task rebuild(int departmentId, SecurityCacheTypes type) + { + var processed = await Process(new SecurityQueueItem() { DepartmentId = departmentId, Type = type }); + + if (processed == null || !processed.Item1) + failures.Add($"{departmentId}/{type}: {processed?.Item2}".Trim()); + } foreach (var department in departments) { - await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewUnits }); - await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewUnitLocations }); - await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewPersonnel }); - await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewPersonnelLocations }); + await rebuild(department.DepartmentId, SecurityCacheTypes.WhoCanViewUnits); + await rebuild(department.DepartmentId, SecurityCacheTypes.WhoCanViewUnitLocations); + await rebuild(department.DepartmentId, SecurityCacheTypes.WhoCanViewPersonnel); + await rebuild(department.DepartmentId, SecurityCacheTypes.WhoCanViewPersonnelLocations); } - return new Tuple(success, result); + if (!failures.Any()) + return new Tuple(true, String.Empty); + + // Capped: a system-wide outage would otherwise build a result string per department. + return new Tuple(false, + $"{failures.Count} security matrix rebuild(s) failed: {String.Join("; ", failures.Take(10))}"); } } }