diff --git a/api/Vote.Monitor.sln b/api/Vote.Monitor.sln index 7393db588..a0147dee7 100644 --- a/api/Vote.Monitor.sln +++ b/api/Vote.Monitor.sln @@ -164,6 +164,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.IncidentReports.Att EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Citizen.Notifications", "src\Feature.Citizen.Notifications\Feature.Citizen.Notifications.csproj", "{A43F29F9-5765-4143-AB9A-C3ECFCDD402F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Coalitions", "src\Feature.Coalitions\Feature.Coalitions.csproj", "{5F573792-C095-44F5-8DDD-05257CA41C9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -470,6 +472,10 @@ Global {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Release|Any CPU.Build.0 = Release|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -550,6 +556,7 @@ Global {0567FED6-2464-462D-BE59-3A4FA50BF438} = {3441EE1D-E3C6-45BE-A020-553816015081} {DF354318-6856-4925-96A0-0C458E530091} = {3441EE1D-E3C6-45BE-A020-553816015081} {A43F29F9-5765-4143-AB9A-C3ECFCDD402F} = {17945B3C-5A4C-4279-8022-65ABC606A510} + {5F573792-C095-44F5-8DDD-05257CA41C9E} = {17945B3C-5A4C-4279-8022-65ABC606A510} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {50C20C9F-F2AF-45D8-994A-06661772B31C} diff --git a/api/src/Authorization.Policies/PoliciesInstaller.cs b/api/src/Authorization.Policies/PoliciesInstaller.cs index 7eb696ce6..52f9da5ed 100644 --- a/api/src/Authorization.Policies/PoliciesInstaller.cs +++ b/api/src/Authorization.Policies/PoliciesInstaller.cs @@ -15,6 +15,7 @@ public static IServiceCollection AddAuthorizationPolicies(this IServiceCollectio services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddAuthorization(options => { diff --git a/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs index 8171bf3b1..e242a1376 100644 --- a/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs +++ b/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs @@ -36,8 +36,7 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext return; } - if (result.ElectionRoundStatus == ElectionRoundStatus.Archived - || result.NgoStatus == NgoStatus.Deactivated + if (result.NgoStatus == NgoStatus.Deactivated || result.MonitoringNgoStatus == MonitoringNgoStatus.Suspended) { context.Fail(); @@ -46,4 +45,4 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context.Succeed(requirement); } -} \ No newline at end of file +} diff --git a/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs new file mode 100644 index 000000000..63e1731fd --- /dev/null +++ b/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs @@ -0,0 +1,49 @@ +using Authorization.Policies.Requirements; +using Authorization.Policies.Specifications; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.RequirementHandlers; + +internal class CoalitionLeaderAuthorizationHandler( + ICurrentUserProvider currentUserProvider, + ICurrentUserRoleProvider currentUserRoleProvider, + IReadRepository coalitionRepository) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + CoalitionLeaderRequirement requirement) + { + if (!currentUserRoleProvider.IsNgoAdmin()) + { + context.Fail(); + return; + } + + var ngoId = currentUserProvider.GetNgoId(); + if (ngoId is null) + { + context.Fail(); + return; + } + + var coalitionLeaderSpecification = new GetCoalitionLeaderDetailsSpecification(requirement.ElectionRoundId, requirement.CoalitionId, ngoId.Value); + var result = await coalitionRepository.FirstOrDefaultAsync(coalitionLeaderSpecification); + + if (result is null) + { + context.Fail(); + return; + } + + if (result.ElectionRoundStatus == ElectionRoundStatus.Archived + || result.NgoStatus == NgoStatus.Deactivated + || result.MonitoringNgoStatus == MonitoringNgoStatus.Suspended) + { + context.Fail(); + return; + } + + context.Succeed(requirement); + } +} diff --git a/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminOrObserverAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminOrObserverAuthorizationHandler.cs index 4dea606cf..4d5e09c7f 100644 --- a/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminOrObserverAuthorizationHandler.cs +++ b/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminOrObserverAuthorizationHandler.cs @@ -51,8 +51,7 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext if (ngoAdminResult is not null) { - if (ngoAdminResult.ElectionRoundStatus == ElectionRoundStatus.Archived || - ngoAdminResult.NgoStatus == NgoStatus.Deactivated || + if (ngoAdminResult.NgoStatus == NgoStatus.Deactivated || ngoAdminResult.MonitoringNgoStatus == MonitoringNgoStatus.Suspended) { context.Fail(); @@ -66,4 +65,4 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context.Fail(); } -} \ No newline at end of file +} diff --git a/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs b/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs new file mode 100644 index 000000000..b36f03d98 --- /dev/null +++ b/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs @@ -0,0 +1,7 @@ +namespace Authorization.Policies.Requirements; + +public class CoalitionLeaderRequirement(Guid electionRoundId, Guid coalitionId) : IAuthorizationRequirement +{ + public Guid ElectionRoundId { get; } = electionRoundId; + public Guid CoalitionId { get; } = coalitionId; +} diff --git a/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs b/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs new file mode 100644 index 000000000..9bf1c0586 --- /dev/null +++ b/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs @@ -0,0 +1,13 @@ +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.Specifications; + +internal class CoalitionLeaderView +{ + public required Guid ElectionRoundId { get; set; } + public required ElectionRoundStatus ElectionRoundStatus { get; set; } + public required Guid NgoId { get; set; } + public required NgoStatus NgoStatus { get; set; } + public required Guid MonitoringNgoId { get; set; } + public required MonitoringNgoStatus MonitoringNgoStatus { get; set; } +} diff --git a/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs b/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs new file mode 100644 index 000000000..323aeb7e6 --- /dev/null +++ b/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs @@ -0,0 +1,26 @@ +using Ardalis.Specification; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Authorization.Policies.Specifications; + +internal sealed class GetCoalitionLeaderDetailsSpecification : SingleResultSpecification +{ + public GetCoalitionLeaderDetailsSpecification(Guid electionRoundId, Guid coalitionId, Guid ngoId) + { + Query + .Include(x => x.ElectionRound) + .Include(x => x.Leader) + .Where(x => x.Leader.NgoId == ngoId && x.ElectionRoundId == electionRoundId && x.Id == coalitionId) + .AsNoTracking(); + + Query.Select(x => new CoalitionLeaderView + { + ElectionRoundId = x.ElectionRoundId, + ElectionRoundStatus = x.ElectionRound.Status, + NgoId = x.Leader.NgoId, + NgoStatus = x.Leader.Ngo.Status, + MonitoringNgoId = x.Id, + MonitoringNgoStatus = x.Leader.Status + }); + } +} diff --git a/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs b/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs index 01c1e6c24..99819c7f4 100644 --- a/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs +++ b/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs @@ -22,4 +22,4 @@ public GetMonitoringNgoSpecification(Guid electionRoundId, Guid ngoId) MonitoringNgoStatus = x.Status }); } -} \ No newline at end of file +} diff --git a/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs index 51afff2c3..359b08a10 100644 --- a/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs @@ -9,7 +9,10 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid? ngoId, Gui Query .Include(x => x.MonitoringNgo) .ThenInclude(x => x.ElectionRound) - .Where(x => x.ObserverId == observerId && x.ElectionRoundId == electionRoundId && x.MonitoringNgo.NgoId == ngoId); + .Where(x => x.ObserverId == observerId + && x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.MonitoringNgo.ElectionRoundId == electionRoundId); Query.Select(x => new MonitoringObserverView { @@ -31,7 +34,7 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) Query .Include(x => x.MonitoringNgo) .ThenInclude(x => x.ElectionRound) - .Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId) + .Where(x => x.ObserverId == observerId && x.ElectionRoundId == electionRoundId && x.MonitoringNgo.ElectionRoundId == electionRoundId) .AsNoTracking(); Query.Select(x => new MonitoringObserverView diff --git a/api/src/Feature.Attachments/Create/Endpoint.cs b/api/src/Feature.Attachments/Create/Endpoint.cs index 305e31364..0cdb651e9 100644 --- a/api/src/Feature.Attachments/Create/Endpoint.cs +++ b/api/src/Feature.Attachments/Create/Endpoint.cs @@ -80,7 +80,7 @@ public override async Task, NotFound, BadRequest, BadRequest>, NotFound, BadReque ElectionRoundId = attachment.ElectionRoundId, PollingStationId = attachment.PollingStationId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs b/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs index 2dfc917ef..1236291a1 100644 --- a/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs +++ b/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs @@ -7,6 +7,8 @@ public sealed class GetAttachmentByIdSpecification : SingleResultSpecification x.ElectionRoundId == electionRoundId - && x.MonitoringObserver.ObserverId == observerId && x.Id == attachmentId); + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId + && x.Id == attachmentId); } } diff --git a/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs b/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs index 79edec906..b75d4f027 100644 --- a/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs +++ b/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs @@ -10,6 +10,8 @@ public GetObserverAttachmentsSpecification(Guid electionRoundId, Guid pollingSta .Where(x => x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.IsDeleted == false && x.IsCompleted == true); diff --git a/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs index 445308fae..be3c11711 100644 --- a/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs +++ b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs @@ -94,7 +94,7 @@ public override async Task, NotFound>> ExecuteAsyn return TypedResults.Ok(citizenGuideModel with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }); } diff --git a/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs b/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs index 94f2cc926..1555a2598 100644 --- a/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs @@ -33,7 +33,7 @@ public override async Task, NotFound>> Exec CN."Title", CN."Body", CN."CreatedOn" "SentAt", - U."FirstName" || ' ' || U."LastName" "Sender" + U."DisplayName" "Sender" FROM "CitizenNotifications" CN INNER JOIN "NgoAdmins" NA ON CN."SenderId" = NA."Id" @@ -49,7 +49,7 @@ public override async Task, NotFound>> Exec { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - id = req.Id, + id = req.Id }; CitizenNotificationModel? notification; @@ -61,4 +61,4 @@ public override async Task, NotFound>> Exec return notification == null ? TypedResults.NotFound() : TypedResults.Ok(notification); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs b/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs index a65585203..4b3a2b03b 100644 --- a/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs @@ -43,7 +43,7 @@ ORDER BY var queryArgs = new { - electionRoundId = req.ElectionRoundId, + electionRoundId = req.ElectionRoundId }; string? ngoName; diff --git a/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs b/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs index 133339a97..ffd3e359c 100644 --- a/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs @@ -45,7 +45,7 @@ public override async Task>, CN."Title", CN."Body", CN."CreatedOn" AS "SentAt", - U."FirstName" || ' ' || U."LastName" AS "Sender" + U."DisplayName" AS "Sender" FROM "CitizenNotifications" CN INNER JOIN "ElectionRounds" ER ON CN."ElectionRoundId" = ER."Id" @@ -64,7 +64,7 @@ OFFSET @offset ROWS electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), - pageSize = req.PageSize, + pageSize = req.PageSize }; int totalRowCount; @@ -80,4 +80,4 @@ OFFSET @offset ROWS return TypedResults.Ok( new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs b/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs index b81ba0276..2bf2e4090 100644 --- a/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs +++ b/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs @@ -45,7 +45,7 @@ await repository.FirstOrDefaultAsync( ElectionRoundId = attachment.ElectionRoundId, CitizenReportId = attachment.CitizenReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }); } } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs b/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs index 49517317f..0f311aa09 100644 --- a/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs +++ b/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs @@ -43,7 +43,7 @@ public override async Task ElectionRoundId = attachment.ElectionRoundId, CitizenReportId = attachment.CitizenReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs b/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs index 49af2efcf..da2d7a10f 100644 --- a/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs +++ b/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs @@ -9,8 +9,9 @@ public ListCitizenReportAttachmentsSpecification(Guid electionRoundId, Guid citi Query .Where(x => x.ElectionRoundId == electionRoundId && x.FormId == formId + && x.Form.ElectionRoundId == electionRoundId && x.CitizenReportId == citizenReportId && !x.IsDeleted && x.IsCompleted); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs b/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs index a42c3a6ab..1da4c4e89 100644 --- a/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs +++ b/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs @@ -6,8 +6,11 @@ public sealed class ListNotesSpecification : Specification x.ElectionRoundId == electionRoundId && x.FormId == citizenReportId); - + Query.Where(x => + x.ElectionRoundId == electionRoundId && x.FormId == citizenReportId && + x.Form.ElectionRoundId == electionRoundId + ); + Query.Select(note => CitizenReportNoteModel.FromEntity(note)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetById/Endpoint.cs b/api/src/Feature.CitizenReports/GetById/Endpoint.cs index 419bf3198..3894a2f59 100644 --- a/api/src/Feature.CitizenReports/GetById/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetById/Endpoint.cs @@ -1,7 +1,8 @@ -using Feature.CitizenReports.Models; -using Vote.Monitor.Answer.Module.Mappers; +using Vote.Monitor.Answer.Module.Mappers; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Form.Module.Mappers; +using AttachmentModel = Feature.CitizenReports.Models.AttachmentModel; +using NoteModel = Feature.CitizenReports.Models.NoteModel; namespace Feature.CitizenReports.GetById; @@ -37,6 +38,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Id == req.CitizenReportId) .AsSplitQuery() .FirstOrDefaultAsync(ct); @@ -64,7 +66,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -89,9 +91,9 @@ public override async Task, NotFound>> ExecuteAsync(Request LocationLevel2 = citizenReport.Location.Level2, LocationLevel3 = citizenReport.Location.Level3, LocationLevel4 = citizenReport.Location.Level4, - LocationLevel5 = citizenReport.Location.Level5, + LocationLevel5 = citizenReport.Location.Level5 }; return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetById/Response.cs b/api/src/Feature.CitizenReports/GetById/Response.cs index 3a2669bf3..bd39027dd 100644 --- a/api/src/Feature.CitizenReports/GetById/Response.cs +++ b/api/src/Feature.CitizenReports/GetById/Response.cs @@ -1,9 +1,10 @@ using System.Text.Json.Serialization; using Ardalis.SmartEnum.SystemTextJson; -using Feature.CitizenReports.Models; using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; using Vote.Monitor.Form.Module.Models; +using AttachmentModel = Feature.CitizenReports.Models.AttachmentModel; +using NoteModel = Feature.CitizenReports.Models.NoteModel; namespace Feature.CitizenReports.GetById; @@ -30,4 +31,4 @@ public class Response public BaseAnswerModel[] Answers { get; init; } = []; public NoteModel[] Notes { get; init; } = []; public AttachmentModel[] Attachments { get; init; } = []; -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs b/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs index 4d0c939d2..320aaa508 100644 --- a/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs @@ -50,7 +50,7 @@ SELECT MIN(COALESCE(CR."LastModifiedOn", CR."CreatedOn")) AS "FirstSubmissionTim var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -66,7 +66,7 @@ SELECT MIN(COALESCE(CR."LastModifiedOn", CR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs index 5ffd4d149..215db6d59 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs @@ -1,7 +1,8 @@ -using Feature.CitizenReports.Models; -using Feature.CitizenReports.Requests; +using Feature.CitizenReports.Requests; using Vote.Monitor.Answer.Module.Aggregators; using Vote.Monitor.Core.Services.FileStorage.Contracts; +using AttachmentModel = Feature.CitizenReports.Models.AttachmentModel; +using NoteModel = Feature.CitizenReports.Models.NoteModel; namespace Feature.CitizenReports.GetSubmissionsAggregated; @@ -37,6 +38,7 @@ public override async Task, NotFound>> ExecuteAsync(Citizen .Forms .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Id == req.FormId) .AsNoTracking() .FirstOrDefaultAsync(ct); @@ -58,6 +60,8 @@ private async Task, NotFound>> AggregateCitizenReportsAsync .Include(x => x.Attachments) .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId) .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || EF.Functions.ILike(x.Location.Level1, req.Level1Filter)) @@ -131,8 +135,8 @@ private async Task, NotFound>> AggregateCitizenReportsAsync Level5Filter = req.Level5Filter, HasFlaggedAnswers = req.HasFlaggedAnswers, QuestionsAnswered = req.QuestionsAnswered, - FollowUpStatus = req.FollowUpStatus, + FollowUpStatus = req.FollowUpStatus } }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Response.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Response.cs index 70b405b95..20e063db6 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Response.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Response.cs @@ -1,6 +1,7 @@ -using Feature.CitizenReports.Models; -using Vote.Monitor.Answer.Module.Aggregators; +using Vote.Monitor.Answer.Module.Aggregators; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; +using AttachmentModel = Feature.CitizenReports.Models.AttachmentModel; +using NoteModel = Feature.CitizenReports.Models.NoteModel; namespace Feature.CitizenReports.GetSubmissionsAggregated; @@ -32,4 +33,4 @@ public class SubmissionsFilterModel public bool? HasNotes { get; set; } public bool? HasAttachments { get; set; } public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs b/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs index 67f57649e..26f6f6226 100644 --- a/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs @@ -229,7 +229,7 @@ OFFSET @offset ROWS hasAttachments = req.HasAttachments, hasNotes = req.HasNotes, questionsAnswered = req.QuestionsAnswered?.ToString(), - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; diff --git a/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs b/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs index 4eaa0e342..c5fca31dd 100644 --- a/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs +++ b/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs @@ -23,6 +23,6 @@ public class CitizenReportModel Answers = entity.Answers .Select(AnswerMapper.ToModel) .ToList(), - FollowUpStatus = entity.FollowUpStatus, + FollowUpStatus = entity.FollowUpStatus }; } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs b/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs index 7270d217b..09d163797 100644 --- a/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs @@ -16,10 +16,11 @@ public override async Task> ExecuteAsync(Request re { await context.CitizenReports .Where(x => x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs b/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs new file mode 100644 index 000000000..10dff27e7 --- /dev/null +++ b/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs @@ -0,0 +1,16 @@ +using Dapper; +using Feature.NgoCoalitions.Models; +using Microsoft.Extensions.DependencyInjection; +using Vote.Monitor.Core.Converters; + +namespace Feature.NgoCoalitions; + +public static class CoalitionsFeatureInstaller +{ + public static IServiceCollection AddCoalitionsFeature(this IServiceCollection services) + { + SqlMapper.AddTypeHandler(typeof(CoalitionMember[]), new JsonToObjectConverter()); + + return services; + } +} diff --git a/api/src/Feature.Coalitions/Create/Endpoint.cs b/api/src/Feature.Coalitions/Create/Endpoint.cs new file mode 100644 index 000000000..263037f76 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Endpoint.cs @@ -0,0 +1,64 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Create; + +public class Endpoint(VoteMonitorContext context) : Endpoint, Conflict, NotFound>> +{ + public override void Configure() + { + Post("/api/election-rounds/{electionRoundId}/coalitions"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, Conflict, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) + { + var electionRound = await context.ElectionRounds.FirstOrDefaultAsync(x => x.Id == req.ElectionRoundId, ct); + if (electionRound == null) + { + return TypedResults.NotFound(); + } + + var coalitionWithSameNameExists = await context + .Coalitions + .AnyAsync(x => EF.Functions.ILike(x.Name, req.CoalitionName) && x.ElectionRoundId == req.ElectionRoundId, + ct); + + if (coalitionWithSameNameExists) + { + return TypedResults.Conflict(); + } + + var ngoIds = req.NgoMembersIds.Union([req.LeaderId]).Distinct().ToList(); + var ngos = await context.Ngos.Where(x => ngoIds.Contains(x.Id)).ToListAsync(ct); + + var monitoringNgos = await context + .MonitoringNgos + .Include(x => x.Ngo) + .Where(x => ngoIds.Contains(x.NgoId) && x.ElectionRoundId == req.ElectionRoundId) + .ToListAsync(ct); + + var ngosToAddAsMonitoringNgos = ngos.Where(ngo => monitoringNgos.All(x => x.NgoId != ngo.Id)).ToList(); + + foreach (var ngo in ngosToAddAsMonitoringNgos) + { + var monitoringNgo = electionRound.AddMonitoringNgo(ngo); + monitoringNgos.Add(monitoringNgo); + context.MonitoringNgos.Add(monitoringNgo); + } + + var coalition = Coalition.Create(electionRound, + req.CoalitionName, + monitoringNgos.First(x => x.NgoId == req.LeaderId), + monitoringNgos); + + context.Coalitions.Add(coalition); + + await context.SaveChangesAsync(ct); + + return TypedResults.Ok(CoalitionModel.FromEntity(coalition)); + } +} diff --git a/api/src/Feature.Coalitions/Create/Request.cs b/api/src/Feature.Coalitions/Create/Request.cs new file mode 100644 index 000000000..1bf6d5036 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Request.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.Create; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public string CoalitionName { get; set; } + public Guid LeaderId { get; set; } + public Guid[] NgoMembersIds { get; set; } +} diff --git a/api/src/Feature.Coalitions/Create/Validator.cs b/api/src/Feature.Coalitions/Create/Validator.cs new file mode 100644 index 000000000..e0b0e1aa5 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Validator.cs @@ -0,0 +1,12 @@ +namespace Feature.NgoCoalitions.Create; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.LeaderId).NotEmpty(); + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + RuleFor(x => x.CoalitionName).NotEmpty().MaximumLength(256); + } +} diff --git a/api/src/Feature.Coalitions/Delete/Endpoint.cs b/api/src/Feature.Coalitions/Delete/Endpoint.cs new file mode 100644 index 000000000..06c3b3de6 --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Endpoint.cs @@ -0,0 +1,31 @@ +namespace Feature.NgoCoalitions.Delete; + +public class Endpoint(VoteMonitorContext context) + : Endpoint> +{ + public override void Configure() + { + Delete("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task> ExecuteAsync(Request req, CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .Where(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId) + .FirstOrDefaultAsync(ct); + + if (coalition == null) + { + return TypedResults.NotFound(); + } + + context.Coalitions.Remove(coalition); + await context.SaveChangesAsync(ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Coalitions/Delete/Request.cs b/api/src/Feature.Coalitions/Delete/Request.cs new file mode 100644 index 000000000..832909b04 --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.NgoCoalitions.Delete; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } +} diff --git a/api/src/Feature.Coalitions/Delete/Validator.cs b/api/src/Feature.Coalitions/Delete/Validator.cs new file mode 100644 index 000000000..82909d1fd --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Validator.cs @@ -0,0 +1,10 @@ +namespace Feature.NgoCoalitions.Delete; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/EnableTesting.cs b/api/src/Feature.Coalitions/EnableTesting.cs new file mode 100644 index 000000000..237ecb7ec --- /dev/null +++ b/api/src/Feature.Coalitions/EnableTesting.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Feature.Coalitions.UnitTests")] +[assembly: InternalsVisibleTo("Vote.Monitor.Api.IntegrationTests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/api/src/Feature.Coalitions/Feature.Coalitions.csproj b/api/src/Feature.Coalitions/Feature.Coalitions.csproj new file mode 100644 index 000000000..8c9f1c863 --- /dev/null +++ b/api/src/Feature.Coalitions/Feature.Coalitions.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + Feature.NgoCoalitions + + + + + + + + + + + + + + + diff --git a/api/src/Feature.Coalitions/FormAccess/Endpoint.cs b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs new file mode 100644 index 000000000..777ce2802 --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs @@ -0,0 +1,84 @@ +using Authorization.Policies.Requirements; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.FormAccess; + +public class Endpoint( + VoteMonitorContext context, + IAuthorizationService authorizationService) + : Endpoint> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task> ExecuteAsync(Request req, + CancellationToken ct) + { + var authorizationResult = await authorizationService.AuthorizeAsync(User, + new CoalitionLeaderRequirement(req.ElectionRoundId, req.CoalitionId)); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var form = await context.Forms + .Where(f => f.ElectionRoundId == req.ElectionRoundId + && f.Id == req.FormId + && f.ElectionRoundId == req.ElectionRoundId + && f.MonitoringNgo.NgoId == req.NgoId + && f.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) + .Select(f => new { f.Id, f.Status }) + .AsNoTracking() + .FirstOrDefaultAsync(ct); + + if (form is null) + { + return TypedResults.NotFound(); + } + + var coalition = await context.Coalitions + .Where(c => c.ElectionRoundId == req.ElectionRoundId && c.Id == req.CoalitionId) + .Include(x => x.FormAccess.Where(fa => fa.FormId == form.Id && fa.Form.ElectionRoundId == req.ElectionRoundId)) + .Include(x => x.Memberships) + .FirstOrDefaultAsync(ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + var requestNgoMembers = req.NgoMembersIds.Distinct().ToList(); + var coalitionMembersIds = coalition + .Memberships + .Select(x => x.MonitoringNgoId) + .Distinct() + .ToList(); + + var coalitionMonitoringNgoIds = await context.MonitoringNgos + .Where(x => x.ElectionRoundId == req.ElectionRoundId + && requestNgoMembers.Contains(x.NgoId) + && coalitionMembersIds.Contains(x.Id)) + .Select(x => x.Id) + .ToListAsync(ct); + + var ngosGainedFormAccess = + coalitionMonitoringNgoIds.Where(x => coalition.FormAccess.All(fa => fa.MonitoringNgoId != x)) + .Select(id => CoalitionFormAccess.Create(coalition.Id, id, req.FormId)) + .ToList(); + + if (ngosGainedFormAccess.Any()) + { + await context.CoalitionFormAccess.AddRangeAsync(ngosGainedFormAccess, ct); + } + + await context.SaveChangesAsync(ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Coalitions/FormAccess/Request.cs b/api/src/Feature.Coalitions/FormAccess/Request.cs new file mode 100644 index 000000000..314ce218b --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Request.cs @@ -0,0 +1,13 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.NgoCoalitions.FormAccess; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + public Guid CoalitionId { get; set; } + public Guid FormId { get; set; } + public Guid[] NgoMembersIds { get; set; } = []; +} diff --git a/api/src/Feature.Coalitions/FormAccess/Validator.cs b/api/src/Feature.Coalitions/FormAccess/Validator.cs new file mode 100644 index 000000000..be989da77 --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Validator.cs @@ -0,0 +1,12 @@ +namespace Feature.NgoCoalitions.FormAccess; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + RuleFor(x => x.FormId).NotEmpty(); + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/Get/Endpoint.cs b/api/src/Feature.Coalitions/Get/Endpoint.cs new file mode 100644 index 000000000..61cd37c64 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Endpoint.cs @@ -0,0 +1,34 @@ +using Feature.NgoCoalitions.Models; + +namespace Feature.NgoCoalitions.Get; + +public class Endpoint(VoteMonitorContext context) : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .ThenInclude(x => x.MonitoringNgo) + .ThenInclude(x => x.Ngo) + .Include(x => x.Leader) + .ThenInclude(x => x.Ngo) + .Where(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId) + .Select(x => CoalitionModel.FromEntity(x)) + .FirstOrDefaultAsync(ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(coalition); + } +} diff --git a/api/src/Feature.Coalitions/Get/Request.cs b/api/src/Feature.Coalitions/Get/Request.cs new file mode 100644 index 000000000..45f05fc36 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.NgoCoalitions.Get; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } +} diff --git a/api/src/Feature.Coalitions/Get/Validator.cs b/api/src/Feature.Coalitions/Get/Validator.cs new file mode 100644 index 000000000..be41754a9 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Validator.cs @@ -0,0 +1,10 @@ +namespace Feature.NgoCoalitions.Get; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/GetMy/Endpoint.cs b/api/src/Feature.Coalitions/GetMy/Endpoint.cs new file mode 100644 index 000000000..e84398b42 --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Endpoint.cs @@ -0,0 +1,50 @@ +using Authorization.Policies.Requirements; +using Feature.NgoCoalitions.Models; +using Microsoft.AspNetCore.Authorization; + +namespace Feature.NgoCoalitions.GetMy; + +public class Endpoint(IAuthorizationService authorizationService, VoteMonitorContext context) + : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions:my"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Summary(s => + { + s.Summary = "Gets coalition details for current ngo and selected election round"; + }); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var result = + await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); + if (!result.Succeeded) + { + return TypedResults.NotFound(); + } + + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .ThenInclude(x => x.MonitoringNgo) + .ThenInclude(x => x.Ngo) + .Include(x => x.Leader) + .ThenInclude(x => x.Ngo) + .Where(x => x.Memberships.Any(m => m.ElectionRoundId == req.ElectionRoundId + && m.MonitoringNgo.NgoId == req.NgoId + && m.MonitoringNgo.ElectionRoundId == req.ElectionRoundId)) + .Select(c => CoalitionModel.FromEntity(c)) + .FirstOrDefaultAsync(ct); + + if (coalition is null) + { + return TypedResults.Ok(new CoalitionModel { IsInCoalition = false }); + } + + return TypedResults.Ok(coalition); + } +} diff --git a/api/src/Feature.Coalitions/GetMy/Request.cs b/api/src/Feature.Coalitions/GetMy/Request.cs new file mode 100644 index 000000000..db26448a7 --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Request.cs @@ -0,0 +1,11 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.NgoCoalitions.GetMy; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } +} diff --git a/api/src/Feature.Coalitions/GetMy/Validator.cs b/api/src/Feature.Coalitions/GetMy/Validator.cs new file mode 100644 index 000000000..c79543d6e --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Validator.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.GetMy; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.NgoId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/GlobalUsings.cs b/api/src/Feature.Coalitions/GlobalUsings.cs new file mode 100644 index 000000000..ae4f6427c --- /dev/null +++ b/api/src/Feature.Coalitions/GlobalUsings.cs @@ -0,0 +1,11 @@ +// Global using directives + +global using Ardalis.Specification; +global using Authorization.Policies; +global using FastEndpoints; +global using FluentValidation; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Http.HttpResults; +global using Microsoft.EntityFrameworkCore; +global using Vote.Monitor.Domain; +global using Vote.Monitor.Domain.Repository; diff --git a/api/src/Feature.Coalitions/List/Endpoint.cs b/api/src/Feature.Coalitions/List/Endpoint.cs new file mode 100644 index 000000000..251663540 --- /dev/null +++ b/api/src/Feature.Coalitions/List/Endpoint.cs @@ -0,0 +1,141 @@ +using Dapper; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.ConnectionFactory; +using Vote.Monitor.Domain.Specifications; + +namespace Feature.NgoCoalitions.List; + +public class Endpoint(INpgsqlConnectionFactory dbConnectionFactory) + : Endpoint>, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task>, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) + { + var sql = """ + SELECT + COUNT(*) COUNT + FROM + "Coalitions" C + WHERE + ( + @searchText IS NULL + OR @searchText = '' + OR C."Id"::TEXT ILIKE @searchText + OR C."Name" ILIKE @searchText + ) + AND C."ElectionRoundId" = @electionRoundId; + + SELECT + C."Id", + C."Name", + C."Members", + C."LeaderId", + C."LeaderName" + FROM + ( + SELECT + C."Id", + C."Name", + N."Id" as "LeaderId", + N."Name" as "LeaderName", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT('Id', MN."NgoId", 'Name', N."Name") + ) + FROM + "CoalitionMemberships" CM + INNER JOIN "MonitoringNgos" MN ON CM."MonitoringNgoId" = MN."Id" + INNER JOIN "Ngos" N ON MN."NgoId" = N."Id" + WHERE + CM."ElectionRoundId" = @electionRoundId + AND CM."CoalitionId" = C."Id" + ), + '[]'::JSONB + ) AS "Members" + FROM + "Coalitions" C + INNER JOIN "MonitoringNgos" MN on C."LeaderId" = MN."Id" + INNER JOIN "Ngos" N on MN."NgoId" = N."Id" + WHERE + C."ElectionRoundId" = @electionRoundId + AND ( + @searchText IS NULL + OR @searchText = '' + OR C."Id"::TEXT ILIKE @searchText + OR C."Name" ILIKE @searchText + ) + ) C + ORDER BY + CASE + WHEN @sortExpression = 'Name ASC' THEN "Name" + END ASC, + CASE + WHEN @sortExpression = 'Name DESC' THEN "Name" + END DESC, + CASE + WHEN @sortExpression = 'NumberOfMembers ASC' THEN JSONB_ARRAY_LENGTH("Members") + END ASC, + CASE + WHEN @sortExpression = 'NumberOfMembers DESC' THEN JSONB_ARRAY_LENGTH("Members") + END DESC + OFFSET @offset ROWS + FETCH NEXT @pageSize ROWS ONLY; + """; + + var queryArgs = new + { + electionRoundId = req.ElectionRoundId, + offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), + pageSize = req.PageSize, + searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) + }; + + int totalRowCount = 0; + List entries; + + using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) + { + using var multi = await dbConnection.QueryMultipleAsync(sql, queryArgs); + totalRowCount = multi.Read().Single(); + entries = multi.Read().ToList(); + } + + return TypedResults.Ok( + new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); + } + + private static string GetSortExpression(string? sortColumnName, bool isAscendingSorting) + { + if (string.IsNullOrWhiteSpace(sortColumnName)) + { + return "Name ASC"; + } + + var sortOrder = isAscendingSorting ? "ASC" : "DESC"; + + if (string.Equals(sortColumnName, nameof(CoalitionModel.Name), StringComparison.InvariantCultureIgnoreCase)) + { + return $"{nameof(CoalitionModel.Name)} {sortOrder}"; + } + + if (string.Equals(sortColumnName, nameof(CoalitionModel.NumberOfMembers), + StringComparison.InvariantCultureIgnoreCase)) + { + return $"{nameof(CoalitionModel.NumberOfMembers)} {sortOrder}"; + } + + return "Name ASC"; + } +} diff --git a/api/src/Feature.Coalitions/List/Request.cs b/api/src/Feature.Coalitions/List/Request.cs new file mode 100644 index 000000000..c97e3fefa --- /dev/null +++ b/api/src/Feature.Coalitions/List/Request.cs @@ -0,0 +1,9 @@ +using Vote.Monitor.Core.Models; + +namespace Feature.NgoCoalitions.List; + +public class Request : BaseSortPaginatedRequest +{ + public Guid ElectionRoundId { get; set; } + [QueryParam] public string? SearchText { get; set; } +} diff --git a/api/src/Feature.Coalitions/List/Validator.cs b/api/src/Feature.Coalitions/List/Validator.cs new file mode 100644 index 000000000..f84974ec9 --- /dev/null +++ b/api/src/Feature.Coalitions/List/Validator.cs @@ -0,0 +1,14 @@ +namespace Feature.NgoCoalitions.List; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + + RuleFor(x => x.PageNumber) + .GreaterThanOrEqualTo(1); + + RuleFor(x => x.PageSize).InclusiveBetween(1, 100); + } +} diff --git a/api/src/Feature.Coalitions/Models/CoalitionMember.cs b/api/src/Feature.Coalitions/Models/CoalitionMember.cs new file mode 100644 index 000000000..216cc1741 --- /dev/null +++ b/api/src/Feature.Coalitions/Models/CoalitionMember.cs @@ -0,0 +1,14 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.NgoCoalitions.Models; + +public class CoalitionMember +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + + public static CoalitionMember FromEntity(MonitoringNgo ngo) + { + return new CoalitionMember { Id = ngo.NgoId, Name = ngo.Ngo.Name }; + } +} diff --git a/api/src/Feature.Coalitions/Models/CoalitionModel.cs b/api/src/Feature.Coalitions/Models/CoalitionModel.cs new file mode 100644 index 000000000..6353e1bd2 --- /dev/null +++ b/api/src/Feature.Coalitions/Models/CoalitionModel.cs @@ -0,0 +1,30 @@ +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Models; + +public class CoalitionModel +{ + public Guid Id { get; init; } + public bool IsInCoalition { get; set; } + public string Name { get; init; } = string.Empty; + public Guid LeaderId { get; set; } + public string LeaderName { get; set; } = string.Empty; + + public int NumberOfMembers => Members.Length; + public CoalitionMember[] Members { get; init; } = []; + + public static CoalitionModel? FromEntity(Coalition? coalition) + { + return coalition is null + ? null + : new CoalitionModel + { + Id = coalition.Id, + Name = coalition.Name, + LeaderId = coalition.Leader.NgoId, + LeaderName = coalition.Leader.Ngo.Name, + IsInCoalition = true, + Members = coalition.Memberships.Select(x => CoalitionMember.FromEntity(x.MonitoringNgo)).ToArray() + }; + } +} diff --git a/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs b/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs new file mode 100644 index 000000000..d63dbac68 --- /dev/null +++ b/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs @@ -0,0 +1,8 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.NgoCoalitions.Specifications; + +public class GetMonitoringNgos: Specification +{ + +} diff --git a/api/src/Feature.Coalitions/Update/Endpoint.cs b/api/src/Feature.Coalitions/Update/Endpoint.cs new file mode 100644 index 000000000..fa46d47c7 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Endpoint.cs @@ -0,0 +1,57 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Update; + +public class Endpoint(VoteMonitorContext context) + : Endpoint, NotFound, ProblemDetails>> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, NotFound, ProblemDetails>> ExecuteAsync(Request req, + CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .ThenInclude(x => x.MonitoringNgo) + .Include(x => x.Leader) + .ThenInclude(x => x.Ngo) + .Include(x => x.ElectionRound) + .FirstOrDefaultAsync(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId, ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + var ngoIds = req.NgoMembersIds.Distinct().ToList(); + var newMembers = await context.Ngos.Where(x => ngoIds.Contains(x.Id)).ToListAsync(ct); + + var coalitionMembers = await context + .MonitoringNgos + .Include(x => x.Ngo) + .Where(x => ngoIds.Contains(x.NgoId) && x.ElectionRoundId == req.ElectionRoundId) + .ToListAsync(ct); + + var ngosToAddAsMonitoringNgos = newMembers.Where(ngo => coalitionMembers.All(x => x.NgoId != ngo.Id)).ToList(); + + foreach (var ngo in ngosToAddAsMonitoringNgos) + { + var monitoringNgo = coalition.ElectionRound.AddMonitoringNgo(ngo); + coalitionMembers.Add(monitoringNgo); + context.MonitoringNgos.Add(monitoringNgo); + } + + coalition.Update(req.CoalitionName, + coalitionMembers.Select(x => CoalitionMembership.Create(req.ElectionRoundId, req.CoalitionId, x.Id))); + + await context.SaveChangesAsync(ct); + return TypedResults.Ok(CoalitionModel.FromEntity(coalition)); + } +} diff --git a/api/src/Feature.Coalitions/Update/Request.cs b/api/src/Feature.Coalitions/Update/Request.cs new file mode 100644 index 000000000..db6663648 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Request.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.Update; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } + public string CoalitionName { get; set; } + public Guid[] NgoMembersIds { get; set; } = []; +} diff --git a/api/src/Feature.Coalitions/Update/Validator.cs b/api/src/Feature.Coalitions/Update/Validator.cs new file mode 100644 index 000000000..42e3d25e1 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Validator.cs @@ -0,0 +1,19 @@ +namespace Feature.NgoCoalitions.Update; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId) + .NotEmpty(); + + RuleFor(x => x.CoalitionId) + .NotEmpty(); + + RuleFor(x => x.CoalitionName) + .NotEmpty() + .MaximumLength(256); + + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + } +} diff --git a/api/src/Feature.DataExport/Start/FormSubmissionsFilters.cs b/api/src/Feature.DataExport/Start/FormSubmissionsFilters.cs index 86aefe940..77778eefe 100644 --- a/api/src/Feature.DataExport/Start/FormSubmissionsFilters.cs +++ b/api/src/Feature.DataExport/Start/FormSubmissionsFilters.cs @@ -8,8 +8,9 @@ namespace Feature.DataExport.Start; public class FormSubmissionsFilters { + public DataSource DataSource { get; set; } = DataSource.Ngo; public string? SearchText { get; set; } - + public Guid? CoalitionMemberId { get; set; } public FormType? FormTypeFilter { get; set; } public string? Level1Filter { get; set; } @@ -39,7 +40,7 @@ public class FormSubmissionsFilters public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } - + public bool? IsCompletedFilter { get; set; } public ExportFormSubmissionsFilters ToFilter() @@ -65,7 +66,9 @@ public ExportFormSubmissionsFilters ToFilter() QuestionsAnswered = QuestionsAnswered, FromDateFilter = FromDateFilter, ToDateFilter = ToDateFilter, - IsCompletedFilter = IsCompletedFilter + IsCompletedFilter = IsCompletedFilter, + DataSource = DataSource, + CoalitionMemberId = CoalitionMemberId }; } -} \ No newline at end of file +} diff --git a/api/src/Feature.DataExport/Start/IncidentReportsFilters.cs b/api/src/Feature.DataExport/Start/IncidentReportsFilters.cs index e5d1ed7b7..db2439f03 100644 --- a/api/src/Feature.DataExport/Start/IncidentReportsFilters.cs +++ b/api/src/Feature.DataExport/Start/IncidentReportsFilters.cs @@ -7,6 +7,8 @@ namespace Feature.DataExport.Start; public class IncidentReportsFilters { + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } public string? SearchText { get; set; } public string? Level1Filter { get; set; } @@ -62,7 +64,9 @@ public ExportIncidentReportsFilters ToFilter() LocationType = LocationType, FromDateFilter = FromDateFilter, ToDateFilter = ToDateFilter, - IsCompletedFilter = IsCompletedFilter + IsCompletedFilter = IsCompletedFilter, + DataSource = DataSource, + CoalitionMemberId = CoalitionMemberId }; } -} \ No newline at end of file +} diff --git a/api/src/Feature.DataExport/Start/QuickReportsFilters.cs b/api/src/Feature.DataExport/Start/QuickReportsFilters.cs index f43762dd7..eff374ff4 100644 --- a/api/src/Feature.DataExport/Start/QuickReportsFilters.cs +++ b/api/src/Feature.DataExport/Start/QuickReportsFilters.cs @@ -1,3 +1,4 @@ +using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.ExportedDataAggregate.Filters; using Vote.Monitor.Domain.Entities.QuickReportAggregate; @@ -5,6 +6,8 @@ namespace Feature.DataExport.Start; public class QuickReportsFilters { + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } public string? Level1Filter { get; set; } public string? Level2Filter { get; set; } public string? Level3Filter { get; set; } @@ -29,8 +32,9 @@ public ExportQuickReportsFilters ToFilter() QuickReportLocationType = QuickReportLocationType, IncidentCategory = IncidentCategory, FromDateFilter = FromDateFilter, - ToDateFilter = ToDateFilter + ToDateFilter = ToDateFilter, + DataSource = DataSource, + CoalitionMemberId = CoalitionMemberId }; - } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Any/Endpoint.cs b/api/src/Feature.Form.Submissions/Any/Endpoint.cs index 43c83f0a9..d9b93cc4e 100644 --- a/api/src/Feature.Form.Submissions/Any/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Any/Endpoint.cs @@ -46,7 +46,7 @@ AND JSONB_ARRAY_LENGTH(FS."Answers") > 0 { electionRoundId = req.ElectionRoundId, observerId = req.ObserverId, - pollingStationId = req.PollingStationId, + pollingStationId = req.PollingStationId }; hasSubmissionsForPollingStation = await dbConnection.QuerySingleAsync(sql, queryParams); diff --git a/api/src/Feature.Form.Submissions/Delete/Endpoint.cs b/api/src/Feature.Form.Submissions/Delete/Endpoint.cs index 75d6de9d5..b61c0b2ba 100644 --- a/api/src/Feature.Form.Submissions/Delete/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Delete/Endpoint.cs @@ -28,24 +28,30 @@ public override async Task> ExecuteAsync(Request re await context.FormSubmissions .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteDeleteAsync(ct); await context.Notes .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteDeleteAsync(ct); await context.Attachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(a => a.IsDeleted, true), ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/FormSubmissionModel.cs b/api/src/Feature.Form.Submissions/FormSubmissionModel.cs index 602bf43ab..3a671d1c6 100644 --- a/api/src/Feature.Form.Submissions/FormSubmissionModel.cs +++ b/api/src/Feature.Form.Submissions/FormSubmissionModel.cs @@ -13,7 +13,6 @@ public record FormSubmissionModel [JsonConverter(typeof(SmartEnumNameConverter))] public SubmissionFollowUpStatus FollowUpStatus { get; init; } - public IReadOnlyList Answers { get; init; } public bool IsCompleted { get; init; } @@ -28,4 +27,4 @@ public record FormSubmissionModel FollowUpStatus = entity.FollowUpStatus, IsCompleted = entity.IsCompleted }; -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/FormSubmissionsInstaller.cs b/api/src/Feature.Form.Submissions/FormSubmissionsInstaller.cs index b096284a8..9bbbf35bf 100644 --- a/api/src/Feature.Form.Submissions/FormSubmissionsInstaller.cs +++ b/api/src/Feature.Form.Submissions/FormSubmissionsInstaller.cs @@ -1,7 +1,7 @@ -using Feature.Form.Submissions.Models; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Core.Converters; +using Vote.Monitor.Core.Models; using Vote.Monitor.Form.Module.Models; namespace Feature.Form.Submissions; diff --git a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs index ef0e8f1fc..615fcb97a 100644 --- a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs @@ -1,7 +1,7 @@ -using Feature.Form.Submissions.Models; -using Feature.Form.Submissions.Requests; +using Feature.Form.Submissions.Requests; using Microsoft.EntityFrameworkCore; using Vote.Monitor.Answer.Module.Aggregators; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Core.Models; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Domain; @@ -37,9 +37,11 @@ public override async Task, NotFound>> ExecuteAsync(FormSub var form = await context .Forms - .Where(x => x.ElectionRoundId == req.ElectionRoundId - && x.MonitoringNgo.NgoId == req.NgoId - && x.Id == req.FormId) + .FromSqlInterpolated($""" + select f.* from "GetAvailableForms"({req.ElectionRoundId}, {req.NgoId}, {req.DataSource.ToString()}) af + inner join "Forms" f on f."Id" = af."FormId" + """) + .Where(x => x.Id == req.FormId) .Where(x => x.Status == FormStatus.Published) .Where(x => x.FormType != FormType.CitizenReporting && x.FormType != FormType.IncidentReporting) .AsNoTracking() @@ -67,149 +69,354 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA FormSubmissionsAggregateFilter req, CancellationToken ct) { - var tags = req.TagsFilter ?? []; - var submissions = await context - .FormSubmissions - .Include(x => x.MonitoringObserver) - .ThenInclude(x => x.Observer) - .ThenInclude(x => x.ApplicationUser) - .Where(x => x.ElectionRoundId == req.ElectionRoundId - && x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId - && x.FormId == req.FormId) - .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || - EF.Functions.ILike(x.PollingStation.Level1, req.Level1Filter)) - .Where(x => string.IsNullOrWhiteSpace(req.Level2Filter) || - EF.Functions.ILike(x.PollingStation.Level2, req.Level2Filter)) - .Where(x => string.IsNullOrWhiteSpace(req.Level3Filter) || - EF.Functions.ILike(x.PollingStation.Level3, req.Level3Filter)) - .Where(x => string.IsNullOrWhiteSpace(req.Level4Filter) || - EF.Functions.ILike(x.PollingStation.Level4, req.Level4Filter)) - .Where(x => string.IsNullOrWhiteSpace(req.Level5Filter) || - EF.Functions.ILike(x.PollingStation.Level5, req.Level5Filter)) - .Where(x => string.IsNullOrWhiteSpace(req.PollingStationNumberFilter) || - EF.Functions.ILike(x.PollingStation.Number, req.PollingStationNumberFilter)) - .Where(x => req.HasFlaggedAnswers == null || (req.HasFlaggedAnswers.Value - ? x.NumberOfFlaggedAnswers > 0 - : x.NumberOfFlaggedAnswers == 0)) - .Where(x => req.FollowUpStatus == null || x.FollowUpStatus == req.FollowUpStatus) - .Where(x => tags.Length == 0 || x.MonitoringObserver.Tags.Any(tag => tags.Contains(tag))) - .Where(x => req.MonitoringObserverStatus == null || - x.MonitoringObserver.Status == req.MonitoringObserverStatus) - .Where(x => req.QuestionsAnswered == null - || (req.QuestionsAnswered == QuestionsAnsweredFilter.All && - x.NumberOfQuestionsAnswered == x.Form.NumberOfQuestions) - || (req.QuestionsAnswered == QuestionsAnsweredFilter.Some && - x.NumberOfQuestionsAnswered < x.Form.NumberOfQuestions) - || (req.QuestionsAnswered == QuestionsAnsweredFilter.None && x.NumberOfQuestionsAnswered == 0)) - .Where(x => req.HasNotes == null || (req.HasNotes.Value - ? context.Notes.Count(n => - n.MonitoringObserverId == x.MonitoringObserverId - && n.FormId == x.FormId - && n.PollingStationId == x.PollingStationId - && n.ElectionRoundId == x.ElectionRoundId) > 0 - : context.Notes.Count(n => - n.MonitoringObserverId == x.MonitoringObserverId - && n.FormId == x.FormId - && n.PollingStationId == x.PollingStationId - && n.ElectionRoundId == x.ElectionRoundId) == 0)) - .Where(x => req.HasAttachments == null || (req.HasAttachments.Value - ? context.Attachments.Count(a => - a.MonitoringObserverId == x.MonitoringObserverId - && a.FormId == x.FormId - && a.PollingStationId == x.PollingStationId - && a.ElectionRoundId == x.ElectionRoundId) > 0 - : context.Attachments.Count(a => - a.MonitoringObserverId == x.MonitoringObserverId - && a.FormId == x.FormId - && a.PollingStationId == x.PollingStationId - && a.ElectionRoundId == x.ElectionRoundId) == 0)) - .Where(x => req.IsCompletedFilter == null || x.IsCompleted == req.IsCompletedFilter) - .AsNoTracking() - .AsSplitQuery() - .ToListAsync(ct); + var submissionsSql = """ + WITH + POLLING_STATION_SUBMISSIONS AS ( + SELECT + PSI."Id" AS "SubmissionId", + 'PSI' AS "FormType", + 'PSI' AS "FormCode", + PSI."PollingStationId", + PSI."MonitoringObserverId", + PSI."NumberOfQuestionsAnswered", + PSI."NumberOfFlaggedAnswers", + 0 AS "MediaFilesCount", + 0 AS "NotesCount", + '[]'::JSONB AS "Attachments", + '[]'::JSONB AS "Notes", + COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") "TimeSubmitted", + PSI."FollowUpStatus", + PSIF."DefaultLanguage", + PSIF."Name", + PSI."IsCompleted", + PSI."Answers" + FROM + "PollingStationInformation" PSI + INNER JOIN "PollingStationInformationForms" PSIF ON PSIF."Id" = PSI."PollingStationInformationFormId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = PSI."MonitoringObserverId" + WHERE + PSI."ElectionRoundId" = @ELECTIONROUNDID + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMID IS NULL + OR PSI."PollingStationInformationFormId" = @FORMID + ) + AND ( + @FROMDATE IS NULL + OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @TODATE::TIMESTAMP + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR ( + @QUESTIONSANSWERED = 'All' + AND PSIF."NumberOfQuestions" = PSI."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'Some' + AND PSIF."NumberOfQuestions" <> PSI."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'None' + AND PSI."NumberOfQuestionsAnswered" = 0 + ) + ) + ), + FORM_SUBMISSIONS AS ( + SELECT + FS."Id" AS "SubmissionId", + F."FormType", + F."Code" AS "FormCode", + FS."PollingStationId", + FS."MonitoringObserverId", + FS."NumberOfQuestionsAnswered", + FS."NumberOfFlaggedAnswers", + ( + SELECT + COUNT(1) + FROM + "Attachments" A + WHERE + A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + ) AS "MediaFilesCount", + ( + SELECT + COUNT(1) + FROM + "Notes" N + WHERE + N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ) AS "NotesCount", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'FileName', + "FileName", + 'MimeType', + "MimeType", + 'FilePath', + "FilePath", + 'UploadedFileName', + "UploadedFileName", + 'TimeSubmitted', + COALESCE("LastModifiedOn", "CreatedOn") + ) + ) + FROM + "Attachments" A + WHERE + A."ElectionRoundId" = @ELECTIONROUNDID + AND A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + AND FS."PollingStationId" = A."PollingStationId" + ), + '[]'::JSONB + ) AS "Attachments", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'Text', + "Text", + 'TimeSubmitted', + COALESCE("LastModifiedOn", "CreatedOn") + ) + ) + FROM + "Notes" N + WHERE + N."ElectionRoundId" = @ELECTIONROUNDID + AND N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ), + '[]'::JSONB + ) AS "Notes", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "TimeSubmitted", + FS."FollowUpStatus", + F."DefaultLanguage", + F."Name", + FS."IsCompleted", + FS."Answers" + FROM + "FormSubmissions" FS + INNER JOIN "Forms" F ON F."Id" = FS."FormId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON FS."MonitoringObserverId" = MO."MonitoringObserverId" + INNER JOIN "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) AF ON AF."FormId" = FS."FormId" + WHERE + FS."ElectionRoundId" = @ELECTIONROUNDID + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMID IS NULL + OR FS."FormId" = @FORMID + ) + AND ( + @FROMDATE IS NULL + OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @TODATE::TIMESTAMP + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR ( + @QUESTIONSANSWERED = 'All' + AND F."NumberOfQuestions" = FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'Some' + AND F."NumberOfQuestions" <> FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'None' + AND FS."NumberOfQuestionsAnswered" = 0 + ) + ) + ) + SELECT + S."SubmissionId", + S."TimeSubmitted", + S."FormCode", + S."FormType", + S."DefaultLanguage", + S."Name" AS "FormName", + PS."Id" AS "PollingStationId", + PS."Level1", + PS."Level2", + PS."Level3", + PS."Level4", + PS."Level5", + PS."Number", + S."MonitoringObserverId", + MO."DisplayName" AS "ObserverName", + MO."Email", + MO."PhoneNumber", + MO."Status", + MO."Tags", + MO."NgoName", + S."NumberOfQuestionsAnswered", + S."NumberOfFlaggedAnswers", + S."MediaFilesCount", + S."NotesCount", + S."FollowUpStatus", + S."IsCompleted", + MO."Status" "MonitoringObserverStatus", + S."Notes", + S."Attachments", + S."Answers" + FROM + ( + SELECT + * + FROM + POLLING_STATION_SUBMISSIONS + UNION ALL + SELECT + * + FROM + FORM_SUBMISSIONS + ) S + INNER JOIN "PollingStations" PS ON PS."Id" = S."PollingStationId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = S."MonitoringObserverId" + WHERE + (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR PS."Number" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR ( + S."NumberOfFlaggedAnswers" = 0 + AND @HASFLAGGEDANSWERS = FALSE + ) + OR ( + S."NumberOfFlaggedAnswers" > 0 + AND @HASFLAGGEDANSWERS = TRUE + ) + ) + AND ( + @FOLLOWUPSTATUS IS NULL + OR S."FollowUpStatus" = @FOLLOWUPSTATUS + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR MO."Tags" && @TAGSFILTER + ) + AND ( + @HASNOTES IS NULL + OR ( + S."NotesCount" = 0 + AND @HASNOTES = FALSE + ) + OR ( + S."NotesCount" > 0 + AND @HASNOTES = TRUE + ) + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + S."MediaFilesCount" = 0 + AND @HASATTACHMENTS = FALSE + ) + OR ( + S."MediaFilesCount" > 0 + AND @HASATTACHMENTS = TRUE + ) + ) + """; - var formSubmissionsAggregate = new FormSubmissionsAggregate(form); - foreach (var formSubmission in submissions) - { - formSubmissionsAggregate.AggregateAnswers(formSubmission); - } - - var pollingStationIds = submissions.Select(x => x.PollingStationId).Distinct().ToArray(); - var monitoringObserverIds = submissions.Select(x => x.MonitoringObserverId).Distinct().ToArray(); - - var sql = """ - SELECT - N."Id", - N."MonitoringObserverId", - N."QuestionId", - N."Text", - COALESCE(N."LastModifiedOn", N."CreatedOn") "TimeSubmitted", - ( - SELECT - FS."Id" - FROM - "FormSubmissions" FS - WHERE - FS."MonitoringObserverId" = N."MonitoringObserverId" - AND FS."FormId" = N."FormId" - AND FS."PollingStationId" = N."PollingStationId" - AND FS."ElectionRoundId" = N."ElectionRoundId" - ) "SubmissionId" - FROM - "Notes" N - WHERE - N."ElectionRoundId" = @electionRoundId - AND N."FormId" = @formId - AND N."MonitoringObserverId" = ANY (@monitoringObserverIds) - AND N."PollingStationId" = ANY (@pollingStationIds); - - SELECT - A."MonitoringObserverId", - A."QuestionId", - A."FileName", - A."MimeType", - A."FilePath", - A."UploadedFileName", - COALESCE(A."LastModifiedOn", A."CreatedOn") "TimeSubmitted", - ( - SELECT - FS."Id" - FROM - "FormSubmissions" FS - WHERE - FS."MonitoringObserverId" = A."MonitoringObserverId" - AND FS."FormId" = A."FormId" - AND FS."PollingStationId" = A."PollingStationId" - AND FS."ElectionRoundId" = A."ElectionRoundId" - ) "SubmissionId" - FROM - "Attachments" A - WHERE - A."ElectionRoundId" = @electionRoundId - AND A."IsDeleted" = false AND A."IsCompleted" = TRUE - AND A."FormId" = @formId - AND A."MonitoringObserverId" = ANY (@monitoringObserverIds) - AND A."PollingStationId" = ANY (@pollingStationIds); - """; - - var queryArgs = new + var submissionsQueryArgs = new { electionRoundId = req.ElectionRoundId, + ngoId = req.NgoId, + coalitionMemberId = req.CoalitionMemberId, + level1 = req.Level1Filter, + level2 = req.Level2Filter, + level3 = req.Level3Filter, + level4 = req.Level4Filter, + level5 = req.Level5Filter, + pollingStationNumber = req.PollingStationNumberFilter, + hasFlaggedAnswers = req.HasFlaggedAnswers, + followUpStatus = req.FollowUpStatus?.ToString(), + tagsFilter = req.TagsFilter ?? [], + monitoringObserverStatus = req.MonitoringObserverStatus?.ToString(), formId = req.FormId, - monitoringObserverIds, - pollingStationIds + hasNotes = req.HasNotes, + hasAttachments = req.HasAttachments, + questionsAnswered = req.QuestionsAnswered?.ToString(), + fromDate = req.FromDateFilter?.ToString("O"), + toDate = req.ToDateFilter?.ToString("O"), + dataSource = req.DataSource?.ToString(), }; - List notes; - List attachments; + List submissions; using (var dbConnection = await connectionFactory.GetOpenConnectionAsync(ct)) { - using var multi = await dbConnection.QueryMultipleAsync(sql, queryArgs); - notes = multi.Read().ToList(); - attachments = multi.Read().ToList(); + using var multi = await dbConnection.QueryMultipleAsync(submissionsSql, submissionsQueryArgs); + submissions = multi.Read().ToList(); + } + + var formSubmissionsAggregate = new FormSubmissionsAggregate(form); + foreach (var formSubmission in submissions) + { + formSubmissionsAggregate.AggregateAnswers(formSubmission); } + + var notes = submissions.SelectMany(x => x.Notes).ToList(); + var attachments = submissions.SelectMany(x => x.Attachments).ToList(); + foreach (var attachment in attachments) { var result = @@ -239,9 +446,10 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA TagsFilter = req.TagsFilter, FollowUpStatus = req.FollowUpStatus, HasFlaggedAnswers = req.HasFlaggedAnswers, - IsCompletedFilter = req.IsCompletedFilter, MonitoringObserverStatus = req.MonitoringObserverStatus, PollingStationNumberFilter = req.PollingStationNumberFilter, + DataSource = req.DataSource!, + CoalitionMemberId = req.CoalitionMemberId, } }); } @@ -258,6 +466,7 @@ private async Task, NotFound>> AggregatePSIFormSubmissionsA .ThenInclude(x => x.Observer) .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId) .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || EF.Functions.ILike(x.PollingStation.Level1, req.Level1Filter)) @@ -298,9 +507,7 @@ private async Task, NotFound>> AggregatePSIFormSubmissionsA return TypedResults.Ok(new Response { - SubmissionsAggregate = formSubmissionsAggregate, - Notes = [], - Attachments = [] + SubmissionsAggregate = formSubmissionsAggregate, Notes = [], Attachments = [] }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetAggregated/Response.cs b/api/src/Feature.Form.Submissions/GetAggregated/Response.cs index 6314a7054..f491be6b5 100644 --- a/api/src/Feature.Form.Submissions/GetAggregated/Response.cs +++ b/api/src/Feature.Form.Submissions/GetAggregated/Response.cs @@ -1,5 +1,4 @@ -using Feature.Form.Submissions.Models; -using Vote.Monitor.Answer.Module.Aggregators; +using Vote.Monitor.Answer.Module.Aggregators; using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; @@ -38,4 +37,6 @@ public class SubmissionsFilterModel public bool? HasAttachments { get; set; } public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } +} diff --git a/api/src/Feature.Form.Submissions/GetById/Endpoint.cs b/api/src/Feature.Form.Submissions/GetById/Endpoint.cs index 28c737a82..cd7aa3761 100644 --- a/api/src/Feature.Form.Submissions/GetById/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetById/Endpoint.cs @@ -1,11 +1,12 @@ -using Vote.Monitor.Core.Services.FileStorage.Contracts; +using Vote.Monitor.Answer.Module.Models; +using Vote.Monitor.Core.Services.FileStorage.Contracts; namespace Feature.Form.Submissions.GetById; public class Endpoint( IAuthorizationService authorizationService, INpgsqlConnectionFactory dbConnectionFactory, - IFileStorageService fileStorageService) : Endpoint, NotFound>> + IFileStorageService fileStorageService) : Endpoint, NotFound>> { public override void Configure() { @@ -17,7 +18,7 @@ public override void Configure() Policies(PolicyNames.NgoAdminsOnly); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) { var authorizationResult = await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); @@ -49,11 +50,8 @@ WITH submissions AS psi."Breaks", psi."IsCompleted" FROM "PollingStationInformation" psi - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND psi."Id" = @submissionId + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, 'Coalition') AMO on AMO."MonitoringObserverId" = psi."MonitoringObserverId" + WHERE psi."Id" = @submissionId and psi."ElectionRoundId" = @electionRoundId UNION ALL SELECT fs."Id" AS "SubmissionId", @@ -88,12 +86,9 @@ UNION ALL '[]'::jsonb AS "Breaks", fs."IsCompleted" FROM "FormSubmissions" fs - INNER JOIN "MonitoringObservers" mo ON fs."MonitoringObserverId" = mo."Id" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, 'Coalition') AMO on AMO."MonitoringObserverId" = FS."MonitoringObserverId" INNER JOIN "Forms" f ON f."Id" = fs."FormId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND fs."Id" = @submissionId) + WHERE fs."Id" = @submissionId and fs."ElectionRoundId" = @electionRoundId) SELECT s."SubmissionId", s."TimeSubmitted", s."FormCode", @@ -106,10 +101,12 @@ UNION ALL ps."Level5", ps."Number", s."MonitoringObserverId", - u."FirstName" || ' ' || u."LastName" "ObserverName", - u."Email", - u."PhoneNumber", - mo."Tags", + AMO."DisplayName" "ObserverName", + AMO."Email", + AMO."PhoneNumber", + AMO."Tags", + AMO."NgoName", + AMO."IsOwnObserver", s."Attachments", s."Notes", s."Answers", @@ -122,27 +119,21 @@ UNION ALL s."IsCompleted" FROM submissions s INNER JOIN "PollingStations" ps ON ps."Id" = s."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = s."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - ORDER BY "TimeSubmitted" desc + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, 'Coalition') AMO on AMO."MonitoringObserverId" = s."MonitoringObserverId" """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - submissionId = req.SubmissionId, + submissionId = req.SubmissionId }; - Response submission = null; + FormSubmissionView submission = null; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) { - submission = await dbConnection.QueryFirstOrDefaultAsync(sql, queryArgs); + submission = await dbConnection.QueryFirstOrDefaultAsync(sql, queryArgs); } if (submission is null) @@ -163,4 +154,4 @@ ORDER BY "TimeSubmitted" desc return TypedResults.Ok(submission); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs index 9392ade66..c62a5b5b8 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs @@ -28,56 +28,78 @@ public override async Task, NotFound>> ExecuteAsync(Request var sql = """ WITH "CombinedTimestamps" AS ( - -- First subquery for FormSubmissions - SELECT MIN(COALESCE(FS."LastModifiedOn", FS."CreatedOn")) AS "FirstSubmissionTimestamp", - MAX(COALESCE(FS."LastModifiedOn", FS."CreatedOn")) AS "LastSubmissionTimestamp" - FROM "FormSubmissions" FS - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - UNION ALL + -- First subquery for FormSubmissions + SELECT + MIN( + COALESCE( + FS."LastModifiedOn", FS."CreatedOn" + ) + ) AS "FirstSubmissionTimestamp", + MAX( + COALESCE( + FS."LastModifiedOn", FS."CreatedOn" + ) + ) AS "LastSubmissionTimestamp" + FROM + "FormSubmissions" FS + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) af on fs."FormId" = af."FormId" + WHERE + FS."ElectionRoundId" = @electionRoundId + UNION ALL -- Second subquery for PollingStationInformation - SELECT MIN(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "FirstSubmissionTimestamp", - MAX(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "LastSubmissionTimestamp" - FROM "PollingStationInformation" PSI - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId) - - -- Final query to get the overall min and max - SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", - MAX("LastSubmissionTimestamp") AS "LastSubmissionTimestamp" - FROM "CombinedTimestamps"; - + SELECT + MIN( + COALESCE( + PSI."LastModifiedOn", PSI."CreatedOn" + ) + ) AS "FirstSubmissionTimestamp", + MAX( + COALESCE( + PSI."LastModifiedOn", PSI."CreatedOn" + ) + ) AS "LastSubmissionTimestamp" + FROM + "PollingStationInformation" PSI + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO ON MO."MonitoringObserverId" = PSI."MonitoringObserverId" + WHERE + PSI."ElectionRoundId" = @electionRoundId + ) -- Final query to get the overall min and max + SELECT + MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", + MAX("LastSubmissionTimestamp") AS "LastSubmissionTimestamp" + FROM + "CombinedTimestamps"; -- ===================================================================================== - - SELECT DISTINCT F."Id" AS "FormId", - F."Name" ->> F."DefaultLanguage" "FormName", - F."Code" "FormCode" - FROM "FormSubmissions" FS - INNER JOIN "Forms" F ON F."Id" = FS."FormId" - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - UNION ALL - SELECT DISTINCT F."Id" AS "FormId", - F."Name" ->> F."DefaultLanguage" "FormName", - F."Code" "FormCode" - FROM "PollingStationInformation" PSI - INNER JOIN "PollingStationInformationForms" F ON F."Id" = PSI."PollingStationInformationFormId" - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId + SELECT + DISTINCT F."Id" AS "FormId", + F."Name" ->> F."DefaultLanguage" "FormName", + F."Code" "FormCode" + FROM + "FormSubmissions" FS + INNER JOIN "Forms" F ON F."Id" = FS."FormId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) af on fs."FormId" = af."FormId" + WHERE + FS."ElectionRoundId" = @electionRoundId + UNION ALL + SELECT + DISTINCT F."Id" AS "FormId", + F."Name" ->> F."DefaultLanguage" "FormName", + F."Code" "FormCode" + FROM + "PollingStationInformation" PSI + INNER JOIN "PollingStationInformationForms" F ON F."Id" = PSI."PollingStationInformationFormId" + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) af on F."Id" = af."FormId" + WHERE + PSI."ElectionRoundId" = @electionRoundId """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, + dataSource = req.DataSource.ToString() }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -93,7 +115,7 @@ UNION ALL return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetFilters/Request.cs b/api/src/Feature.Form.Submissions/GetFilters/Request.cs index a34764eb2..dc8b992c6 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Request.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Request.cs @@ -1,4 +1,6 @@ -using Vote.Monitor.Core.Security; +using Microsoft.AspNetCore.Mvc; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.Security; namespace Feature.Form.Submissions.GetFilters; @@ -8,4 +10,6 @@ public class Request [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + + [FromQuery] public DataSource DataSource { get; set; } = DataSource.Ngo; } diff --git a/api/src/Feature.Form.Submissions/GetFilters/Validator.cs b/api/src/Feature.Form.Submissions/GetFilters/Validator.cs index ad2a96849..079d94b1c 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Validator.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs b/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs index ec89f14aa..2e8ca7e3f 100644 --- a/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs @@ -15,7 +15,8 @@ public override void Configure() Summary(x => { x.Summary = "Form submissions aggregated by observer"; }); } - public override async Task, NotFound>> ExecuteAsync(FormSubmissionsAggregateFilter req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(FormSubmissionsAggregateFilter req, + CancellationToken ct) { var authorizationResult = await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); @@ -24,113 +25,280 @@ public override async Task, NotFound>> ExecuteAsync(FormSub return TypedResults.NotFound(); } - var sql = """ - SELECT - F."Id" AS "FormId", - 'PSI' AS "FormCode", - 'PSI' AS "FormType", - F."Name" as "FormName", - F."DefaultLanguage", - COUNT(DISTINCT PSI."Id") "NumberOfSubmissions", - SUM(PSI."NumberOfFlaggedAnswers") "NumberOfFlaggedAnswers", - 0 AS "NumberOfMediaFiles", - 0 AS "NumberOfNotes" - FROM - "PollingStationInformationForms" F - LEFT JOIN "PollingStationInformation" PSI ON PSI."ElectionRoundId" = F."ElectionRoundId" - LEFT JOIN "PollingStations" PS ON PSI."PollingStationId" = PS."Id" - LEFT JOIN "MonitoringObservers" mo ON mo."Id" = PSI."MonitoringObserverId" - - WHERE - F."ElectionRoundId" = @electionRoundId - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers is NULL OR ("NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) OR ("NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) - AND (@followUpStatus is NULL OR "FollowUpStatus" = @followUpStatus) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) - AND (@questionsAnswered is null - OR (@questionsAnswered = 'All' AND F."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND F."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) - AND (@hasNotes is NULL OR (TRUE AND @hasNotes = false) OR (FALSE AND @hasNotes = true)) - AND (@hasAttachments is NULL OR (TRUE AND @hasAttachments = false) OR (FALSE AND @hasAttachments = true)) - AND (@isCompleted is NULL OR psi."IsCompleted" = @isCompleted) - GROUP BY - F."Id" - UNION ALL - SELECT - F."Id" AS "FormId", - F."Code" AS "FormCode", - F."FormType" AS "FormType", - F."Name" as "FormName", - F."DefaultLanguage", - COUNT(DISTINCT FS."Id") "NumberOfSubmissions", - SUM(FS."NumberOfFlaggedAnswers") "NumberOfFlaggedAnswers", - ( - SELECT - COUNT(1) - FROM - "Attachments" - WHERE - "FormId" = F."Id" - AND "IsCompleted" = TRUE AND "IsDeleted" = FALSE - ) AS "NumberOfMediaFiles", - ( - SELECT - COUNT(1) - FROM - "Notes" - WHERE - "FormId" = F."Id" - ) AS "NumberOfNotes" - FROM - "Forms" F - INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" - LEFT JOIN "FormSubmissions" FS ON FS."FormId" = F."Id" - LEFT JOIN "PollingStations" ps ON ps."Id" = FS."PollingStationId" - LEFT JOIN "MonitoringObservers" mo ON mo."Id" = FS."MonitoringObserverId" - - WHERE - F."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND F."Status" = 'Published' - AND F."FormType" NOT IN ('CitizenReporting', 'IncidentReporting') - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers is NULL OR ("NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) OR ("NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) - AND (@followUpStatus is NULL OR "FollowUpStatus" = @followUpStatus) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR fs."FormId" = @formId) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) - AND (@questionsAnswered is null - OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0)) - AND (@hasAttachments is NULL - OR ((SELECT COUNT(1) FROM "Attachments" WHERE "FormId" = fs."FormId" AND "MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = "PollingStationId" AND "IsDeleted" = false AND "IsCompleted" = true) = 0 AND @hasAttachments = false) - OR ((SELECT COUNT(1) FROM "Attachments" WHERE "FormId" = fs."FormId" AND "MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = "PollingStationId" AND "IsDeleted" = false AND "IsCompleted" = true) > 0 AND @hasAttachments = true)) - AND (@hasNotes is NULL - OR ((SELECT COUNT(1) FROM "Notes" WHERE "FormId" = fs."FormId" AND "MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = "PollingStationId") = 0 AND @hasNotes = false) - OR ((SELECT COUNT(1) FROM "Notes" WHERE "FormId" = fs."FormId" AND "MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = "PollingStationId") > 0 AND @hasNotes = true)) - GROUP BY - F."Id" - """; + var sql = + """ + WITH + FORM_SUBMISSIONS AS ( + SELECT + FS."Id" AS "SubmissionId", + FS."NumberOfFlaggedAnswers", + FS."NumberOfQuestionsAnswered", + FS."Answers", + FS."FollowUpStatus", + FS."IsCompleted", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "TimeSubmitted", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'FileName', + "FileName", + 'MimeType', + "MimeType", + 'FilePath', + "FilePath", + 'UploadedFileName', + "UploadedFileName", + 'TimeSubmitted', + COALESCE("LastModifiedOn", "CreatedOn") + ) + ) + FROM + "Attachments" A + WHERE + A."ElectionRoundId" = @ELECTIONROUNDID + AND A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + AND FS."PollingStationId" = A."PollingStationId" + ), + '[]'::JSONB + ) AS "Attachments", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'Text', + "Text", + 'TimeSubmitted', + COALESCE("LastModifiedOn", "CreatedOn") + ) + ) + FROM + "Notes" N + WHERE + N."ElectionRoundId" = @ELECTIONROUNDID + AND N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ), + '[]'::JSONB + ) AS "Notes", + PS."Id" AS "PollingStationId", + PS."Level1" AS "PollingStationLevel1", + PS."Level2" AS "PollingStationLevel2", + PS."Level3" AS "PollingStationLevel3", + PS."Level4" AS "PollingStationLevel4", + PS."Level5" AS "PollingStationLevel5", + PS."Number" AS "PollingStationNumber", + F."Id" AS "FormId", + F."Code" AS "FormCode", + F."FormType" AS "FormType", + F."Name" AS "FormName", + F."DefaultLanguage" AS "FormDefaultLanguage", + F."Questions" AS "FormQuestions", + MO."MonitoringObserverId", + MO."DisplayName", + MO."Tags", + MO."Status", + MO."Email", + MO."PhoneNumber", + MO."NgoId" + FROM + "FormSubmissions" FS + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON FS."MonitoringObserverId" = MO."MonitoringObserverId" + INNER JOIN "PollingStations" PS ON PS."Id" = FS."PollingStationId" + INNER JOIN "Forms" F ON F."Id" = FS."FormId" + WHERE + FS."ElectionRoundId" = @ELECTIONROUNDID + ), + PSI_SUBMISSIONS AS ( + SELECT + PSI."Id" AS "SubmissionId", + PSI."NumberOfFlaggedAnswers", + PSI."NumberOfQuestionsAnswered", + PSI."Answers", + PSI."FollowUpStatus", + PSI."IsCompleted", + COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") AS "TimeSubmitted", + '[]'::JSONB AS "Attachments", + '[]'::JSONB AS "Notes", + PS."Id" AS "PollingStationId", + PS."Level1" AS "PollingStationLevel1", + PS."Level2" AS "PollingStationLevel2", + PS."Level3" AS "PollingStationLevel3", + PS."Level4" AS "PollingStationLevel4", + PS."Level5" AS "PollingStationLevel5", + PS."Number" AS "PollingStationNumber", + PSIF."Id" AS "FormId", + PSIF."Code" AS "FormCode", + PSIF."FormType" AS "FormType", + PSIF."Name" AS "FormName", + PSIF."DefaultLanguage" AS "FormDefaultLanguage", + PSIF."Questions" AS "FormQuestions", + MO."MonitoringObserverId", + MO."DisplayName", + MO."Tags", + MO."Status", + MO."Email", + MO."PhoneNumber", + MO."NgoId" + FROM + "PollingStationInformation" PSI + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON PSI."MonitoringObserverId" = MO."MonitoringObserverId" + INNER JOIN "PollingStations" PS ON PSI."PollingStationId" = PS."Id" + INNER JOIN "PollingStationInformationForms" PSIF ON PSIF."Id" = PSI."PollingStationInformationFormId" + WHERE + PSI."ElectionRoundId" = @ELECTIONROUNDID + ), + ALL_SUBMISSIONS AS ( + SELECT + * + FROM + PSI_SUBMISSIONS + UNION + SELECT + * + FROM + FORM_SUBMISSIONS + ), + FILTERED_SUBMISSIONS AS ( + SELECT + * + FROM + ALL_SUBMISSIONS FS + WHERE + ( + @COALITIONMEMBERID IS NULL + OR FS."NgoId" = @COALITIONMEMBERID + ) + AND ( + @LEVEL1 IS NULL + OR FS."PollingStationLevel1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR FS."PollingStationLevel2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR FS."PollingStationLevel3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR FS."PollingStationLevel4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR FS."PollingStationLevel5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR FS."PollingStationNumber" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR ( + "NumberOfFlaggedAnswers" = 0 + AND @HASFLAGGEDANSWERS = FALSE + ) + OR ( + "NumberOfFlaggedAnswers" > 0 + AND @HASFLAGGEDANSWERS = TRUE + ) + ) + AND ( + @FOLLOWUPSTATUS IS NULL + OR "FollowUpStatus" = @FOLLOWUPSTATUS + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR FS."Tags" && @TAGSFILTER + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR FS."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMID IS NULL + OR FS."FormId" = @FORMID + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR ( + @QUESTIONSANSWERED = 'All' + AND JSONB_ARRAY_LENGTH(FS."FormQuestions") = FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'Some' + AND JSONB_ARRAY_LENGTH(FS."FormQuestions") <> FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'None' + AND FS."NumberOfQuestionsAnswered" = 0 + ) + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + JSONB_ARRAY_LENGTH(FS."Attachments") = 0 + AND @HASATTACHMENTS = FALSE + ) + OR ( + JSONB_ARRAY_LENGTH(FS."Attachments") > 0 + AND @HASATTACHMENTS = TRUE + ) + ) + AND ( + @HASNOTES IS NULL + OR ( + JSONB_ARRAY_LENGTH(FS."Notes") = 0 + AND @HASNOTES = FALSE + ) + OR ( + JSONB_ARRAY_LENGTH(FS."Notes") > 0 + AND @HASNOTES = TRUE + ) + ) + ) + SELECT + AF."FormId", + AF."FormCode", + AF."FormType", + AF."FormName", + AF."FormDefaultLanguage", + COUNT(FS."SubmissionId") AS "NumberOfSubmissions", + COALESCE(SUM(FS."NumberOfFlaggedAnswers"), 0) AS "NumberOfFlaggedAnswers", + COALESCE(SUM(JSONB_ARRAY_LENGTH(FS."Attachments")), 0) AS "NumberOfMediaFiles", + COALESCE(SUM(JSONB_ARRAY_LENGTH(FS."Notes")), 0) AS "NumberOfNotes" + FROM + "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) AF + LEFT JOIN FILTERED_SUBMISSIONS FS ON FS."FormId" = AF."FormId" + WHERE + AF."FormStatus" = 'Published' + AND AF."FormType" NOT IN ('CitizenReporting', 'IncidentReporting') + GROUP BY + AF."FormId", + AF."FormCode", + AF."FormType", + AF."FormName", + AF."FormDefaultLanguage"; + """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, + dataSource = req.DataSource.ToString(), + coalitionMemberId= req.CoalitionMemberId, level1 = req.Level1Filter, level2 = req.Level2Filter, level3 = req.Level3Filter, @@ -145,7 +313,6 @@ GROUP BY hasNotes = req.HasNotes, hasAttachments = req.HasAttachments, questionsAnswered = req.QuestionsAnswered?.ToString(), - isCompleted = req.IsCompletedFilter }; IEnumerable aggregatedFormOverviews; @@ -157,4 +324,4 @@ GROUP BY return TypedResults.Ok(new Response { AggregatedForms = aggregatedFormOverviews.ToList() }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs index 516c1ba59..fb3eed5aa 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs @@ -28,169 +28,171 @@ public override async Task> } var sql = """ - SELECT COUNT(*) count + SELECT + COUNT(*) count FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" + "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter); - - SELECT + (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND ( + @searchText IS NULL + OR @searchText = '' + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + ) + AND ( + @tagsFilter IS NULL + OR cardinality(@tagsFilter) = 0 + OR mo."Tags" && @tagsFilter + ); + + SELECT "MonitoringObserverId", "ObserverName", "PhoneNumber", "Email", "Tags", - "NumberOfCompletedForms", + "NgoName", "NumberOfFlaggedAnswers", "NumberOfLocations", "NumberOfFormsSubmitted", - "FollowUpStatus" - FROM ( - SELECT - MO."Id" "MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" "ObserverName", - U."PhoneNumber", - U."Email", - MO."Tags", - COALESCE( - ( - SELECT - count(*) - FROM - "FormSubmissions" FS - WHERE - FS."MonitoringObserverId" = MO."Id" and fs."IsCompleted" = true - ), - 0 - ) AS "NumberOfCompletedForms", - COALESCE( + "FollowUpStatus", + "IsOwnObserver" + FROM + ( + SELECT + MO."MonitoringObserverId" "MonitoringObserverId", + Mo."DisplayName" "ObserverName", + Mo."PhoneNumber", + Mo."Email", + MO."Tags", + MO."NgoName", + MO."IsOwnObserver", + COALESCE( + ( + SELECT + SUM("NumberOfFlaggedAnswers") + FROM + "FormSubmissions" FS + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) f on f."FormId" = fs."FormId" + WHERE + FS."MonitoringObserverId" = MO."MonitoringObserverId" + ), + 0 + ) AS "NumberOfFlaggedAnswers", ( SELECT - SUM("NumberOfFlaggedAnswers") + COUNT(*) FROM - "FormSubmissions" FS - WHERE - FS."MonitoringObserverId" = MO."Id" - ), - 0 - ) AS "NumberOfFlaggedAnswers", - ( - SELECT - COUNT(*) - FROM - ( - SELECT - PSI."PollingStationId" - FROM - "PollingStationInformation" PSI - WHERE - PSI."MonitoringObserverId" = MO."Id" - AND PSI."ElectionRoundId" = @electionRoundId - UNION - SELECT - FS."PollingStationId" - FROM - "FormSubmissions" FS - WHERE - FS."MonitoringObserverId" = MO."Id" - AND FS."ElectionRoundId" = @electionRoundId - ) TMP - ) AS "NumberOfLocations", - ( - SELECT - COUNT(*) - FROM - ( - SELECT - PSI."Id" - FROM - "PollingStationInformation" PSI - WHERE - PSI."MonitoringObserverId" = MO."Id" - AND PSI."ElectionRoundId" = @electionRoundId - UNION + ( + SELECT + PSI."PollingStationId" + FROM + "PollingStationInformation" PSI + WHERE + PSI."MonitoringObserverId" = MO."MonitoringObserverId" + AND PSI."ElectionRoundId" = @electionRoundId + UNION + SELECT + FS."PollingStationId" + FROM + "FormSubmissions" FS + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) f on f."FormId" = fs."FormId" + WHERE + FS."MonitoringObserverId" = MO."MonitoringObserverId" + AND FS."ElectionRoundId" = @electionRoundId + ) TMP + ) AS "NumberOfLocations", + ( + SELECT + COUNT(*) + FROM + ( + SELECT + PSI."Id" + FROM + "PollingStationInformation" PSI + WHERE + PSI."MonitoringObserverId" = MO."MonitoringObserverId" + AND PSI."ElectionRoundId" = @electionRoundId + UNION + SELECT + FS."Id" + FROM + "FormSubmissions" FS + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) f on f."FormId" = fs."FormId" + WHERE + FS."MonitoringObserverId" = MO."MonitoringObserverId" + AND FS."ElectionRoundId" = @electionRoundId + ) TMP + ) AS "NumberOfFormsSubmitted", + ( + CASE WHEN EXISTS ( SELECT - FS."Id" + 1 FROM "FormSubmissions" FS + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) f on f."FormId" = fs."FormId" WHERE - FS."MonitoringObserverId" = MO."Id" - AND FS."ElectionRoundId" = @electionRoundId - ) TMP - ) AS "NumberOfFormsSubmitted", - ( - CASE - WHEN EXISTS ( - SELECT 1 - FROM - "FormSubmissions" FS - WHERE - FS."FollowUpStatus" = 'NeedsFollowUp' - AND FS."MonitoringObserverId" = MO."Id" - AND FS."ElectionRoundId" = @electionRoundId - ) - THEN 'NeedsFollowUp' - ELSE NULL - END - ) AS "FollowUpStatus" - FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) + FS."FollowUpStatus" = 'NeedsFollowUp' + AND FS."MonitoringObserverId" = MO."MonitoringObserverId" + AND FS."ElectionRoundId" = @electionRoundId + ) THEN 'NeedsFollowUp' ELSE NULL END + ) AS "FollowUpStatus" + FROM + "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO + WHERE + (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND ( + @searchText IS NULL + OR @searchText = '' + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + ) + AND ( + @tagsFilter IS NULL + OR cardinality(@tagsFilter) = 0 + OR mo."Tags" && @tagsFilter + ) ) T WHERE - (@needsFollowUp IS NULL OR T."FollowUpStatus" = 'NeedsFollowUp') + ( + @needsFollowUp IS NULL + OR T."FollowUpStatus" = 'NeedsFollowUp' + ) ORDER BY CASE WHEN @sortExpression = 'ObserverName ASC' THEN "ObserverName" END ASC, CASE WHEN @sortExpression = 'ObserverName DESC' THEN "ObserverName" END DESC, - CASE WHEN @sortExpression = 'PhoneNumber ASC' THEN "PhoneNumber" END ASC, CASE WHEN @sortExpression = 'PhoneNumber DESC' THEN "PhoneNumber" END DESC, - CASE WHEN @sortExpression = 'Email ASC' THEN "Email" END ASC, CASE WHEN @sortExpression = 'Email DESC' THEN "Email" END DESC, - CASE WHEN @sortExpression = 'Tags ASC' THEN "Tags" END ASC, CASE WHEN @sortExpression = 'Tags DESC' THEN "Tags" END DESC, - CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers ASC' THEN "NumberOfFlaggedAnswers" END ASC, CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers DESC' THEN "NumberOfFlaggedAnswers" END DESC, - CASE WHEN @sortExpression = 'NumberOfLocations ASC' THEN "NumberOfLocations" END ASC, CASE WHEN @sortExpression = 'NumberOfLocations DESC' THEN "NumberOfLocations" END DESC, - CASE WHEN @sortExpression = 'NumberOfFormsSubmitted ASC' THEN "NumberOfFormsSubmitted" END ASC, - CASE WHEN @sortExpression = 'NumberOfFormsSubmitted DESC' THEN "NumberOfFormsSubmitted" END DESC, - - CASE WHEN @sortExpression = 'NumberOfCompletedForms ASC' THEN "NumberOfCompletedForms" END ASC, - CASE WHEN @sortExpression = 'NumberOfCompletedForms DESC' THEN "NumberOfCompletedForms" END DESC - - OFFSET @offset ROWS - FETCH NEXT @pageSize ROWS ONLY; + CASE WHEN @sortExpression = 'NumberOfFormsSubmitted DESC' THEN "NumberOfFormsSubmitted" END DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, + coalitionMemberId = req.CoalitionMemberId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), pageSize = req.PageSize, tagsFilter = req.TagsFilter ?? [], searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", + datasource = req.DataSource?.ToString(), sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), - needsFollowUp = req.FollowUpStatus?.ToString(), + needsFollowUp = req.FollowUpStatus?.ToString() }; int totalRowCount = 0; @@ -258,12 +260,6 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(ObserverSubmissionOverview.NumberOfFormsSubmitted)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(ObserverSubmissionOverview.NumberOfCompletedForms), - StringComparison.InvariantCultureIgnoreCase)) - { - return $"{nameof(ObserverSubmissionOverview.NumberOfCompletedForms)} {sortOrder}"; - } - return $"{nameof(ObserverSubmissionOverview.ObserverName)} ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListByObserver/ObserverSubmissionOverview.cs b/api/src/Feature.Form.Submissions/ListByObserver/ObserverSubmissionOverview.cs index 5da5cfe43..357bf2450 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/ObserverSubmissionOverview.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/ObserverSubmissionOverview.cs @@ -9,12 +9,14 @@ public record ObserverSubmissionOverview public string ObserverName { get; init; } = default!; public string Email { get; init; } = default!; public string PhoneNumber { get; init; } = default!; + public string NgoName { get; init; } = default!; public string[] Tags { get; init; } = []; public int NumberOfFlaggedAnswers { get; init; } public int NumberOfLocations { get; init; } public int NumberOfFormsSubmitted { get; init; } - public int NumberOfCompletedForms { get; init; } [JsonConverter(typeof(SmartEnumNameConverter))] public SubmissionFollowUpStatus? FollowUpStatus { get; init; } + + public bool IsOwnObserver { get; set; } } diff --git a/api/src/Feature.Form.Submissions/ListByObserver/Request.cs b/api/src/Feature.Form.Submissions/ListByObserver/Request.cs index f1426b976..c487674f8 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/Request.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/Request.cs @@ -10,12 +10,12 @@ public class Request : BaseSortPaginatedRequest [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } - [QueryParam] - public string[]? TagsFilter { get; set; } = []; + [QueryParam] public string[]? TagsFilter { get; set; } = []; - [QueryParam] - public string? SearchText { get; set; } + [QueryParam] public string? SearchText { get; set; } - [QueryParam] - public SubmissionFollowUpStatus? FollowUpStatus { get; set; } + [QueryParam] public SubmissionFollowUpStatus? FollowUpStatus { get; set; } + + [QueryParam] public DataSource DataSource { get; set; } = DataSource.Ngo; + [QueryParam] public Guid? CoalitionMemberId { get; set; } } diff --git a/api/src/Feature.Form.Submissions/ListByObserver/Validator.cs b/api/src/Feature.Form.Submissions/ListByObserver/Validator.cs index 29b42ac65..f1e8b9060 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/Validator.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs index b8f77232e..49a3cbc4b 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs @@ -27,162 +27,196 @@ public override async Task>, NotFo var sql = """ SELECT SUM(count) - FROM - (SELECT count(*) AS count - FROM "PollingStationInformation" psi - INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" - INNER JOIN "PollingStations" ps ON ps."Id" = psi."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) - AND (@formType IS NULL OR 'PSI' = @formType) - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers is NULL OR @hasFlaggedAnswers = false OR 1 = 2) - AND (@followUpStatus is NULL OR psi."FollowUpStatus" = @followUpStatus) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) - AND (@questionsAnswered is null - OR (@questionsAnswered = 'All' AND psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) - AND (@hasNotes is NULL OR (TRUE AND @hasNotes = false) OR (FALSE AND @hasNotes = true)) - AND (@hasAttachments is NULL OR (TRUE AND @hasAttachments = false) OR (FALSE AND @hasAttachments = true)) - AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR PSI."IsCompleted" = @isCompleted) - UNION ALL SELECT count(*) AS count - FROM "FormSubmissions" fs - INNER JOIN "Forms" f ON f."Id" = fs."FormId" - INNER JOIN "PollingStations" ps ON ps."Id" = fs."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = fs."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) - AND (@formType IS NULL OR f."FormType" = @formType) - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers is NULL OR (fs."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) OR ("NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) - AND (@followUpStatus is NULL OR fs."FollowUpStatus" = @followUpStatus) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR fs."FormId" = @formId) - AND (@questionsAnswered is null - OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0)) - AND (@hasAttachments is NULL - OR ((SELECT COUNT(1) FROM "Attachments" A WHERE A."FormId" = fs."FormId" AND A."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = A."PollingStationId" AND A."IsDeleted" = false AND A."IsCompleted" = true) = 0 AND @hasAttachments = false) - OR ((SELECT COUNT(1) FROM "Attachments" A WHERE A."FormId" = fs."FormId" AND A."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = A."PollingStationId" AND A."IsDeleted" = false AND A."IsCompleted" = true) > 0 AND @hasAttachments = true)) - AND (@hasNotes is NULL - OR ((SELECT COUNT(1) FROM "Notes" N WHERE N."FormId" = fs."FormId" AND N."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = N."PollingStationId") = 0 AND @hasNotes = false) - OR ((SELECT COUNT(1) FROM "Notes" N WHERE N."FormId" = fs."FormId" AND N."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = N."PollingStationId") > 0 AND @hasNotes = true)) - AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) - ) c; - - WITH polling_station_submissions AS ( - SELECT psi."Id" AS "SubmissionId", - 'PSI' AS "FormType", - 'PSI' AS "FormCode", - psi."PollingStationId", - psi."MonitoringObserverId", - psi."NumberOfQuestionsAnswered", - psi."NumberOfFlaggedAnswers", - 0 AS "MediaFilesCount", - 0 AS "NotesCount", - COALESCE(psi."LastModifiedOn", psi."CreatedOn") "TimeSubmitted", - psi."FollowUpStatus", - psif."DefaultLanguage", - psif."Name", - psi."IsCompleted" - FROM "PollingStationInformation" psi - INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) - AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR psi."IsCompleted" = @isCompleted) - AND (@questionsAnswered IS NULL - OR (@questionsAnswered = 'All' AND psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) - ), - form_submissions AS ( - SELECT fs."Id" AS "SubmissionId", - f."FormType", - f."Code" AS "FormCode", - fs."PollingStationId", - fs."MonitoringObserverId", - fs."NumberOfQuestionsAnswered", - fs."NumberOfFlaggedAnswers", - ( - SELECT COUNT(1) + FROM (SELECT count(*) AS count + FROM "PollingStationInformation" psi + INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" + INNER JOIN "PollingStations" ps ON ps."Id" = psi."PollingStationId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) mo ON mo."MonitoringObserverId" = psi."MonitoringObserverId" + WHERE psi."ElectionRoundId" = @electionRoundId + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND (@searchText IS NULL + OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText) + AND (@formType IS NULL OR 'PSI' = @formType) + AND (@level1 IS NULL OR ps."Level1" = @level1) + AND (@level2 IS NULL OR ps."Level2" = @level2) + AND (@level3 IS NULL OR ps."Level3" = @level3) + AND (@level4 IS NULL OR ps."Level4" = @level4) + AND (@level5 IS NULL OR ps."Level5" = @level5) + AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) + AND (@hasFlaggedAnswers is NULL OR @hasFlaggedAnswers = false OR 1 = 2) + AND (@followUpStatus is NULL OR psi."FollowUpStatus" = @followUpStatus) + AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) + AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) + AND (@questionsAnswered is null + OR (@questionsAnswered = 'All' AND psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'Some' AND psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) + AND (@hasNotes is NULL OR (TRUE AND @hasNotes = false) OR (FALSE AND @hasNotes = true)) + AND (@hasAttachments is NULL OR (TRUE AND @hasAttachments = false) OR (FALSE AND @hasAttachments = true)) + AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) + AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) + UNION ALL + SELECT count(*) AS count + FROM "FormSubmissions" fs + INNER JOIN "Forms" f ON f."Id" = fs."FormId" + INNER JOIN "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) AF ON AF."FormId" = fs."FormId" + INNER JOIN "PollingStations" ps ON ps."Id" = fs."PollingStationId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) mo ON mo."MonitoringObserverId" = fs."MonitoringObserverId" + WHERE fs."ElectionRoundId" = @electionRoundId + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND (@searchText IS NULL + OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText) + AND (@formType IS NULL OR f."FormType" = @formType) + AND (@level1 IS NULL OR ps."Level1" = @level1) + AND (@level2 IS NULL OR ps."Level2" = @level2) + AND (@level3 IS NULL OR ps."Level3" = @level3) + AND (@level4 IS NULL OR ps."Level4" = @level4) + AND (@level5 IS NULL OR ps."Level5" = @level5) + AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) + AND (@hasFlaggedAnswers is NULL OR (fs."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) OR + ("NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) + AND (@followUpStatus is NULL OR fs."FollowUpStatus" = @followUpStatus) + AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) + AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) + AND (@formId IS NULL OR fs."FormId" = @formId) + AND (@questionsAnswered is null + OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0)) + AND (@hasAttachments is NULL + OR ((SELECT COUNT(1) + FROM "Attachments" A + WHERE A."FormId" = fs."FormId" + AND A."MonitoringObserverId" = fs."MonitoringObserverId" + AND fs."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = false + AND A."IsCompleted" = true) = 0 AND @hasAttachments = false) + OR ((SELECT COUNT(1) FROM "Attachments" A WHERE A."FormId" = fs."FormId" - AND a."MonitoringObserverId" = fs."MonitoringObserverId" + AND A."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = A."PollingStationId" - AND A."IsDeleted" = false AND A."IsCompleted" = true - ) AS "MediaFilesCount", - ( - SELECT COUNT(1) + AND A."IsDeleted" = false + AND A."IsCompleted" = true) > 0 AND @hasAttachments = true)) + AND (@hasNotes is NULL + OR ((SELECT COUNT(1) + FROM "Notes" N + WHERE N."FormId" = fs."FormId" + AND N."MonitoringObserverId" = fs."MonitoringObserverId" + AND fs."PollingStationId" = N."PollingStationId") = 0 AND @hasNotes = false) + OR ((SELECT COUNT(1) FROM "Notes" N WHERE N."FormId" = fs."FormId" AND N."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = N."PollingStationId" - ) AS "NotesCount", - COALESCE(fs."LastModifiedOn", fs."CreatedOn") AS "TimeSubmitted", - fs."FollowUpStatus", - f."DefaultLanguage", - f."Name", - fs."IsCompleted" - FROM "FormSubmissions" fs - INNER JOIN "Forms" f ON f."Id" = fs."FormId" - INNER JOIN "MonitoringObservers" mo ON fs."MonitoringObserverId" = mo."Id" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR fs."FormId" = @formId) - AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) - AND (@questionsAnswered IS NULL - OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0)) - ) + AND fs."PollingStationId" = N."PollingStationId") > 0 AND @hasNotes = true)) + AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) + AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp)) c; + + WITH polling_station_submissions AS (SELECT psi."Id" AS "SubmissionId", + 'PSI' AS "FormType", + 'PSI' AS "FormCode", + psi."PollingStationId", + psi."MonitoringObserverId", + psi."NumberOfQuestionsAnswered", + psi."NumberOfFlaggedAnswers", + 0 AS "MediaFilesCount", + 0 AS "NotesCount", + COALESCE(psi."LastModifiedOn", psi."CreatedOn") "TimeSubmitted", + psi."FollowUpStatus", + psif."DefaultLanguage", + psif."Name", + psi."IsCompleted" + FROM "PollingStationInformation" psi + INNER JOIN "PollingStationInformationForms" psif + ON psif."Id" = psi."PollingStationInformationFormId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) mo + ON mo."MonitoringObserverId" = psi."MonitoringObserverId" + WHERE psi."ElectionRoundId" = @electionRoundId + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@searchText IS NULL OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText) + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@monitoringObserverStatus IS NULL OR + mo."Status" = @monitoringObserverStatus) + AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) + AND (@fromDate is NULL OR + COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) + AND (@toDate is NULL OR + COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) + AND (@questionsAnswered IS NULL + OR (@questionsAnswered = 'All' AND + psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'Some' AND + psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0))), + form_submissions AS (SELECT fs."Id" AS "SubmissionId", + f."FormType", + f."Code" AS "FormCode", + fs."PollingStationId", + fs."MonitoringObserverId", + fs."NumberOfQuestionsAnswered", + fs."NumberOfFlaggedAnswers", + (SELECT COUNT(1) + FROM "Attachments" A + WHERE A."FormId" = fs."FormId" + AND a."MonitoringObserverId" = fs."MonitoringObserverId" + AND fs."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = false + AND A."IsCompleted" = true) AS "MediaFilesCount", + (SELECT COUNT(1) + FROM "Notes" N + WHERE N."FormId" = fs."FormId" + AND N."MonitoringObserverId" = fs."MonitoringObserverId" + AND fs."PollingStationId" = N."PollingStationId") AS "NotesCount", + COALESCE(fs."LastModifiedOn", fs."CreatedOn") AS "TimeSubmitted", + fs."FollowUpStatus", + f."DefaultLanguage", + f."Name", + fs."IsCompleted" + FROM "FormSubmissions" fs + INNER JOIN "Forms" f ON f."Id" = fs."FormId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) mo ON fs."MonitoringObserverId" = mo."MonitoringObserverId" + INNER JOIN "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) AF ON AF."FormId" = fs."FormId" + WHERE fs."ElectionRoundId" = @electionRoundId + AND (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@searchText IS NULL OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."MonitoringObserverId"::TEXT ILIKE @searchText) + AND (@monitoringObserverId IS NULL OR mo."MonitoringObserverId" = @monitoringObserverId) + AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) + AND (@formId IS NULL OR fs."FormId" = @formId) + AND (@fromDate is NULL OR + COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) + AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) + AND (@questionsAnswered IS NULL + OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'Some' AND + f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0))) SELECT s."SubmissionId", s."TimeSubmitted", s."FormCode", s."FormType", s."DefaultLanguage", - s."Name" as "FormName", - ps."Id" AS "PollingStationId", + s."Name" as "FormName", + ps."Id" AS "PollingStationId", ps."Level1", ps."Level2", ps."Level3", @@ -190,93 +224,82 @@ SELECT COUNT(1) ps."Level5", ps."Number", s."MonitoringObserverId", - u."FirstName" || ' ' || u."LastName" AS "ObserverName", - u."Email", - u."PhoneNumber", + mo."DisplayName" AS "ObserverName", + mo."Email", + mo."PhoneNumber", mo."Status", mo."Tags", + MO."NgoName", s."NumberOfQuestionsAnswered", s."NumberOfFlaggedAnswers", s."MediaFilesCount", s."NotesCount", s."FollowUpStatus", s."IsCompleted", - mo."Status" "MonitoringObserverStatus" - FROM ( - SELECT * FROM polling_station_submissions - UNION ALL - SELECT * FROM form_submissions - ) s - INNER JOIN "PollingStations" ps ON ps."Id" = s."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = s."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' - OR u."FirstName" ILIKE @searchText - OR u."LastName" ILIKE @searchText - OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText) - AND (@formType IS NULL OR s."FormType" = @formType) + mo."Status" "MonitoringObserverStatus" + FROM (SELECT * + FROM polling_station_submissions + UNION ALL + SELECT * + FROM form_submissions) s + INNER JOIN "PollingStations" ps ON ps."Id" = s."PollingStationId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) mo ON mo."MonitoringObserverId" = s."MonitoringObserverId" + WHERE (@formType IS NULL OR s."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) AND (@level3 IS NULL OR ps."Level3" = @level3) AND (@level4 IS NULL OR ps."Level4" = @level4) AND (@level5 IS NULL OR ps."Level5" = @level5) AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL - OR (s."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) - OR (s."NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) + AND (@hasFlaggedAnswers IS NULL + OR (s."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) + OR (s."NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) AND (@followUpStatus IS NULL OR s."FollowUpStatus" = @followUpStatus) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@hasNotes IS NULL - OR (s."NotesCount" = 0 AND @hasNotes = false) - OR (s."NotesCount" > 0 AND @hasNotes = true)) - AND (@hasAttachments IS NULL - OR (s."MediaFilesCount" = 0 AND @hasAttachments = false) - OR (s."MediaFilesCount" > 0 AND @hasAttachments = true)) - ORDER BY - CASE WHEN @sortExpression = 'TimeSubmitted ASC' THEN s."TimeSubmitted" END ASC, - CASE WHEN @sortExpression = 'TimeSubmitted DESC' THEN s."TimeSubmitted" END DESC, - CASE WHEN @sortExpression = 'FormCode ASC' THEN s."FormCode" END ASC, - CASE WHEN @sortExpression = 'FormCode DESC' THEN s."FormCode" END DESC, - CASE WHEN @sortExpression = 'FormType ASC' THEN s."FormType" END ASC, - CASE WHEN @sortExpression = 'FormType DESC' THEN s."FormType" END DESC, - CASE WHEN @sortExpression = 'Level1 ASC' THEN ps."Level1" END ASC, - CASE WHEN @sortExpression = 'Level1 DESC' THEN ps."Level1" END DESC, - CASE WHEN @sortExpression = 'Level2 ASC' THEN ps."Level2" END ASC, - CASE WHEN @sortExpression = 'Level2 DESC' THEN ps."Level2" END DESC, - CASE WHEN @sortExpression = 'Level3 ASC' THEN ps."Level3" END ASC, - CASE WHEN @sortExpression = 'Level3 DESC' THEN ps."Level3" END DESC, - CASE WHEN @sortExpression = 'Level4 ASC' THEN ps."Level4" END ASC, - CASE WHEN @sortExpression = 'Level4 DESC' THEN ps."Level4" END DESC, - CASE WHEN @sortExpression = 'Level5 ASC' THEN ps."Level5" END ASC, - CASE WHEN @sortExpression = 'Level5 DESC' THEN ps."Level5" END DESC, - CASE WHEN @sortExpression = 'Number ASC' THEN ps."Number" END ASC, - CASE WHEN @sortExpression = 'Number DESC' THEN ps."Number" END DESC, - CASE WHEN @sortExpression = 'ObserverName ASC' THEN u."FirstName" || ' ' || u."LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN u."FirstName" || ' ' || u."LastName" END DESC, - CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers ASC' THEN s."NumberOfFlaggedAnswers" END ASC, - CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers DESC' THEN s."NumberOfFlaggedAnswers" END DESC, - CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered ASC' THEN s."NumberOfQuestionsAnswered" END ASC, - CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered DESC' THEN s."NumberOfQuestionsAnswered" END DESC, - CASE WHEN @sortExpression = 'MediaFilesCount ASC' THEN s."MediaFilesCount" END ASC, - CASE WHEN @sortExpression = 'MediaFilesCount DESC' THEN s."MediaFilesCount" END DESC, - CASE WHEN @sortExpression = 'NotesCount ASC' THEN s."NotesCount" END ASC, - CASE WHEN @sortExpression = 'NotesCount DESC' THEN s."NotesCount" END DESC, - CASE WHEN @sortExpression = 'MonitoringObserverStatus ASC' THEN mo."Status" END ASC, - CASE WHEN @sortExpression = 'MonitoringObserverStatus DESC' THEN mo."Status" END DESC - OFFSET @offset ROWS - FETCH NEXT @pageSize ROWS ONLY; + AND (@hasNotes IS NULL + OR (s."NotesCount" = 0 AND @hasNotes = false) + OR (s."NotesCount" > 0 AND @hasNotes = true)) + AND (@hasAttachments IS NULL + OR (s."MediaFilesCount" = 0 AND @hasAttachments = false) + OR (s."MediaFilesCount" > 0 AND @hasAttachments = true)) + ORDER BY CASE WHEN @sortExpression = 'TimeSubmitted ASC' THEN s."TimeSubmitted" END ASC, + CASE WHEN @sortExpression = 'TimeSubmitted DESC' THEN s."TimeSubmitted" END DESC, + CASE WHEN @sortExpression = 'FormCode ASC' THEN s."FormCode" END ASC, + CASE WHEN @sortExpression = 'FormCode DESC' THEN s."FormCode" END DESC, + CASE WHEN @sortExpression = 'FormType ASC' THEN s."FormType" END ASC, + CASE WHEN @sortExpression = 'FormType DESC' THEN s."FormType" END DESC, + CASE WHEN @sortExpression = 'Level1 ASC' THEN ps."Level1" END ASC, + CASE WHEN @sortExpression = 'Level1 DESC' THEN ps."Level1" END DESC, + CASE WHEN @sortExpression = 'Level2 ASC' THEN ps."Level2" END ASC, + CASE WHEN @sortExpression = 'Level2 DESC' THEN ps."Level2" END DESC, + CASE WHEN @sortExpression = 'Level3 ASC' THEN ps."Level3" END ASC, + CASE WHEN @sortExpression = 'Level3 DESC' THEN ps."Level3" END DESC, + CASE WHEN @sortExpression = 'Level4 ASC' THEN ps."Level4" END ASC, + CASE WHEN @sortExpression = 'Level4 DESC' THEN ps."Level4" END DESC, + CASE WHEN @sortExpression = 'Level5 ASC' THEN ps."Level5" END ASC, + CASE WHEN @sortExpression = 'Level5 DESC' THEN ps."Level5" END DESC, + CASE WHEN @sortExpression = 'Number ASC' THEN ps."Number" END ASC, + CASE WHEN @sortExpression = 'Number DESC' THEN ps."Number" END DESC, + CASE WHEN @sortExpression = 'ObserverName ASC' THEN mo."DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN mo."DisplayName" END DESC, + CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers ASC' THEN s."NumberOfFlaggedAnswers" END ASC, + CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers DESC' THEN s."NumberOfFlaggedAnswers" END DESC, + CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered ASC' THEN s."NumberOfQuestionsAnswered" END ASC, + CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered DESC' THEN s."NumberOfQuestionsAnswered" END DESC, + CASE WHEN @sortExpression = 'MediaFilesCount ASC' THEN s."MediaFilesCount" END ASC, + CASE WHEN @sortExpression = 'MediaFilesCount DESC' THEN s."MediaFilesCount" END DESC, + CASE WHEN @sortExpression = 'NotesCount ASC' THEN s."NotesCount" END ASC, + CASE WHEN @sortExpression = 'NotesCount DESC' THEN s."NotesCount" END DESC, + CASE WHEN @sortExpression = 'MonitoringObserverStatus ASC' THEN mo."Status" END ASC, + CASE WHEN @sortExpression = 'MonitoringObserverStatus DESC' THEN mo."Status" END DESC + OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, + coalitionMemberId = req.CoalitionMemberId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), pageSize = req.PageSize, monitoringObserverId = req.MonitoringObserverId, @@ -298,7 +321,7 @@ OFFSET @offset ROWS questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, + dataSource = req.DataSource?.ToString(), sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; @@ -435,4 +458,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(FormSubmissionEntry.TimeSubmitted)} DESC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/FormSubmissionEntry.cs b/api/src/Feature.Form.Submissions/ListEntries/FormSubmissionEntry.cs index e8017dd5d..5358de4ea 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/FormSubmissionEntry.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/FormSubmissionEntry.cs @@ -29,17 +29,15 @@ public record FormSubmissionEntry public string ObserverName { get; init; } = default!; public string Email { get; init; } = default!; public string PhoneNumber { get; init; } = default!; + public string NgoName { get; init; } = default!; public string[] Tags { get; init; } = []; public int NumberOfQuestionsAnswered { get; init; } public int NumberOfFlaggedAnswers { get; init; } public int MediaFilesCount { get; init; } public int NotesCount { get; init; } - [JsonConverter(typeof(SmartEnumNameConverter))] public SubmissionFollowUpStatus FollowUpStatus { get; init; } - - [JsonConverter(typeof(SmartEnumNameConverter))] public MonitoringObserverStatus MonitoringObserverStatus { get; init; } public bool IsCompleted { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/Request.cs b/api/src/Feature.Form.Submissions/ListEntries/Request.cs index 32c5dec3b..ede8ac271 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Request.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Request.cs @@ -12,6 +12,8 @@ public class Request : BaseSortPaginatedRequest [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + [QueryParam] public DataSource DataSource { get; set; } = DataSource.Ngo; + [QueryParam] public string? SearchText { get; set; } [QueryParam] public FormType? FormTypeFilter { get; set; } @@ -43,6 +45,5 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file + [QueryParam] public Guid? CoalitionMemberId { get; set; } +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/Validator.cs b/api/src/Feature.Form.Submissions/ListEntries/Validator.cs index 0ad43acee..1f635214e 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Validator.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs b/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs index 4a6580ae8..f631b75fa 100644 --- a/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs +++ b/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs @@ -13,6 +13,8 @@ public class FormSubmissionsAggregateFilter public Guid? FormId { get; set; } + [QueryParam] public DataSource DataSource { get; set; } = DataSource.Ngo; + [QueryParam] public string? Level1Filter { get; set; } [QueryParam] public string? Level2Filter { get; set; } @@ -35,5 +37,7 @@ public class FormSubmissionsAggregateFilter [QueryParam] public bool? HasNotes { get; set; } [QueryParam] public bool? HasAttachments { get; set; } [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file + [QueryParam] public DateTime? FromDateFilter { get; set; } + [QueryParam] public DateTime? ToDateFilter { get; set; } + [QueryParam] public Guid? CoalitionMemberId { get; set; } +} diff --git a/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs b/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs index b16dcd42c..bee59e7fb 100644 --- a/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs @@ -27,11 +27,13 @@ public override async Task> ExecuteAsync(Request re await context.FormSubmissions .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs new file mode 100644 index 000000000..1bc9e99f5 --- /dev/null +++ b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs @@ -0,0 +1,20 @@ +using Ardalis.Specification; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.Form.Submissions.Specifications; + +public sealed class GetCoalitionFormSpecification : SingleResultSpecification +{ + public GetCoalitionFormSpecification(Guid electionRondId, Guid observerId, Guid formId) + { + Query.Where(x => x.ElectionRoundId == electionRondId) + .Where(x => x.Memberships.Any(cm => + cm.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId))) + .Where(x => x.FormAccess.Any(fa => + fa.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId) && fa.FormId == formId)); + + Query.Include(x => x.FormAccess).ThenInclude(x => x.Form).ThenInclude(x => x.ElectionRound); + + Query.Select(x => x.FormAccess.First(f => f.FormId == formId).Form); + } +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs deleted file mode 100644 index 826632012..000000000 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ardalis.Specification; - -namespace Feature.Form.Submissions.Specifications; - -public sealed class GetFormSpecification : SingleResultSpecification -{ - public GetFormSpecification(Guid electionRondId, Guid formId) - { - Query.Where(x => x.ElectionRoundId == electionRondId && x.Id == formId); - Query.Include(x => x.ElectionRound); - } -} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs index d9dfbd80a..e77544bcb 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs @@ -4,14 +4,16 @@ namespace Feature.Form.Submissions.Specifications; public sealed class GetFormSubmissionForObserverSpecification : Specification { - public GetFormSubmissionForObserverSpecification(Guid electionRoundId, Guid observerId, List? pollingStationIds) + public GetFormSubmissionForObserverSpecification(Guid electionRoundId, Guid observerId, + List? pollingStationIds) { Query.Where(x => - x.ElectionRoundId == electionRoundId && - x.MonitoringObserver.ObserverId == observerId) - .Where(x => pollingStationIds.Contains(x.PollingStationId), pollingStationIds != null && pollingStationIds.Any()); + x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId) + .Where(x => pollingStationIds.Contains(x.PollingStationId), + pollingStationIds != null && pollingStationIds.Any()); Query.Select(x => FormSubmissionModel.FromEntity(x)); - } } diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs index 169cecbd3..5cf81f325 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs @@ -9,7 +9,9 @@ public GetFormSubmissionSpecification(Guid electionRoundId, Guid pollingStationI Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } @@ -17,6 +19,7 @@ public GetFormSubmissionSpecification(Guid electionRoundId, Guid ngoId, Guid sub { Query.Where(x => x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId && x.Id == submissionId); } diff --git a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs new file mode 100644 index 000000000..d45dd6056 --- /dev/null +++ b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs @@ -0,0 +1,16 @@ +using Ardalis.Specification; + +namespace Feature.Form.Submissions.Specifications; + +public sealed class GetMonitoringNgoFormSpecification : SingleResultSpecification +{ + public GetMonitoringNgoFormSpecification(Guid electionRondId, Guid observerId, Guid formId) + { + Query.Where(x => + x.ElectionRoundId == electionRondId + && x.Id == formId && + x.MonitoringNgo.MonitoringObservers.Any(mo => + mo.ObserverId == observerId && mo.ElectionRoundId == electionRondId)); + Query.Include(x => x.ElectionRound); + } +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs index 1122644f3..452b9a9d2 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs @@ -7,6 +7,6 @@ public sealed class GetMonitoringObserverSpecification: SingleResultSpecificatio { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs b/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs index 266da77b8..c80867fb1 100644 --- a/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs @@ -27,16 +27,18 @@ public override async Task> ExecuteAsync(Request re await context.PollingStationInformation .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); await context.FormSubmissions .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs b/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs index 9ed717af8..c67a5fb0c 100644 --- a/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Mappers; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormAnswerBase; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; @@ -10,6 +11,7 @@ public class Endpoint( IRepository repository, IReadRepository pollingStationRepository, IReadRepository monitoringObserverRepository, + IReadRepository coalitionRepository, IReadRepository formRepository, IAuthorizationService authorizationService) : Endpoint, NotFound>> { @@ -37,8 +39,13 @@ public override async Task, NotFound>> ExecuteAs return TypedResults.NotFound(); } - var formSpecification = new GetFormSpecification(req.ElectionRoundId, req.FormId); - var form = await formRepository.FirstOrDefaultAsync(formSpecification, ct); + var coalitionFormSpecification = + new GetCoalitionFormSpecification(req.ElectionRoundId, req.ObserverId, req.FormId); + var ngoFormSpecification = + new GetMonitoringNgoFormSpecification(req.ElectionRoundId, req.ObserverId, req.FormId); + + var form = (await coalitionRepository.FirstOrDefaultAsync(coalitionFormSpecification, ct)) ?? + (await formRepository.FirstOrDefaultAsync(ngoFormSpecification, ct)); if (form is null) { return TypedResults.NotFound(); @@ -116,4 +123,4 @@ private void ValidateAnswers(List answers, FormAggregate form) ThrowIfAnyErrors(); } } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Validators/FormSubmissionsAggregateFilterValidator.cs b/api/src/Feature.Form.Submissions/Validators/FormSubmissionsAggregateFilterValidator.cs index 3e2641d5f..97bb54f24 100644 --- a/api/src/Feature.Form.Submissions/Validators/FormSubmissionsAggregateFilterValidator.cs +++ b/api/src/Feature.Form.Submissions/Validators/FormSubmissionsAggregateFilterValidator.cs @@ -8,5 +8,6 @@ public FormSubmissionsAggregateFilterValidator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs b/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs index 413be0a42..e9ee87828 100644 --- a/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs +++ b/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.AddTranslations; -public class Endpoint(IRepository
repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Create/Endpoint.cs b/api/src/Feature.FormTemplates/Create/Endpoint.cs index a6dd83b39..f692eca6a 100644 --- a/api/src/Feature.FormTemplates/Create/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Create/Endpoint.cs @@ -3,7 +3,7 @@ namespace Feature.FormTemplates.Create; -public class Endpoint(IRepository repository) : +public class Endpoint(IRepository repository) : Endpoint, Conflict>> { public override void Configure() @@ -27,7 +27,7 @@ public override async Task, Conflict repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs b/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs index 081eefda9..8b226c4c2 100644 --- a/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs +++ b/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.DeleteTranslation; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Draft/Endpoint.cs b/api/src/Feature.FormTemplates/Draft/Endpoint.cs index 93dc27255..f48bd1b27 100644 --- a/api/src/Feature.FormTemplates/Draft/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Draft/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.Draft; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Get/Endpoint.cs b/api/src/Feature.FormTemplates/Get/Endpoint.cs index cb2fce8b0..e50b23ffe 100644 --- a/api/src/Feature.FormTemplates/Get/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Get/Endpoint.cs @@ -7,7 +7,7 @@ namespace Feature.FormTemplates.Get; public class Endpoint( - IReadRepository repository, + IReadRepository repository, ICurrentUserRoleProvider userRoleProvider, IAuthorizationService authorizationService) : Endpoint, NotFound>> { diff --git a/api/src/Feature.FormTemplates/GlobalUsings.cs b/api/src/Feature.FormTemplates/GlobalUsings.cs index 59120cf13..c8b58fa05 100644 --- a/api/src/Feature.FormTemplates/GlobalUsings.cs +++ b/api/src/Feature.FormTemplates/GlobalUsings.cs @@ -8,4 +8,4 @@ global using Microsoft.AspNetCore.Http.HttpResults; global using Vote.Monitor.Domain.Repository; global using Vote.Monitor.Form.Module.Mappers; -global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; +global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate; diff --git a/api/src/Feature.FormTemplates/Publish/Endpoint.cs b/api/src/Feature.FormTemplates/Publish/Endpoint.cs index 71b7bce23..53ab779d6 100644 --- a/api/src/Feature.FormTemplates/Publish/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Publish/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.Publish; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs b/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs index bf246c1d4..a180dc1e6 100644 --- a/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs +++ b/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.SetDefaultLanguage; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Update/Endpoint.cs b/api/src/Feature.FormTemplates/Update/Endpoint.cs index 32dc5475d..a181c2ac4 100644 --- a/api/src/Feature.FormTemplates/Update/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Update/Endpoint.cs @@ -3,7 +3,7 @@ namespace Feature.FormTemplates.Update; -public class Endpoint(IRepository repository) : Endpoint>> +public class Endpoint(IRepository repository) : Endpoint>> { public override void Configure() { diff --git a/api/src/Feature.Forms/Create/Endpoint.cs b/api/src/Feature.Forms/Create/Endpoint.cs index 6d8b9423b..7582a0b98 100644 --- a/api/src/Feature.Forms/Create/Endpoint.cs +++ b/api/src/Feature.Forms/Create/Endpoint.cs @@ -1,5 +1,6 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; diff --git a/api/src/Feature.Forms/FetchAll/Endpoint.cs b/api/src/Feature.Forms/FetchAll/Endpoint.cs index 434d9c6f0..27c5cf35f 100644 --- a/api/src/Feature.Forms/FetchAll/Endpoint.cs +++ b/api/src/Feature.Forms/FetchAll/Endpoint.cs @@ -1,11 +1,13 @@ using Feature.Forms.Models; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Vote.Monitor.Core.Helpers; using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.FormAggregate; namespace Feature.Forms.FetchAll; -public class Endpoint(VoteMonitorContext context, IMemoryCache cache) : Endpoint, NotFound>> + +public class Endpoint(VoteMonitorContext context) + : Endpoint, NotFound>> { public override void Configure() { @@ -20,17 +22,25 @@ public override void Configure() }); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) { var monitoringNgo = await context.MonitoringObservers .Include(x => x.MonitoringNgo) + .ThenInclude(x => x.Memberships.Where(m => m.ElectionRoundId == req.ElectionRoundId)) + .ThenInclude(cm => cm.Coalition) .Where(x => x.ObserverId == req.ObserverId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Select(x => new { x.MonitoringNgo.ElectionRoundId, x.MonitoringNgoId, - x.MonitoringNgo.FormsVersion + x.MonitoringNgo.FormsVersion, + IsCoalitionLeader = + x.MonitoringNgo.Memberships.Any(m => + m.ElectionRoundId == req.ElectionRoundId && m.Coalition.LeaderId == x.MonitoringNgoId), + IsInACoalition = x.MonitoringNgo.Memberships.Count != 0 }) .FirstOrDefaultAsync(ct); @@ -39,28 +49,35 @@ public override async Task, NotFound>> Execute return TypedResults.NotFound(); } - var cacheKey = $"election-rounds/{req.ElectionRoundId}/monitoring-ngo/{monitoringNgo.MonitoringNgoId}/forms/{monitoringNgo.FormsVersion}"; + List resultForms = new List(); + if (monitoringNgo.IsInACoalition || monitoringNgo.IsCoalitionLeader) + { + resultForms.AddRange(await context.CoalitionFormAccess + .Include(x => x.Form) + .Where(x => x.MonitoringNgoId == monitoringNgo.MonitoringNgoId && x.Coalition.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.Form.FormType != FormType.CitizenReporting) + .Select(f => FormFullModel.FromEntity(f.Form)) + .AsNoTracking() + .ToListAsync(ct)); + } - var cachedResponse = await cache.GetOrCreateAsync(cacheKey, async (e) => + if (!monitoringNgo.IsCoalitionLeader) { - var forms = await context.Forms + resultForms.AddRange(await context.Forms .Where(x => x.Status == FormStatus.Published) .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgoId == monitoringNgo.MonitoringNgoId) - .Where(x=>x.FormType != FormType.CitizenReporting) - .OrderBy(x => x.Code) - .ToListAsync(cancellationToken: ct); - - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + .Where(x => x.FormType != FormType.CitizenReporting) + .Select(f => FormFullModel.FromEntity(f)) + .AsNoTracking() + .ToListAsync(cancellationToken: ct)); + } - return new NgoFormsResponseModel - { - ElectionRoundId = monitoringNgo.ElectionRoundId, - Version = monitoringNgo.FormsVersion.ToString(), - Forms = forms.Select(FormFullModel.FromEntity).ToList() - }; + return TypedResults.Ok(new NgoFormsResponseModel + { + ElectionRoundId = monitoringNgo.ElectionRoundId, + Version = DeterministicGuid.Create(resultForms.Select(x => x.Id)).ToString(), + Forms = resultForms }); - - return TypedResults.Ok(cachedResponse!); } } diff --git a/api/src/Feature.Forms/FormFeatureInstaller.cs b/api/src/Feature.Forms/FormFeatureInstaller.cs index 385513a16..59c39b2f5 100644 --- a/api/src/Feature.Forms/FormFeatureInstaller.cs +++ b/api/src/Feature.Forms/FormFeatureInstaller.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using Dapper; +using Feature.Forms.Models; +using Microsoft.Extensions.DependencyInjection; +using Vote.Monitor.Core.Converters; namespace Feature.Forms; @@ -6,6 +9,8 @@ public static class FormFeatureInstaller { public static IServiceCollection AddFormFeature(this IServiceCollection services) { + SqlMapper.AddTypeHandler(typeof(FormAccessModel[]), new JsonToObjectConverter()); + return services; } } diff --git a/api/src/Feature.Forms/FromForm/Endpoint.cs b/api/src/Feature.Forms/FromForm/Endpoint.cs index 5f701965c..8c86209d3 100644 --- a/api/src/Feature.Forms/FromForm/Endpoint.cs +++ b/api/src/Feature.Forms/FromForm/Endpoint.cs @@ -1,9 +1,9 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; -using Vote.Monitor.Form.Module.Mappers; namespace Feature.Forms.FromForm; diff --git a/api/src/Feature.Forms/FromForm/Validator.cs b/api/src/Feature.Forms/FromForm/Validator.cs index c3f91c31e..288fd1f82 100644 --- a/api/src/Feature.Forms/FromForm/Validator.cs +++ b/api/src/Feature.Forms/FromForm/Validator.cs @@ -1,5 +1,4 @@ using Vote.Monitor.Core.Validators; -using Vote.Monitor.Form.Module.Validators; namespace Feature.Forms.FromForm; diff --git a/api/src/Feature.Forms/FromTemplate/Endpoint.cs b/api/src/Feature.Forms/FromTemplate/Endpoint.cs index 9da8ca2d6..a93c2f90d 100644 --- a/api/src/Feature.Forms/FromTemplate/Endpoint.cs +++ b/api/src/Feature.Forms/FromTemplate/Endpoint.cs @@ -1,5 +1,6 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; @@ -9,7 +10,7 @@ namespace Feature.Forms.FromTemplate; public class Endpoint( IAuthorizationService authorizationService, - IReadRepository formTemplateRepository, + IReadRepository formTemplateRepository, IRepository monitoringNgoRepository, IRepository formsRepository) : Endpoint, NotFound>> { diff --git a/api/src/Feature.Forms/Get/Endpoint.cs b/api/src/Feature.Forms/Get/Endpoint.cs index 50585c038..b668acbbc 100644 --- a/api/src/Feature.Forms/Get/Endpoint.cs +++ b/api/src/Feature.Forms/Get/Endpoint.cs @@ -1,12 +1,16 @@ using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using GetCoalitionFormSpecification = Feature.Forms.Specifications.GetCoalitionFormSpecification; namespace Feature.Forms.Get; public class Endpoint( IAuthorizationService authorizationService, - IReadRepository repository) : Endpoint, NotFound>> + IReadRepository formRepository, + IReadRepository coalitionRepository) : Endpoint, NotFound>> { public override void Configure() { @@ -24,10 +28,13 @@ public override async Task, NotFound>> ExecuteAsync(Re return TypedResults.NotFound(); } - FormAggregate? form = null; + var coalitionFormSpecification = + new GetCoalitionFormSpecification(req.ElectionRoundId, req.NgoId, req.Id); + var ngoFormSpecification = + new GetFormByIdSpecification(req.ElectionRoundId, req.NgoId, req.Id); - var specification = new GetFormByIdSpecification(req.ElectionRoundId, req.NgoId, req.Id); - form = await repository.FirstOrDefaultAsync(specification, ct); + var form = (await coalitionRepository.FirstOrDefaultAsync(coalitionFormSpecification, ct)) ?? + (await formRepository.FirstOrDefaultAsync(ngoFormSpecification, ct)); if (form is null) { diff --git a/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs b/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs index b82efac0f..21b6cdacd 100644 --- a/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs +++ b/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs @@ -18,11 +18,13 @@ public override void Configure() }); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) { var monitoringNgo = await context.MonitoringObservers - .Include(x=>x.MonitoringNgo) - .Where(x=>x.ObserverId == req.ObserverId) + .Include(x => x.MonitoringNgo) + .Where(x => x.ObserverId == req.ObserverId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Select(x => new { x.MonitoringNgo.FormsVersion, x.MonitoringNgo.ElectionRoundId }) .FirstOrDefaultAsync(ct); @@ -34,8 +36,7 @@ public override async Task, NotFound>> Exec return TypedResults.Ok(new FormVersionResponseModel { - ElectionRoundId = monitoringNgo.ElectionRoundId, - CacheKey = monitoringNgo.FormsVersion.ToString() + ElectionRoundId = monitoringNgo.ElectionRoundId, CacheKey = monitoringNgo.FormsVersion.ToString() }); } } diff --git a/api/src/Feature.Forms/List/Endpoint.cs b/api/src/Feature.Forms/List/Endpoint.cs index f0ec7d6af..43d904ee5 100644 --- a/api/src/Feature.Forms/List/Endpoint.cs +++ b/api/src/Feature.Forms/List/Endpoint.cs @@ -1,6 +1,7 @@ using Authorization.Policies; using Authorization.Policies.Requirements; using Dapper; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.ConnectionFactory; @@ -32,90 +33,322 @@ public override async Task>, NotFound>> } var sql = """ - SELECT COUNT(*) COUNT - FROM "Forms" F - INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" - WHERE ( - @searchText IS NULL - OR @searchText = '' - OR F."Code" ILIKE @searchText - OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText - OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + WITH + "MonitoringNgoData" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader", + -- Check if MonitoringNgo is in a coalition + ( + SELECT + COUNT(1) + FROM + "CoalitionMemberships" CM + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + ) > 0 AS "IsInACoalition" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 ) - AND (@type IS NULL OR F."FormType" = @type) - AND (@status IS NULL OR F."Status" = @status) - AND F."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId; + SELECT + COUNT (DISTINCT "Id") COUNT + FROM + ( + SELECT + F."Id" + FROM + "CoalitionFormAccess" CFA + JOIN "Forms" F ON CFA."FormId" = F."Id" + JOIN "Coalitions" C ON CFA."CoalitionId" = C."Id" + WHERE + CFA."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND C."ElectionRoundId" = @electionRoundId + AND ( + ( + SELECT + "IsInACoalition" + FROM + "MonitoringNgoData" + ) + OR ( + SELECT + "IsCoalitionLeader" + FROM + "MonitoringNgoData" + ) + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + UNION + SELECT + F."Id" + FROM + "Forms" F + WHERE + F."ElectionRoundId" = @electionRoundId + AND F."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + ) F; - SELECT F."Id", - F."Code", - F."Name", - F."Description", - F."DefaultLanguage", - F."Languages", - F."Status", - F."FormType", - F."NumberOfQuestions", - F."LanguagesTranslationStatus", - F."Icon", - F."LastModifiedOn", - F."LastModifiedBy" - FROM (SELECT F."Id", - F."Code", - F."Name", - F."Description", - F."DefaultLanguage", - F."Languages", - F."Status", - F."FormType", - F."NumberOfQuestions", - F."LanguagesTranslationStatus", - F."Icon", - COALESCE(F."LastModifiedOn", F."CreatedOn") as "LastModifiedOn", - COALESCE(UPDATER."FirstName" || ' ' || UPDATER."LastName", - CREATOR."FirstName" || ' ' || CREATOR."LastName") AS "LastModifiedBy" - FROM "Forms" F - INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" - INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" - LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" - Where F."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId) F - WHERE ( - @searchText IS NULL - OR @searchText = '' - OR F."Code" ILIKE @searchText - OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText - OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + WITH + "MonitoringNgoData" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader", + -- Check if MonitoringNgo is in a coalition + ( + SELECT + COUNT(1) + FROM + "CoalitionMemberships" CM + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + ) > 0 AS "IsInACoalition" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 ) - AND (@type IS NULL OR F."FormType" = @type) - AND (@status IS NULL OR F."Status" = @status) - - ORDER BY CASE - WHEN @sortExpression = 'Code ASC' THEN "Code" - END ASC, - CASE - WHEN @sortExpression = 'Code DESC' THEN "Code" - END DESC, - CASE - WHEN @sortExpression = 'LastModifiedOn ASC' THEN "LastModifiedOn" - END ASC, - CASE - WHEN @sortExpression = 'LastModifiedOn DESC' THEN "LastModifiedOn" - END DESC, - CASE - WHEN @sortExpression = 'FormType ASC' THEN "FormType" - END ASC, - CASE - WHEN @sortExpression = 'FormType DESC' THEN "FormType" - END DESC, - CASE - WHEN @sortExpression = 'Status ASC' THEN "Status" - END ASC, - CASE - WHEN @sortExpression = 'Status DESC' THEN "Status" - END DESC - OFFSET @offset ROWS - FETCH NEXT @pageSize ROWS ONLY; + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + F."LastModifiedOn", + F."LastModifiedBy", + F."IsFormOwner", + CASE WHEN f."IsFormOwner" THEN COALESCE( + (SELECT JSONB_AGG( + JSONB_BUILD_OBJECT( + 'NgoId', + N."Id", + 'Name', + N."Name" + ) + ) + FROM "CoalitionFormAccess" cfa + inner join "Coalitions" c on c."Id" = cfa."CoalitionId" + inner join "MonitoringNgos" mn on cfa."MonitoringNgoId" = mn."Id" + inner join "Ngos" n on mn."NgoId" = n."Id" + WHERE c."ElectionRoundId" = @electionRoundId + AND cfa."FormId" = F."Id"), + '[]'::JSONB) ELSE '[]'::jsonb END AS "FormAccess" + FROM + ( + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + COALESCE(F."LastModifiedOn", F."CreatedOn") AS "LastModifiedOn", + COALESCE(UPDATER."DisplayName", CREATOR."DisplayName") AS "LastModifiedBy", + EXISTS ( + SELECT 1 + FROM "MonitoringNgoData" + WHERE "MonitoringNgoId" = f."MonitoringNgoId" + ) AS "IsFormOwner" + FROM + "CoalitionFormAccess" CFA + INNER JOIN "Coalitions" C ON CFA."CoalitionId" = C."Id" + INNER JOIN "Forms" F ON CFA."FormId" = F."Id" + INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" + LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" + WHERE + CFA."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND C."ElectionRoundId" = @electionRoundId + AND ( + (SELECT "IsInACoalition" FROM "MonitoringNgoData") + OR (SELECT "IsCoalitionLeader" FROM "MonitoringNgoData") + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + UNION + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + COALESCE(F."LastModifiedOn", F."CreatedOn") AS "LastModifiedOn", + COALESCE(UPDATER."DisplayName", CREATOR."DisplayName") AS "LastModifiedBy", + true as "IsFormOwner" + FROM + "Forms" F + INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" + LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" + WHERE + F."ElectionRoundId" = @electionRoundId + AND F."MonitoringNgoId" = (SELECT "MonitoringNgoId" FROM "MonitoringNgoData") + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText + ) + AND (@type IS NULL OR F."FormType" = @type) + AND (@status IS NULL OR F."Status" = @status) + ) F + WHERE + ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + ORDER BY + CASE + WHEN @sortExpression = 'Code ASC' THEN "Code" + END ASC, + CASE + WHEN @sortExpression = 'Code DESC' THEN "Code" + END DESC, + CASE + WHEN @sortExpression = 'LastModifiedOn ASC' THEN "LastModifiedOn" + END ASC, + CASE + WHEN @sortExpression = 'LastModifiedOn DESC' THEN "LastModifiedOn" + END DESC, + CASE + WHEN @sortExpression = 'FormType ASC' THEN "FormType" + END ASC, + CASE + WHEN @sortExpression = 'FormType DESC' THEN "FormType" + END DESC, + CASE + WHEN @sortExpression = 'Status ASC' THEN "Status" + END ASC, + CASE + WHEN @sortExpression = 'Status DESC' THEN "Status" + END DESC + OFFSET + @offset + ROWS + FETCH NEXT + @pageSize ROWS ONLY; """; var queryArgs = new @@ -176,4 +409,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return "LastModifiedOn ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/Models/FormAccessModel.cs b/api/src/Feature.Forms/Models/FormAccessModel.cs new file mode 100644 index 000000000..d07a0b17b --- /dev/null +++ b/api/src/Feature.Forms/Models/FormAccessModel.cs @@ -0,0 +1,7 @@ +namespace Feature.Forms.Models; + +public class FormAccessModel +{ + public Guid NgoId { get; set; } + public string Name { get; set; } +} diff --git a/api/src/Feature.Forms/FormFullModel.cs b/api/src/Feature.Forms/Models/FormFullModel.cs similarity index 98% rename from api/src/Feature.Forms/FormFullModel.cs rename to api/src/Feature.Forms/Models/FormFullModel.cs index b5ada17ce..e5087c019 100644 --- a/api/src/Feature.Forms/FormFullModel.cs +++ b/api/src/Feature.Forms/Models/FormFullModel.cs @@ -5,7 +5,7 @@ using Vote.Monitor.Form.Module.Mappers; using Vote.Monitor.Form.Module.Models; -namespace Feature.Forms; +namespace Feature.Forms.Models; public class FormFullModel { @@ -49,4 +49,4 @@ public static FormFullModel FromEntity(FormAggregate form) => form == null public IReadOnlyList Questions { get; init; } = []; -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/FormSlimModel.cs b/api/src/Feature.Forms/Models/FormSlimModel.cs similarity index 88% rename from api/src/Feature.Forms/FormSlimModel.cs rename to api/src/Feature.Forms/Models/FormSlimModel.cs index 9e8be7846..01aa897bc 100644 --- a/api/src/Feature.Forms/FormSlimModel.cs +++ b/api/src/Feature.Forms/Models/FormSlimModel.cs @@ -3,7 +3,7 @@ using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.FormAggregate; -namespace Feature.Forms; +namespace Feature.Forms.Models; public class FormSlimModel { @@ -27,6 +27,9 @@ public class FormSlimModel public required DateTime LastModifiedOn { get; init; } public string LastModifiedBy { get; init; } + public bool IsFormOwner { get; init; } + + public FormAccessModel[] FormAccess { get; init; } public LanguagesTranslationStatus LanguagesTranslationStatus { get; init; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs new file mode 100644 index 000000000..1722a89ca --- /dev/null +++ b/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs @@ -0,0 +1,17 @@ +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.Forms.Specifications; + +public sealed class GetCoalitionFormSpecification : SingleResultSpecification +{ + public GetCoalitionFormSpecification(Guid electionRondId, Guid ngoId, Guid formId) + { + Query.Where(x => x.ElectionRoundId == electionRondId) + .Where(x => x.Memberships.Any(cm => cm.MonitoringNgo.NgoId == ngoId)) + .Where(x => x.FormAccess.Any(fa => fa.MonitoringNgo.NgoId == ngoId && fa.FormId == formId)); + + Query.Include(x => x.FormAccess).ThenInclude(x => x.Form).ThenInclude(x => x.ElectionRound); + + Query.Select(x => x.FormAccess.First(f => f.FormId == formId).Form); + } +} diff --git a/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs b/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs index 5732f4bde..fa98c6d97 100644 --- a/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs @@ -5,6 +5,9 @@ public sealed class GetFormByIdSpecification : SingleResultSpecification - x.MonitoringNgo.ElectionRoundId == electionRoundId && x.MonitoringNgo.NgoId == ngoId && x.Id == id); + x.MonitoringNgo.ElectionRoundId == electionRoundId + && x.MonitoringNgo.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.Id == id); } } diff --git a/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs index 5dee605be..02beaa78e 100644 --- a/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs @@ -8,6 +8,8 @@ public GetIncidentReportsForFormSpecification(Guid electionRoundId, Guid ngoId, { Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs index 8dbce52bf..5cbd03cc7 100644 --- a/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs @@ -8,6 +8,8 @@ public GetSubmissionsForFormSpecification(Guid electionRoundId, Guid ngoId, Guid { Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs b/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs index 747727452..4e7a2931f 100644 --- a/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs +++ b/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs @@ -44,7 +44,7 @@ await repository.FirstOrDefaultAsync( ElectionRoundId = attachment.ElectionRoundId, IncidentReportId = attachment.IncidentReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }); } } \ No newline at end of file diff --git a/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs b/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs index 5926a1eed..c345d7eb1 100644 --- a/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs +++ b/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs @@ -42,7 +42,7 @@ public override async Task ElectionRoundId = attachment.ElectionRoundId, IncidentReportId = attachment.IncidentReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs b/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs index e6d5b8f46..3f4b7b4b2 100644 --- a/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs +++ b/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs @@ -8,9 +8,10 @@ public ListIncidentReportAttachmentsSpecification(Guid electionRoundId, Guid inc { Query .Where(x => x.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.IncidentReportId == incidentReportId && !x.IsDeleted && x.IsCompleted); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs b/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs index e50b1e643..1143f9821 100644 --- a/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs +++ b/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs @@ -6,8 +6,8 @@ public sealed class ListNotesSpecification : Specification x.ElectionRoundId == electionRoundId && x.FormId == incidentReportId); + Query.Where(x => x.ElectionRoundId == electionRoundId && x.IncidentReportId == incidentReportId); Query.Select(note => IncidentReportNoteModel.FromEntity(note)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetById/Endpoint.cs b/api/src/Feature.IncidentReports/GetById/Endpoint.cs index 3dbe5607d..638485251 100644 --- a/api/src/Feature.IncidentReports/GetById/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetById/Endpoint.cs @@ -1,4 +1,7 @@ -namespace Feature.IncidentReports.GetById; +using AttachmentModel = Feature.IncidentReports.Models.AttachmentModel; +using NoteModel = Feature.IncidentReports.Models.NoteModel; + +namespace Feature.IncidentReports.GetById; public class Endpoint( IAuthorizationService authorizationService, @@ -35,6 +38,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId && x.Id == req.IncidentReportId) .Select(incidentReport => new @@ -54,12 +58,10 @@ public override async Task, NotFound>> ExecuteAsync(Request incidentReport.MonitoringObserver.Observer.ApplicationUser.LastName, TimeSubmitted = incidentReport.LastModifiedOn ?? incidentReport.CreatedOn, FollowUpStatus = incidentReport.FollowUpStatus, - LocationType = incidentReport.LocationType, LocationDescription = incidentReport.LocationDescription, - PollingStation = incidentReport.PollingStation, - IsCompleted = incidentReport.IsCompleted, + IsCompleted = incidentReport.IsCompleted }) .AsSplitQuery() .FirstOrDefaultAsync(ct); @@ -80,7 +82,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -96,15 +98,12 @@ public override async Task, NotFound>> ExecuteAsync(Request Notes = incidentReport.Notes.Select(NoteModel.FromEntity).ToArray(), Attachments = attachments, Questions = incidentReport.FormQuestions.Select(QuestionsMapper.ToModel).ToArray(), - MonitoringObserverId = incidentReport.MonitoringObserverId, ObserverName = incidentReport.ObserverName, TimeSubmitted = incidentReport.TimeSubmitted, FollowUpStatus = incidentReport.FollowUpStatus, - LocationType = incidentReport.LocationType, LocationDescription = incidentReport.LocationDescription, - PollingStationId = incidentReport.PollingStation?.Id, PollingStationLevel1 = incidentReport.PollingStation?.Level1, PollingStationLevel2 = incidentReport.PollingStation?.Level2, @@ -117,4 +116,4 @@ public override async Task, NotFound>> ExecuteAsync(Request return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetById/Response.cs b/api/src/Feature.IncidentReports/GetById/Response.cs index d7da310ec..de4124482 100644 --- a/api/src/Feature.IncidentReports/GetById/Response.cs +++ b/api/src/Feature.IncidentReports/GetById/Response.cs @@ -3,6 +3,8 @@ using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; using Vote.Monitor.Form.Module.Models; +using AttachmentModel = Feature.IncidentReports.Models.AttachmentModel; +using NoteModel = Feature.IncidentReports.Models.NoteModel; namespace Feature.IncidentReports.GetById; @@ -38,4 +40,4 @@ public class Response public BaseAnswerModel[] Answers { get; init; } = []; public NoteModel[] Notes { get; init; } = []; public AttachmentModel[] Attachments { get; init; } = []; -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs b/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs index 26cb0500f..402ed3884 100644 --- a/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs @@ -51,7 +51,7 @@ SELECT MIN(COALESCE(IR."LastModifiedOn", IR."CreatedOn")) AS "FirstSubmissionTim var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -67,7 +67,7 @@ SELECT MIN(COALESCE(IR."LastModifiedOn", IR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs index f0f81f91f..77f8db324 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs @@ -1,5 +1,7 @@ using Feature.IncidentReports.Requests; using Vote.Monitor.Answer.Module.Aggregators; +using AttachmentModel = Feature.IncidentReports.Models.AttachmentModel; +using NoteModel = Feature.IncidentReports.Models.NoteModel; namespace Feature.IncidentReports.GetSubmissionsAggregated; @@ -33,6 +35,7 @@ public override async Task, NotFound>> ExecuteAsync(Inciden var form = await context .Forms .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.MonitoringNgo.NgoId == req.NgoId && x.Id == req.FormId) .AsNoTracking() @@ -56,6 +59,8 @@ private async Task, NotFound>> AggregateIncidentReportsAsyn .Include(x => x.MonitoringObserver).ThenInclude(x => x.Observer).ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId) .Where(x => x.PollingStation != null && (string.IsNullOrWhiteSpace(req.Level1Filter) @@ -146,4 +151,4 @@ private async Task, NotFound>> AggregateIncidentReportsAsyn } }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Response.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Response.cs index ea7329251..17c8cafab 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Response.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Response.cs @@ -1,5 +1,7 @@ using Vote.Monitor.Answer.Module.Aggregators; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; +using AttachmentModel = Feature.IncidentReports.Models.AttachmentModel; +using NoteModel = Feature.IncidentReports.Models.NoteModel; namespace Feature.IncidentReports.GetSubmissionsAggregated; @@ -33,4 +35,4 @@ public class SubmissionsFilterModel public bool? HasAttachments { get; set; } public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs index a321733d7..679301dbc 100644 --- a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs @@ -31,14 +31,14 @@ public override async Task))] public IncidentReportFollowUpStatus? FollowUpStatus { get; init; } diff --git a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs index 94b16f705..36ed084d7 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs @@ -45,10 +45,10 @@ public override async Task>, AND (@monitoringObserverId IS NULL OR MO."Id" = @monitoringObserverId) AND (@searchText IS NULL OR @searchText = '' - OR U."FirstName" ILIKE @searchText - OR U."LastName" ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText ) AND (@level1 IS NULL OR PS."Level1" = @level1) AND (@level2 IS NULL OR PS."Level2" = @level2) @@ -100,8 +100,7 @@ OR U."PhoneNumber" ILIKE @searchText OR (@hasNotes = TRUE AND (SELECT COUNT(1) FROM "IncidentReportNotes" N WHERE N."IncidentReportId" = IR."Id") > 0 ) )) AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted); + AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp); WITH @@ -150,7 +149,6 @@ INCIDENT_REPORTS AS ( ) AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted) ) SELECT IR."IncidentReportId", @@ -168,7 +166,7 @@ INCIDENT_REPORTS AS ( PS."Level5", PS."Number" "PollingStationNumber", IR."MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" AS "ObserverName", + U."DisplayName" AS "ObserverName", U."Email", U."PhoneNumber", MO."Status", @@ -193,10 +191,10 @@ INCIDENT_REPORTS IR AND ( @searchText IS NULL OR @searchText = '' - OR U."FirstName" ILIKE @searchText - OR U."LastName" ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText ) AND (@level1 IS NULL OR PS."Level1" = @level1) AND (@level2 IS NULL OR PS."Level2" = @level2) @@ -235,8 +233,8 @@ ORDER BY CASE WHEN @sortExpression = 'Level5 DESC' THEN PS."Level5" END DESC, CASE WHEN @sortExpression = 'PollingStationNumber ASC' THEN PS."Number" END ASC, CASE WHEN @sortExpression = 'PollingStationNumber DESC' THEN PS."Number" END DESC, - CASE WHEN @sortExpression = 'ObserverName ASC' THEN U."FirstName" || ' ' || U."LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN U."FirstName" || ' ' || U."LastName" END DESC + CASE WHEN @sortExpression = 'ObserverName ASC' THEN U."DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN U."DisplayName" END DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; """; @@ -267,7 +265,6 @@ OFFSET @offset ROWS fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), - iscompleted = req.IsCompletedFilter }; int totalRowCount; @@ -367,4 +364,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(IncidentReportEntryModel.TimeSubmitted)} DESC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListEntries/Request.cs b/api/src/Feature.IncidentReports/ListEntries/Request.cs index acf4a9f85..03a622a43 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Request.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Request.cs @@ -41,5 +41,4 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListMy/Endpoint.cs b/api/src/Feature.IncidentReports/ListMy/Endpoint.cs index e65692f3e..38d019252 100644 --- a/api/src/Feature.IncidentReports/ListMy/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListMy/Endpoint.cs @@ -1,4 +1,7 @@ -namespace Feature.IncidentReports.ListMy; +using AttachmentModel = Feature.IncidentReports.Models.AttachmentModel; +using NoteModel = Feature.IncidentReports.Models.NoteModel; + +namespace Feature.IncidentReports.ListMy; public class Endpoint( IAuthorizationService authorizationService, @@ -35,6 +38,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId) .Select(incidentReport => new { @@ -55,7 +59,7 @@ public override async Task, NotFound>> ExecuteAsync(Request LocationType = incidentReport.LocationType, LocationDescription = incidentReport.LocationDescription, - PollingStation = incidentReport.PollingStation, + PollingStation = incidentReport.PollingStation }) .AsSplitQuery() .ToListAsync(ct); @@ -75,7 +79,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -103,4 +107,4 @@ public override async Task, NotFound>> ExecuteAsync(Request return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs b/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs index 07043f0fb..979a7cefa 100644 --- a/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs +++ b/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs @@ -23,10 +23,11 @@ public override async Task> ExecuteAsync(Request re await context.IncidentReports .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs b/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs index 8d143bca3..f1390cc08 100644 --- a/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs +++ b/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs @@ -6,8 +6,10 @@ public GetIncidentReportSpecification(Guid electionRoundId, Guid observerId, Gui { Query.Where(x => x.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId && x.Id == incidentReportId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs index 1d3c4045d..0c80cd805 100644 --- a/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs @@ -2,10 +2,12 @@ namespace Feature.IncidentReports.Specifications; -public sealed class GetMonitoringObserverSpecification: SingleResultSpecification +public sealed class GetMonitoringObserverSpecification : SingleResultSpecification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => + x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs b/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs index ea08512e1..8488550a8 100644 --- a/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs @@ -24,9 +24,11 @@ public override async Task> ExecuteAsync(Request re await context.IncidentReports .Where(x => x.Form.MonitoringNgo.NgoId == req.NgoId && x.ElectionRoundId == req.ElectionRoundId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgoId == x.Form.MonitoringNgoId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Locations/GetLocationsVersion/Endpoint.cs b/api/src/Feature.Locations/GetLocationsVersion/Endpoint.cs index bbd23bb7a..825c42a33 100644 --- a/api/src/Feature.Locations/GetLocationsVersion/Endpoint.cs +++ b/api/src/Feature.Locations/GetLocationsVersion/Endpoint.cs @@ -1,9 +1,6 @@ -using Authorization.Policies.Requirements; -using Microsoft.AspNetCore.Authorization; - namespace Feature.Locations.GetLocationsVersion; -public class Endpoint(IAuthorizationService authorizationService, VoteMonitorContext context) : Endpoint, NotFound>> +public class Endpoint(VoteMonitorContext context) : Endpoint, NotFound>> { public override void Configure() { @@ -15,17 +12,11 @@ public override void Configure() s.Summary = "Gets current version of locations for an election round"; s.Description = "Cache key changes every time any location changes"; }); + AllowAnonymous(); } public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) { - var requirement = new MonitoringObserverRequirement(req.ElectionRoundId); - var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); - if (!authorizationResult.Succeeded) - { - return TypedResults.NotFound(); - } - var electionRound = await context.ElectionRounds .Where(x => x.Id == req.ElectionRoundId) .Select(x => new { x.LocationsVersion, x.Id }) diff --git a/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs b/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs index c175868d5..fe91ece96 100644 --- a/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs +++ b/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs @@ -14,7 +14,7 @@ public GetMonitoringNgoSpecification(Guid electionRoundId) NgoId = x.NgoId, Name = x.Ngo.Name, NgoStatus = x.Ngo.Status, - MonitoringNgoStatus = x.Status, + MonitoringNgoStatus = x.Status }); } } diff --git a/api/src/Feature.MonitoringObservers/Assign/Request.cs b/api/src/Feature.MonitoringObservers/Assign/Request.cs index 14714b882..a600dfc46 100644 --- a/api/src/Feature.MonitoringObservers/Assign/Request.cs +++ b/api/src/Feature.MonitoringObservers/Assign/Request.cs @@ -3,7 +3,7 @@ public class Request { public Guid ElectionRoundId { get; set; } - public Guid ObserverId { get; set; } public Guid MonitoringNgoId { get; set; } + public Guid ObserverId { get; set; } } diff --git a/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs b/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs index f4520d6db..0d352482f 100644 --- a/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs @@ -34,6 +34,7 @@ await context .Where(x => req.MonitoringObserverIds.Contains(x.Id)) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.NgoId == req.NgoId) + .Where(x=>x.ElectionRoundId == req.ElectionRoundId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.Tags, b => Array.Empty()), cancellationToken: ct); return TypedResults.NoContent(); diff --git a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs index d50578435..5063539cd 100644 --- a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs @@ -1,13 +1,30 @@ using System.Globalization; +using System.Text.Json.Serialization; +using Ardalis.SmartEnum.SystemTextJson; using Authorization.Policies; using CsvHelper; using Dapper; using Vote.Monitor.Domain.ConnectionFactory; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; namespace Feature.MonitoringObservers.Export; public class Endpoint(INpgsqlConnectionFactory dbConnectionFactory) : Endpoint { + private class MonitoringObserverExportModel + { + public Guid Id { get; init; } + public string FirstName { get; init; } + public string LastName { get; init; } + public string Email { get; init; } + public string PhoneNumber { get; init; } + public string[] Tags { get; init; } + public DateTime? LatestActivityAt { get; init; } + + [JsonConverter(typeof(SmartEnumNameConverter))] + public MonitoringObserverStatus Status { get; init; } + } + public override void Configure() { Get("api/election-rounds/{electionRoundId}/monitoring-observers:export"); @@ -28,33 +45,29 @@ public override void Configure() public override async Task HandleAsync(Request req, CancellationToken ct) { var sql = """ - SELECT - MO."Id", - U."FirstName", - U."LastName", - U."PhoneNumber", - U."Email", - MO."Tags", - MO."Status" - FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - """; - - var queryArgs = new - { - electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, - }; - IEnumerable monitoringObservers = []; + SELECT + MO."Id", + U."FirstName", + U."LastName", + U."PhoneNumber", + U."Email", + MO."Tags", + MO."Status" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" + INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + """; + + var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId }; + IEnumerable monitoringObservers = []; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) { - monitoringObservers = await dbConnection.QueryAsync(sql, queryArgs); + monitoringObservers = await dbConnection.QueryAsync(sql, queryArgs); } var monitoringObserverModels = monitoringObservers.ToArray(); @@ -84,7 +97,8 @@ await SendBytesAsync(memoryStream.ToArray(), "monitoring-observers.csv", } } - private void WriteData(MonitoringObserverModel[] monitoringObservers, CsvWriter csvWriter, List availableTags) + private void WriteData(MonitoringObserverExportModel[] monitoringObservers, CsvWriter csvWriter, + List availableTags) { foreach (var monitoringObserver in monitoringObservers) { @@ -107,12 +121,12 @@ private void WriteData(MonitoringObserverModel[] monitoringObservers, CsvWriter private static void WriteHeader(CsvWriter csvWriter, List availableTags) { - csvWriter.WriteField(nameof(MonitoringObserverModel.Id)); - csvWriter.WriteField(nameof(MonitoringObserverModel.FirstName)); - csvWriter.WriteField(nameof(MonitoringObserverModel.LastName)); - csvWriter.WriteField(nameof(MonitoringObserverModel.PhoneNumber)); - csvWriter.WriteField(nameof(MonitoringObserverModel.Email)); - csvWriter.WriteField(nameof(MonitoringObserverModel.Status)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Id)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.FirstName)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.LastName)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.PhoneNumber)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Email)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Status)); foreach (var tag in availableTags) { @@ -133,5 +147,4 @@ private IReadOnlyList MergeTags(IReadOnlyList tags, List return result; } - } diff --git a/api/src/Feature.MonitoringObservers/Get/Endpoint.cs b/api/src/Feature.MonitoringObservers/Get/Endpoint.cs index 0a8f2f3e1..d7868d2a2 100644 --- a/api/src/Feature.MonitoringObservers/Get/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/Get/Endpoint.cs @@ -30,99 +30,84 @@ public override async Task, NotFound>> Execu return TypedResults.NotFound(); } - var sql = """ - WITH - MONITORINGOBSERVER AS ( - SELECT - MO."Id", - U."FirstName", - U."LastName", - U."PhoneNumber", - U."Email", - MO."Tags", - MO."Status" - FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" - WHERE - MO."Id" = @id - AND MO."ElectionRoundId" = @electionRoundId - ), - LATESTTIMESTAMPS AS ( - SELECT - MAX(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "LatestActivityAt" - FROM - "PollingStationInformation" PSI - WHERE - PSI."ElectionRoundId" = @electionRoundId - AND PSI."MonitoringObserverId" = ( - SELECT - "Id" - FROM - MONITORINGOBSERVER - ) - UNION ALL - SELECT - MAX(COALESCE(N."LastModifiedOn", N."CreatedOn")) AS "LatestActivityAt" - FROM - "Notes" N - WHERE - N."ElectionRoundId" = @electionRoundId - AND N."MonitoringObserverId" = ( - SELECT - "Id" - FROM - MONITORINGOBSERVER - ) - UNION ALL - SELECT - MAX(COALESCE(A."LastModifiedOn", A."CreatedOn")) AS "LatestActivityAt" - FROM - "Attachments" A - WHERE - A."ElectionRoundId" = @electionRoundId - AND A."MonitoringObserverId" = ( - SELECT - "Id" - FROM - MONITORINGOBSERVER - ) - UNION ALL - SELECT - MAX(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "LatestActivityAt" - FROM - "QuickReports" QR - WHERE - QR."ElectionRoundId" = @electionRoundId - AND QR."MonitoringObserverId" = ( - SELECT - "Id" - FROM - MONITORINGOBSERVER - ) - ) - SELECT - MO."Id", - MO."FirstName", - MO."LastName", - MO."PhoneNumber", - MO."Email", - MO."Tags", - MO."Status", - MAX(LT."LatestActivityAt") AS "LatestActivityAt" - FROM - MONITORINGOBSERVER MO, - LATESTTIMESTAMPS LT - GROUP BY - MO."Id", - MO."FirstName", - MO."LastName", - MO."PhoneNumber", - MO."Email", - MO."Tags", - MO."Status"; + var sql = + """ + WITH + MONITORINGOBSERVER AS ( + SELECT + "MonitoringObserverId" AS "Id", + "DisplayName", + "PhoneNumber", + "Email", + "Tags", + "Status", + "NgoName", + "IsOwnObserver" + FROM + "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, 'Coalition') + WHERE + "MonitoringObserverId" = @id + LIMIT 1 + ), + LATESTTIMESTAMPS AS ( + SELECT + MAX(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "LatestActivityAt" + FROM + "PollingStationInformation" PSI + WHERE + PSI."ElectionRoundId" = @electionRoundId + AND PSI."MonitoringObserverId" = (SELECT "Id" FROM MONITORINGOBSERVER) + + UNION ALL + + SELECT + MAX(COALESCE(N."LastModifiedOn", N."CreatedOn")) AS "LatestActivityAt" + FROM + "Notes" N + WHERE + N."ElectionRoundId" = @electionRoundId + AND N."MonitoringObserverId" = (SELECT "Id" FROM MONITORINGOBSERVER) + + UNION ALL + + SELECT + MAX(COALESCE(A."LastModifiedOn", A."CreatedOn")) AS "LatestActivityAt" + FROM + "Attachments" A + WHERE + A."ElectionRoundId" = @electionRoundId + AND A."MonitoringObserverId" = (SELECT "Id" FROM MONITORINGOBSERVER) + + UNION ALL + + SELECT + MAX(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "LatestActivityAt" + FROM + "QuickReports" QR + WHERE + QR."ElectionRoundId" = @electionRoundId + AND QR."MonitoringObserverId" = (SELECT "Id" FROM MONITORINGOBSERVER) + ) + SELECT + MO."Id", + MO."DisplayName", + MO."PhoneNumber", + MO."Email", + MO."Tags", + MO."Status", + MO."IsOwnObserver", + MAX(LT."LatestActivityAt") AS "LatestActivityAt" + FROM + MONITORINGOBSERVER MO + LEFT JOIN LATESTTIMESTAMPS LT ON TRUE + GROUP BY + MO."Id", + MO."DisplayName", + MO."PhoneNumber", + MO."Email", + MO."Tags", + MO."Status", + MO."IsOwnObserver"; """; var queryArgs = new diff --git a/api/src/Feature.MonitoringObservers/List/Endpoint.cs b/api/src/Feature.MonitoringObservers/List/Endpoint.cs index 7e2336188..a2d1778be 100644 --- a/api/src/Feature.MonitoringObservers/List/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/List/Endpoint.cs @@ -32,14 +32,18 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR U."DisplayName" ILIKE @searchText + OR U."Email" ILIKE @searchText + OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status); SELECT "Id", - "FirstName", - "LastName", + "DisplayName", "PhoneNumber", "Email", "Tags", @@ -48,8 +52,7 @@ SELECT COUNT(*) count FROM ( SELECT MO."Id", - U."FirstName", - U."LastName", + U."DisplayName", U."PhoneNumber", U."Email", MO."Tags", @@ -112,13 +115,17 @@ GROUP BY WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR U."DisplayName" ILIKE @searchText + OR U."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status) GROUP BY MO."Id", - U."FirstName", - U."LastName", + U."DisplayName", U."PhoneNumber", U."Email", MO."Tags", @@ -126,14 +133,8 @@ GROUP BY ) T ORDER BY - CASE WHEN @sortExpression = 'ObserverName ASC' THEN "FirstName" || ' ' || "LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN "FirstName" || ' ' || "LastName" END DESC, - - CASE WHEN @sortExpression = 'FirstName ASC' THEN "FirstName" END ASC, - CASE WHEN @sortExpression = 'FirstName DESC' THEN "FirstName" END DESC, - - CASE WHEN @sortExpression = 'LastName ASC' THEN "LastName" END ASC, - CASE WHEN @sortExpression = 'LastName DESC' THEN "LastName" END DESC, + CASE WHEN @sortExpression = 'DisplayName ASC' THEN "DisplayName" END ASC, + CASE WHEN @sortExpression = 'DisplayName DESC' THEN "DisplayName" END DESC, CASE WHEN @sortExpression = 'PhoneNumber ASC' THEN "PhoneNumber" END ASC, CASE WHEN @sortExpression = 'PhoneNumber DESC' THEN "PhoneNumber" END DESC, @@ -160,7 +161,7 @@ OFFSET @offset ROWS tagsFilter = req.Tags ?? [], searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", status = req.Status?.ToString(), - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; @@ -178,26 +179,16 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending { if (string.IsNullOrWhiteSpace(sortColumnName)) { - return "ObserverName ASC"; + return "DisplayName ASC"; } var sortOrder = isAscendingSorting ? "ASC" : "DESC"; - if (string.Equals(sortColumnName, "name", StringComparison.InvariantCultureIgnoreCase)) - { - return $"ObserverName {sortOrder}"; - } - - if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.FirstName), StringComparison.InvariantCultureIgnoreCase)) + if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.DisplayName), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(MonitoringObserverModel.FirstName)} {sortOrder}"; + return $"DisplayName {sortOrder}"; } - - if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.LastName), StringComparison.InvariantCultureIgnoreCase)) - { - return $"{nameof(MonitoringObserverModel.LastName)} {sortOrder}"; - } - + if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.Email), StringComparison.InvariantCultureIgnoreCase)) { return $"{nameof(MonitoringObserverModel.Email)} {sortOrder}"; diff --git a/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs b/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs index c21478d17..759b1639d 100644 --- a/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs @@ -26,6 +26,7 @@ public override async Task, NotFound>> ExecuteAsync(Request var tags = await context .MonitoringObservers + .Where(x=>x.ElectionRoundId == request.ElectionRoundId) .Where(x => x.MonitoringNgo.NgoId == request.NgoId && x.MonitoringNgo.ElectionRoundId == request.ElectionRoundId) .Where(x => x.Tags.Any()) diff --git a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs index 37659fffb..62aab18d3 100644 --- a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs +++ b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; -using Ardalis.SmartEnum.SystemTextJson; -using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; namespace Feature.MonitoringObservers; @@ -9,11 +7,11 @@ public class MonitoringObserverModel public Guid Id { get; init; } public string FirstName { get; init; } public string LastName { get; init; } + public string DisplayName { get; init; } public string Email { get; init; } public string PhoneNumber { get; init; } public string[] Tags { get; init; } + public bool IsOwnObserver { get; init; } public DateTime? LatestActivityAt { get; init; } - - [JsonConverter(typeof(SmartEnumNameConverter))] public MonitoringObserverStatus Status { get; init; } } diff --git a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs index b137f5680..364527967 100644 --- a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs +++ b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs @@ -5,7 +5,7 @@ public class MonitoringObserverImportModel public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } public string[] Tags { get; set; } = []; public override int GetHashCode() @@ -21,4 +21,4 @@ public override bool Equals(object obj) MonitoringObserverImportModel other = (MonitoringObserverImportModel)obj; return Email == other.Email; } -} \ No newline at end of file +} diff --git a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs index e422ad63f..c6960d8ae 100644 --- a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs +++ b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs @@ -10,7 +10,5 @@ public MonitoringObserverImportModelValidator() RuleFor(x => x.Email) .NotEmpty() .EmailAddress(); - - RuleFor(x => x.PhoneNumber).MinimumLength(3); } } diff --git a/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs b/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs index 6a9318488..f0b73333d 100644 --- a/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs @@ -12,12 +12,13 @@ namespace Feature.MonitoringObservers.ResendInvites; -public class Endpoint(IAuthorizationService authorizationService, +public class Endpoint( + IAuthorizationService authorizationService, VoteMonitorContext context, IJobService jobService, IEmailTemplateFactory emailFactory, IOptions apiConfig - ) : Endpoint> +) : Endpoint> { private readonly ApiConfiguration _apiConfig = apiConfig.Value; @@ -55,31 +56,33 @@ public override async Task> ExecuteAsync(Request re .FirstAsync(ct); var pendingInviteMonitoringObservers = await context.MonitoringObservers - .Include(x=>x.Observer) - .ThenInclude(x=>x.ApplicationUser) - .Where(x => x.MonitoringNgo.NgoId == req.NgoId) - .Where(x => x.ElectionRoundId == req.ElectionRoundId) - .Where(x => x.Status == MonitoringObserverStatus.Pending) - .Where(x => req.Ids.Count == 0 || req.Ids.Contains(x.Id)) - .Select(x => new - { - InvitationToken = x.Observer.ApplicationUser.InvitationToken, - FullName = x.Observer.ApplicationUser.FirstName + " " + x.Observer.ApplicationUser.LastName, - Email = x.Observer.ApplicationUser.Email - }).ToListAsync(ct); + .Include(x => x.Observer) + .ThenInclude(x => x.ApplicationUser) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.MonitoringNgo.NgoId == req.NgoId && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.Status == MonitoringObserverStatus.Pending) + .Where(x => req.Ids.Count == 0 || req.Ids.Contains(x.Id)) + .Select(x => new + { + InvitationToken = x.Observer.ApplicationUser.InvitationToken, + FullName = x.Observer.ApplicationUser.FirstName + " " + x.Observer.ApplicationUser.LastName, + Email = x.Observer.ApplicationUser.Email + }).ToListAsync(ct); foreach (var monitoringObserver in pendingInviteMonitoringObservers) { var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite")); - string acceptInviteUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", monitoringObserver.InvitationToken!); + string acceptInviteUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", + monitoringObserver.InvitationToken!); var invitationNewUserEmailProps = new InvitationNewUserEmailProps(FullName: monitoringObserver.FullName, CdnUrl: _apiConfig.WebAppUrl, AcceptUrl: acceptInviteUrl, NgoName: ngoName, ElectionRoundDetails: electionRoundName); - + var email = emailFactory.GenerateNewUserInvitationEmail(invitationNewUserEmailProps); jobService.EnqueueSendEmail(monitoringObserver.Email!, email.Subject, email.Body); } diff --git a/api/src/Feature.MonitoringObservers/Services/ObserverImportService.cs b/api/src/Feature.MonitoringObservers/Services/ObserverImportService.cs index 63c939c88..293790d58 100644 --- a/api/src/Feature.MonitoringObservers/Services/ObserverImportService.cs +++ b/api/src/Feature.MonitoringObservers/Services/ObserverImportService.cs @@ -101,21 +101,12 @@ public async Task ImportAsync(Guid electionRoundId, Guid ngoId, existingAccount.NewInvite(); var newObserver = ObserverAggregate.Create(existingAccount); var newMonitoringObserver = MonitoringObserverAggregate.CreateForExisting(electionRoundId, - monitoringNgo.Id, newObserver.Id, observer.Tags ?? []); + monitoringNgo.Id, newObserver.Id, observer.Tags ?? [], existingAccount.Status); await context.Observers.AddAsync(newObserver, ct); await context.MonitoringObservers.AddAsync(newMonitoringObserver, ct); - var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite")); - string acceptInviteUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", - existingAccount.InvitationToken!); - - var invitationNewUserEmailProps = new InvitationNewUserEmailProps(FullName: fullName, - CdnUrl: _apiConfig.WebAppUrl, - AcceptUrl: acceptInviteUrl, - NgoName: ngoName, - ElectionRoundDetails: electionRoundName); - - var email = emailFactory.GenerateNewUserInvitationEmail(invitationNewUserEmailProps); + var email = GenerateCreateAccountEmail(existingAccount.InvitationToken!, fullName, ngoName, + electionRoundName); jobService.EnqueueSendEmail(observer.Email, email.Subject, email.Body); } else @@ -123,13 +114,16 @@ public async Task ImportAsync(Guid electionRoundId, Guid ngoId, var newMonitoringObserver = MonitoringObserverAggregate.CreateForExisting(electionRoundId, monitoringNgo.Id, existingObserver.Id, - observer.Tags ?? []); + observer.Tags ?? [], + existingObserver.ApplicationUser.Status); await context.MonitoringObservers.AddAsync(newMonitoringObserver, ct); - var invitationExistingUserEmailProps = new InvitationExistingUserEmailProps(FullName: fullName, - CdnUrl: _apiConfig.WebAppUrl, NgoName: ngoName, ElectionRoundDetails: electionRoundName); - var email = emailFactory.GenerateInvitationExistingUserEmail(invitationExistingUserEmailProps); + var email = existingObserver.ApplicationUser.Status == UserStatus.Pending + ? GenerateCreateAccountEmail(existingObserver.ApplicationUser.InvitationToken!, + existingObserver.ApplicationUser.DisplayName, ngoName, electionRoundName) + : GenerateNotificationEmail(fullName, ngoName, electionRoundName); + jobService.EnqueueSendEmail(observer.Email, email.Subject, email.Body); } } @@ -151,15 +145,8 @@ public async Task ImportAsync(Guid electionRoundId, Guid ngoId, newObserver.Id, observer.Tags ?? []); await context.Observers.AddAsync(newObserver, ct); await context.MonitoringObservers.AddAsync(newMonitoringObserver, ct); - var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite")); - string acceptInviteUrl = - QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", user.InvitationToken!); - var invitationNewUserEmailProps = new InvitationNewUserEmailProps(FullName: fullName, - CdnUrl: _apiConfig.WebAppUrl, AcceptUrl: acceptInviteUrl, NgoName: ngoName, - ElectionRoundDetails: electionRoundName); - - var email = emailFactory.GenerateNewUserInvitationEmail(invitationNewUserEmailProps); + var email = GenerateCreateAccountEmail(user.InvitationToken!, fullName, ngoName, electionRoundName); jobService.EnqueueSendEmail(observer.Email, email.Subject, email.Body); } } @@ -167,9 +154,35 @@ public async Task ImportAsync(Guid electionRoundId, Guid ngoId, await context.SaveChangesAsync(ct); } + private EmailModel GenerateNotificationEmail(string fullName, string ngoName, string electionRoundName) + { + var invitationExistingUserEmailProps = new InvitationExistingUserEmailProps(FullName: fullName, + CdnUrl: _apiConfig.WebAppUrl, NgoName: ngoName, ElectionRoundDetails: electionRoundName); + + var email = emailFactory.GenerateInvitationExistingUserEmail(invitationExistingUserEmailProps); + return email; + } + + private EmailModel GenerateCreateAccountEmail(string invitationToken, string fullName, string ngoName, + string electionRoundName) + { + var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite")); + string acceptInviteUrl = + QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", invitationToken); + + var invitationNewUserEmailProps = new InvitationNewUserEmailProps(FullName: fullName, + CdnUrl: _apiConfig.WebAppUrl, + AcceptUrl: acceptInviteUrl, + NgoName: ngoName, + ElectionRoundDetails: electionRoundName); + + var email = emailFactory.GenerateNewUserInvitationEmail(invitationNewUserEmailProps); + return email; + } + private static string GetFullName(MonitoringObserverImportModel observer) { return observer.FirstName + " " + observer.LastName; } -} \ No newline at end of file +} diff --git a/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs b/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs index 38119380f..de2e00ad5 100644 --- a/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs +++ b/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs @@ -1,8 +1,11 @@ namespace Feature.MonitoringObservers.Specifications; + public sealed class GetMonitoringObserverForElectionRound : SingleResultSpecification { public GetMonitoringObserverForElectionRound(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ObserverId == observerId); + Query.Where(x => + x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ObserverId == observerId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs index b0fc00257..c6482c327 100644 --- a/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs @@ -2,10 +2,13 @@ using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; namespace Feature.Notes.Specifications; + public sealed class GetMonitoringObserverSpecification : Specification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId); + Query.Where(o => + o.ElectionRoundId == electionRoundId && o.ObserverId == observerId && + o.MonitoringNgo.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs b/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs index f8edf4788..028f87fe1 100644 --- a/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs @@ -7,6 +7,9 @@ public sealed class GetNoteByIdSpecification : SingleResultSpecification x.ElectionRoundId == electionRoundId && x.MonitoringObserver.ObserverId == observerId && x.Id == id); + .Where(x => x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId + && x.Id == id + && x.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs b/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs index 538c68d57..f6f1eb9b3 100644 --- a/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs @@ -10,6 +10,8 @@ public GetNotesSpecification(Guid electionRoundId, Guid pollingStationId, Guid o .Where(x => x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } } diff --git a/api/src/Feature.Notifications/Get/Endpoint.cs b/api/src/Feature.Notifications/Get/Endpoint.cs index 80aa147ed..34c2d7e75 100644 --- a/api/src/Feature.Notifications/Get/Endpoint.cs +++ b/api/src/Feature.Notifications/Get/Endpoint.cs @@ -33,7 +33,7 @@ public override async Task, NotFound>> Exe N."Title", N."Body", N."CreatedOn" "SentAt", - U."FirstName" || ' ' || U."LastName" "Sender", + U."DisplayName" "Sender", ( SELECT JSONB_AGG( @@ -41,7 +41,7 @@ public override async Task, NotFound>> Exe 'Id', "MonitoringObserverId", 'Name', - MOU."FirstName" || ' ' || MOU."LastName", + MOU."DisplayName", 'HasReadNotification', MON."IsRead" ) @@ -68,7 +68,7 @@ public override async Task, NotFound>> Exe { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - id = req.Id, + id = req.Id }; NotificationDetailedModel notification; @@ -80,4 +80,4 @@ public override async Task, NotFound>> Exe return notification == null ? TypedResults.NotFound() : TypedResults.Ok(notification); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListReceived/Endpoint.cs b/api/src/Feature.Notifications/ListReceived/Endpoint.cs index 042a2c3b8..54933cbfe 100644 --- a/api/src/Feature.Notifications/ListReceived/Endpoint.cs +++ b/api/src/Feature.Notifications/ListReceived/Endpoint.cs @@ -34,7 +34,7 @@ FETCH FIRST N."Id", N."Title", N."Body", - U."FirstName" || ' ' || U."LastName" "Sender", + U."DisplayName" "Sender", N."CreatedOn" "SentAt", MON."IsRead" FROM @@ -53,7 +53,7 @@ ORDER BY var queryArgs = new { electionRoundId = req.ElectionRoundId, - observerId = req.ObserverId, + observerId = req.ObserverId }; string? ngoName; diff --git a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs index 8b66f432d..14ee0d0ed 100644 --- a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs +++ b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs @@ -20,291 +20,567 @@ public override void Configure() public override async Task> ExecuteAsync(Request req, CancellationToken ct) { - var sql = """ - WITH "ObserverPSI" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'Some' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'None' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "PollingStationInformationForms" F ON f."ElectionRoundId" = @electionRoundId - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversFormSubmissions" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - - (SELECT COUNT(*) - FROM "Attachments" A - WHERE A."FormId" = fs."FormId" - AND a."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = A."PollingStationId" - AND A."IsDeleted" = FALSE - AND A."IsCompleted" = TRUE) AS "MediaFilesCount", - - (SELECT COUNT(*) - FROM "Notes" N - WHERE N."FormId" = fs."FormId" - AND N."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = N."PollingStationId") AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'Some' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'None' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "Forms" F ON FS."FormId" = F."Id" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversQuickReports" AS - (SELECT CAST(NULL AS UUID) AS "FormId", - NULL AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - qr."PollingStationId" AS "PollingStationId", - NULL AS "FollowUpStatus", - COALESCE(qr."LastModifiedOn", qr."CreatedOn") AS "LastModifiedOn", - CAST(NULL AS boolean) AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - NULL AS "QuestionsAnswered", - CAST(NULL AS boolean) AS "HasFlaggedAnswers", - qr."Id" AS "QuickReportId", - qr."IncidentCategory" AS "IncidentCategory", - qr."FollowUpStatus" AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversActivity" AS - (SELECT * - FROM "ObserversFormSubmissions" - UNION ALL SELECT * - FROM "ObserversQuickReports" - UNION ALL SELECT * - FROM "ObserverPSI") - SELECT COUNT(DISTINCT OA."ObserverId") COUNT - FROM "ObserversActivity" OA - INNER JOIN "MonitoringObservers" mo ON mo."Id" = OA."MonitoringObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = OA."ObserverId" - LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" - WHERE (@searchText IS NULL - OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText - OR U."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText - OR mo."Id"::text ILIKE @searchText) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formType IS NULL OR OA."FormType" = @formType) - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL OR OA."HasFlaggedAnswers" = @hasFlaggedAnswers) - AND (@submissionsFollowUpStatus IS NULL OR OA."FollowUpStatus" = @submissionsFollowUpStatus) - AND (@formId IS NULL OR OA."FormId" = @formId) - AND (@questionsAnswered IS NULL OR OA."QuestionsAnswered" = @questionsAnswered) - AND (@hasAttachments IS NULL OR (@hasAttachments = TRUE AND OA."MediaFilesCount" > 0) OR (@hasAttachments = FALSE AND OA."MediaFilesCount" = 0)) - AND (@hasNotes IS NULL OR (OA."NotesCount" = 0 AND @hasNotes = FALSE) OR (OA."NotesCount" > 0 AND @hasNotes = TRUE)) - AND (@fromDate IS NULL OR OA."LastModifiedOn" >= @fromDate::timestamp) - AND (@toDate IS NULL OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL OR OA."IsCompleted" = @isCompleted) - AND (@hasQuickReports IS NULL OR (@hasQuickReports = TRUE AND OA."QuickReportId" IS NOT NULL) OR (@hasQuickReports = FALSE AND OA."QuickReportId" IS NULL)) - AND (@quickReportFollowUpStatus IS NULL OR OA."QuickReportFollowUpStatus" = @quickReportFollowUpStatus) - AND (@quickReportIncidentCategory IS NULL OR OA."IncidentCategory" = @quickReportIncidentCategory); - - ------------------------------------------------------------------------------------------------- - WITH "ObserverPSI" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'Some' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'None' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "PollingStationInformationForms" F ON f."ElectionRoundId" = @electionRoundId - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversFormSubmissions" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - - (SELECT COUNT(*) - FROM "Attachments" A - WHERE A."FormId" = fs."FormId" - AND a."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = A."PollingStationId" - AND A."IsDeleted" = FALSE - AND A."IsCompleted" = TRUE) AS "MediaFilesCount", - - (SELECT COUNT(*) - FROM "Notes" N - WHERE N."FormId" = fs."FormId" - AND N."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = N."PollingStationId") AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'ALL' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'SOME' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'NONE' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "Forms" F ON FS."FormId" = F."Id" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversQuickReports" AS - (SELECT CAST(NULL AS UUID) AS "FormId", - NULL AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - qr."PollingStationId" AS "PollingStationId", - NULL AS "FollowUpStatus", - COALESCE(qr."LastModifiedOn", qr."CreatedOn") AS "LastModifiedOn", - CAST(NULL AS boolean) AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - NULL AS "QuestionsAnswered", - CAST(NULL AS boolean) AS "HasFlaggedAnswers", - qr."Id" AS "QuickReportId", - qr."IncidentCategory" AS "IncidentCategory", - qr."FollowUpStatus" AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversActivity" AS - (SELECT * - FROM "ObserversFormSubmissions" - UNION ALL SELECT * - FROM "ObserversQuickReports" - UNION ALL SELECT * - FROM "ObserverPSI"), - "FilteredObservers" AS - (SELECT DISTINCT OA."MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" "ObserverName", - U."PhoneNumber", - U."Email", - MO."Tags", - MO."Status" - FROM "ObserversActivity" OA - INNER JOIN "MonitoringObservers" mo ON mo."Id" = OA."MonitoringObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = OA."ObserverId" - LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" - WHERE (@searchText IS NULL - OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText - OR U."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText - OR mo."Id"::text ILIKE @searchText) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formType IS NULL OR OA."FormType" = @formType) - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL OR OA."HasFlaggedAnswers" = @hasFlaggedAnswers) - AND (@submissionsFollowUpStatus IS NULL OR OA."FollowUpStatus" = @submissionsFollowUpStatus) - AND (@formId IS NULL OR OA."FormId" = @formId) - AND (@questionsAnswered IS NULL OR OA."QuestionsAnswered" = @questionsAnswered) - AND (@hasAttachments IS NULL OR (@hasAttachments = TRUE AND OA."MediaFilesCount" > 0) OR (@hasAttachments = FALSE AND OA."MediaFilesCount" = 0)) - AND (@hasNotes IS NULL OR (OA."NotesCount" = 0 AND @hasNotes = FALSE) OR (OA."NotesCount" > 0 AND @hasNotes = TRUE)) - AND (@fromDate IS NULL OR OA."LastModifiedOn" >= @fromDate::timestamp) - AND (@toDate IS NULL OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL OR OA."IsCompleted" = @isCompleted) - AND (@hasQuickReports IS NULL OR (@hasQuickReports = TRUE AND OA."QuickReportId" IS NOT NULL) - OR (@hasQuickReports = FALSE AND OA."QuickReportId" IS NULL)) - AND (@quickReportFollowUpStatus IS NULL OR OA."QuickReportFollowUpStatus" = @quickReportFollowUpStatus) - AND (@quickReportIncidentCategory IS NULL OR OA."IncidentCategory" = @quickReportIncidentCategory)) - SELECT * - FROM "FilteredObservers" - ORDER BY CASE - WHEN @sortExpression = 'ObserverName ASC' THEN "ObserverName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN "ObserverName" END DESC, - CASE WHEN @sortExpression = 'PhoneNumber ASC' THEN "PhoneNumber" END ASC, - CASE WHEN @sortExpression = 'PhoneNumber DESC' THEN "PhoneNumber" END DESC, - CASE WHEN @sortExpression = 'Email ASC' THEN "Email" END ASC, - CASE WHEN @sortExpression = 'Email DESC' THEN "Email" END DESC, - CASE WHEN @sortExpression = 'Tags ASC' THEN "Tags" END ASC, - CASE WHEN @sortExpression = 'Tags DESC' THEN "Tags" END DESC, - CASE WHEN @sortExpression = 'Status ASC' THEN "Status" END ASC, - CASE WHEN @sortExpression = 'Status DESC' THEN "Status" END DESC - OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; - """; + var sql = + """ + WITH + "ObserverPSI" AS ( + SELECT + F."Id" AS "FormId", + F."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'Some' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'None' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "PollingStationInformationForms" F ON F."ElectionRoundId" = @ELECTIONROUNDID + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversFormSubmissions" AS ( + SELECT + AF."FormId" AS "FormId", + AF."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + ( + SELECT + COUNT(*) + FROM + "Attachments" A + WHERE + A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + ) AS "MediaFilesCount", + ( + SELECT + COUNT(*) + FROM + "Notes" N + WHERE + N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'Some' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'None' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, 'Coalition') AF ON AF."FormId" = FS."FormId" + INNER JOIN "Forms" F ON F."Id" = AF."FormId" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversQuickReports" AS ( + SELECT + CAST(NULL AS UUID) AS "FormId", + NULL AS "FormType", + MO."Id" AS "MonitoringObserverId", + QR."PollingStationId" AS "PollingStationId", + NULL AS "FollowUpStatus", + COALESCE(QR."LastModifiedOn", QR."CreatedOn") AS "LastModifiedOn", + CAST(NULL AS BOOLEAN) AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + NULL AS "QuestionsAnswered", + CAST(NULL AS BOOLEAN) AS "HasFlaggedAnswers", + QR."Id" AS "QuickReportId", + QR."IncidentCategory" AS "IncidentCategory", + QR."FollowUpStatus" AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversActivity" AS ( + SELECT + * + FROM + "ObserversFormSubmissions" + UNION ALL + SELECT + * + FROM + "ObserversQuickReports" + UNION ALL + SELECT + * + FROM + "ObserverPSI" + ) + SELECT + COUNT(DISTINCT MO."Id") COUNT + FROM + "MonitoringObservers" MO + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + INNER JOIN "MonitoringNgos" MN ON MO."MonitoringNgoId" = MN."Id" + LEFT JOIN "ObserversActivity" OA ON MO."Id" = OA."MonitoringObserverId" + LEFT JOIN "PollingStations" PS ON OA."PollingStationId" = PS."Id" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + AND ( + @SEARCHTEXT IS NULL + OR @SEARCHTEXT = '' + OR U."DisplayName" ILIKE @SEARCHTEXT + OR U."Email" ILIKE @SEARCHTEXT + OR U."PhoneNumber" ILIKE @SEARCHTEXT + OR MO."Id"::TEXT ILIKE @SEARCHTEXT + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR MO."Tags" && @TAGSFILTER + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMTYPE IS NULL + OR OA."FormType" = @FORMTYPE + ) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR PS."Number" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR OA."HasFlaggedAnswers" = @HASFLAGGEDANSWERS + ) + AND ( + @SUBMISSIONSFOLLOWUPSTATUS IS NULL + OR OA."FollowUpStatus" = @SUBMISSIONSFOLLOWUPSTATUS + ) + AND ( + @FORMID IS NULL + OR OA."FormId" = @FORMID + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR OA."QuestionsAnswered" = @QUESTIONSANSWERED + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + @HASATTACHMENTS = TRUE + AND OA."MediaFilesCount" > 0 + ) + OR ( + @HASATTACHMENTS = FALSE + AND OA."MediaFilesCount" = 0 + ) + ) + AND ( + @HASNOTES IS NULL + OR ( + OA."NotesCount" = 0 + AND @HASNOTES = FALSE + ) + OR ( + OA."NotesCount" > 0 + AND @HASNOTES = TRUE + ) + ) + AND ( + @FROMDATE IS NULL + OR OA."LastModifiedOn" >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR OA."LastModifiedOn" <= @TODATE::TIMESTAMP + ) + AND ( + @HASQUICKREPORTS IS NULL + OR ( + @HASQUICKREPORTS = TRUE + AND OA."QuickReportId" IS NOT NULL + ) + OR ( + @HASQUICKREPORTS = FALSE + AND OA."QuickReportId" IS NULL + ) + ) + AND ( + @QUICKREPORTFOLLOWUPSTATUS IS NULL + OR OA."QuickReportFollowUpStatus" = @QUICKREPORTFOLLOWUPSTATUS + ) + AND ( + @QUICKREPORTINCIDENTCATEGORY IS NULL + OR OA."IncidentCategory" = @QUICKREPORTINCIDENTCATEGORY + ); + + ------------------------------------------------------------------------------------------------- + WITH + "ObserverPSI" AS ( + SELECT + F."Id" AS "FormId", + F."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'Some' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'None' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "PollingStationInformationForms" F ON F."ElectionRoundId" = @ELECTIONROUNDID + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversFormSubmissions" AS ( + SELECT + AF."FormId" AS "FormId", + AF."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + ( + SELECT + COUNT(*) + FROM + "Attachments" A + WHERE + A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + ) AS "MediaFilesCount", + ( + SELECT + COUNT(*) + FROM + "Notes" N + WHERE + N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'ALL' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'SOME' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'NONE' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, 'Coalition') AF ON AF."FormId" = FS."FormId" + INNER JOIN "Forms" F ON AF."FormId" = F."Id" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversQuickReports" AS ( + SELECT + CAST(NULL AS UUID) AS "FormId", + NULL AS "FormType", + MO."Id" AS "MonitoringObserverId", + QR."PollingStationId" AS "PollingStationId", + NULL AS "FollowUpStatus", + COALESCE(QR."LastModifiedOn", QR."CreatedOn") AS "LastModifiedOn", + CAST(NULL AS BOOLEAN) AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + NULL AS "QuestionsAnswered", + CAST(NULL AS BOOLEAN) AS "HasFlaggedAnswers", + QR."Id" AS "QuickReportId", + QR."IncidentCategory" AS "IncidentCategory", + QR."FollowUpStatus" AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversActivity" AS ( + SELECT + * + FROM + "ObserversFormSubmissions" + UNION ALL + SELECT + * + FROM + "ObserversQuickReports" + UNION ALL + SELECT + * + FROM + "ObserverPSI" + ), + "FilteredObservers" AS ( + SELECT DISTINCT + MO."Id" AS "MonitoringObserverId", + U."DisplayName" "ObserverName", + U."PhoneNumber", + U."Email", + MO."Tags", + MO."Status" + FROM + "MonitoringObservers" MO + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + INNER JOIN "MonitoringNgos" MN ON MO."MonitoringNgoId" = MN."Id" + LEFT JOIN "ObserversActivity" OA ON MO."Id" = OA."MonitoringObserverId" + LEFT JOIN "PollingStations" PS ON OA."PollingStationId" = PS."Id" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + AND ( + @SEARCHTEXT IS NULL + OR @SEARCHTEXT = '' + OR (U."DisplayName") ILIKE @SEARCHTEXT + OR U."Email" ILIKE @SEARCHTEXT + OR U."PhoneNumber" ILIKE @SEARCHTEXT + OR MO."Id"::TEXT ILIKE @SEARCHTEXT + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR MO."Tags" && @TAGSFILTER + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMTYPE IS NULL + OR OA."FormType" = @FORMTYPE + ) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR PS."Number" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR OA."HasFlaggedAnswers" = @HASFLAGGEDANSWERS + ) + AND ( + @SUBMISSIONSFOLLOWUPSTATUS IS NULL + OR OA."FollowUpStatus" = @SUBMISSIONSFOLLOWUPSTATUS + ) + AND ( + @FORMID IS NULL + OR OA."FormId" = @FORMID + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR OA."QuestionsAnswered" = @QUESTIONSANSWERED + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + @HASATTACHMENTS = TRUE + AND OA."MediaFilesCount" > 0 + ) + OR ( + @HASATTACHMENTS = FALSE + AND OA."MediaFilesCount" = 0 + ) + ) + AND ( + @HASNOTES IS NULL + OR ( + OA."NotesCount" = 0 + AND @HASNOTES = FALSE + ) + OR ( + OA."NotesCount" > 0 + AND @HASNOTES = TRUE + ) + ) + AND ( + @FROMDATE IS NULL + OR OA."LastModifiedOn" >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR OA."LastModifiedOn" <= @TODATE::TIMESTAMP + ) + AND ( + @HASQUICKREPORTS IS NULL + OR ( + @HASQUICKREPORTS = TRUE + AND OA."QuickReportId" IS NOT NULL + ) + OR ( + @HASQUICKREPORTS = FALSE + AND OA."QuickReportId" IS NULL + ) + ) + AND ( + @QUICKREPORTFOLLOWUPSTATUS IS NULL + OR OA."QuickReportFollowUpStatus" = @QUICKREPORTFOLLOWUPSTATUS + ) + AND ( + @QUICKREPORTINCIDENTCATEGORY IS NULL + OR OA."IncidentCategory" = @QUICKREPORTINCIDENTCATEGORY + ) + ) + SELECT + * + FROM + "FilteredObservers" + ORDER BY + CASE + WHEN @SORTEXPRESSION = 'ObserverName ASC' THEN "ObserverName" + END ASC, + CASE + WHEN @SORTEXPRESSION = 'ObserverName DESC' THEN "ObserverName" + END DESC, + CASE + WHEN @SORTEXPRESSION = 'PhoneNumber ASC' THEN "PhoneNumber" + END ASC, + CASE + WHEN @SORTEXPRESSION = 'PhoneNumber DESC' THEN "PhoneNumber" + END DESC, + CASE + WHEN @SORTEXPRESSION = 'Email ASC' THEN "Email" + END ASC, + CASE + WHEN @SORTEXPRESSION = 'Email DESC' THEN "Email" + END DESC, + CASE + WHEN @SORTEXPRESSION = 'Tags ASC' THEN "Tags" + END ASC, + CASE + WHEN @SORTEXPRESSION = 'Tags DESC' THEN "Tags" + END DESC, + CASE + WHEN @SORTEXPRESSION = 'Status ASC' THEN "Status" + END ASC, + CASE + WHEN @SORTEXPRESSION = 'Status DESC' THEN "Status" + END DESC + OFFSET + @OFFSET ROWS + FETCH NEXT + @PAGESIZE ROWS ONLY; + """; var queryArgs = new { @@ -328,7 +604,6 @@ ORDER BY CASE questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, hasQuickReports = req.HasQuickReports, quickReportFollowUpStatus = req.QuickReportFollowUpStatus?.ToString(), @@ -337,7 +612,7 @@ ORDER BY CASE offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), pageSize = req.PageSize, - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; @@ -393,4 +668,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(TargetedMonitoringObserverModel.ObserverName)} ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListRecipients/Request.cs b/api/src/Feature.Notifications/ListRecipients/Request.cs index 795b258cb..563ba2e8d 100644 --- a/api/src/Feature.Notifications/ListRecipients/Request.cs +++ b/api/src/Feature.Notifications/ListRecipients/Request.cs @@ -43,9 +43,8 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } [QueryParam] public QuickReportFollowUpStatus? QuickReportFollowUpStatus { get; set; } [QueryParam] public IncidentCategory? QuickReportIncidentCategory { get; set; } [QueryParam] public bool? HasQuickReports { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListSent/Endpoint.cs b/api/src/Feature.Notifications/ListSent/Endpoint.cs index 4f66e8bf4..67140a9d8 100644 --- a/api/src/Feature.Notifications/ListSent/Endpoint.cs +++ b/api/src/Feature.Notifications/ListSent/Endpoint.cs @@ -35,7 +35,7 @@ public override async Task> ExecuteAsync(Reques N."Title", N."Body", N."CreatedOn" AS "SentAt", - U."FirstName" || ' ' || U."LastName" AS "Sender", + U."DisplayName" AS "Sender", COUNT(MON."NotificationId") AS "NumberOfTargetedObservers", SUM(CASE WHEN MON."IsRead" = TRUE THEN 1 ELSE 0 END) AS "NumberOfReadNotifications" FROM @@ -48,8 +48,10 @@ public override async Task> ExecuteAsync(Reques WHERE MN."NgoId" = @ngoId AND N."ElectionRoundId" = @electionRoundId + AND MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId GROUP BY - N."Id", N."Title", N."Body", N."CreatedOn", U."FirstName", U."LastName" + N."Id", N."Title", N."Body", N."CreatedOn", U."DisplayName" ORDER BY N."CreatedOn" DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; @@ -59,7 +61,7 @@ OFFSET @offset ROWS electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), - pageSize = req.PageSize, + pageSize = req.PageSize }; int totalRowCount; @@ -74,4 +76,4 @@ OFFSET @offset ROWS return new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Send/Endpoint.cs b/api/src/Feature.Notifications/Send/Endpoint.cs index 29cc57e25..63f85ec1f 100644 --- a/api/src/Feature.Notifications/Send/Endpoint.cs +++ b/api/src/Feature.Notifications/Send/Endpoint.cs @@ -26,171 +26,272 @@ public override void Configure() public override async Task, ProblemHttpResult>> ExecuteAsync(Request req, CancellationToken ct) { - var sql = """ - WITH "ObserverPSI" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'Some' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'None' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "PollingStationInformationForms" F ON f."ElectionRoundId" = @electionRoundId - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversFormSubmissions" AS - (SELECT f."Id" AS "FormId", - f."FormType" AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - fs."PollingStationId" AS "PollingStationId", - fs."FollowUpStatus" AS "FollowUpStatus", - COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", - fs."IsCompleted" AS "IsCompleted", - - (SELECT COUNT(*) - FROM "Attachments" A - WHERE A."FormId" = fs."FormId" - AND a."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = A."PollingStationId" - AND A."IsDeleted" = FALSE - AND A."IsCompleted" = TRUE) AS "MediaFilesCount", - - (SELECT COUNT(*) - FROM "Notes" N - WHERE N."FormId" = fs."FormId" - AND N."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = N."PollingStationId") AS "NotesCount", - (CASE - WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'ALL' - WHEN fs."NumberOfQuestionsAnswered" > 0 THEN 'SOME' - WHEN fs."NumberOfQuestionsAnswered" = 0 THEN 'NONE' - END) "QuestionsAnswered", - (CASE - WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE - ELSE FALSE - END) "HasFlaggedAnswers", - CAST(NULL AS UUID) AS "QuickReportId", - NULL AS "IncidentCategory", - NULL AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "Forms" F ON FS."FormId" = F."Id" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversQuickReports" AS - (SELECT CAST(NULL AS UUID) AS "FormId", - NULL AS "FormType", - mo."ObserverId" AS "ObserverId", - mo."Id" AS "MonitoringObserverId", - qr."PollingStationId" AS "PollingStationId", - NULL AS "FollowUpStatus", - COALESCE(qr."LastModifiedOn", qr."CreatedOn") AS "LastModifiedOn", - CAST(NULL AS boolean) AS "IsCompleted", - CAST(NULL AS bigint) AS "MediaFilesCount", - CAST(NULL AS bigint) AS "NotesCount", - NULL AS "QuestionsAnswered", - CAST(NULL AS boolean) AS "HasFlaggedAnswers", - qr."Id" AS "QuickReportId", - qr."IncidentCategory" AS "IncidentCategory", - qr."FollowUpStatus" AS "QuickReportFollowUpStatus" - FROM "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" - WHERE MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId), - "ObserversActivity" AS - (SELECT * - FROM "ObserversFormSubmissions" - UNION ALL SELECT * - FROM "ObserversQuickReports" - UNION ALL SELECT * - FROM "ObserverPSI") - SELECT DISTINCT OA."MonitoringObserverId", - NT."Token" - FROM "ObserversActivity" OA - INNER JOIN "MonitoringObservers" mo ON mo."Id" = OA."MonitoringObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = OA."ObserverId" - LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" - LEFT JOIN "NotificationTokens" NT ON NT."ObserverId" = OA."ObserverId" - WHERE (@searchText IS NULL - OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText - OR U."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText - OR mo."Id"::text ILIKE @searchText) - AND (@tagsFilter IS NULL - OR cardinality(@tagsFilter) = 0 - OR mo."Tags" && @tagsFilter) - AND (@monitoringObserverStatus IS NULL - OR mo."Status" = @monitoringObserverStatus) - AND (@formType IS NULL - OR OA."FormType" = @formType) - AND (@level1 IS NULL - OR ps."Level1" = @level1) - AND (@level2 IS NULL - OR ps."Level2" = @level2) - AND (@level3 IS NULL - OR ps."Level3" = @level3) - AND (@level4 IS NULL - OR ps."Level4" = @level4) - AND (@level5 IS NULL - OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL - OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL - OR OA."HasFlaggedAnswers" = @hasFlaggedAnswers) - AND (@submissionsFollowUpStatus IS NULL - OR OA."FollowUpStatus" = @submissionsFollowUpStatus) - AND (@formId IS NULL - OR OA."FormId" = @formId) - AND (@questionsAnswered IS NULL - OR OA."QuestionsAnswered" = @questionsAnswered) - AND (@hasAttachments IS NULL - OR (@hasAttachments = TRUE - AND OA."MediaFilesCount" > 0) - OR (@hasAttachments = FALSE - AND OA."MediaFilesCount" = 0)) - AND (@hasNotes IS NULL - OR (OA."NotesCount" = 0 - AND @hasNotes = FALSE) - OR (OA."NotesCount" > 0 - AND @hasNotes = TRUE)) - AND (@fromDate IS NULL - OR OA."LastModifiedOn" >= @fromDate::timestamp) - AND (@toDate IS NULL - OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL - OR OA."IsCompleted" = @isCompleted) - AND (@hasQuickReports IS NULL - OR (@hasQuickReports = TRUE - AND OA."QuickReportId" IS NOT NULL) - OR (@hasQuickReports = FALSE - AND OA."QuickReportId" IS NULL)) - AND (@quickReportFollowUpStatus IS NULL - OR OA."QuickReportFollowUpStatus" = @quickReportFollowUpStatus) - AND (@quickReportIncidentCategory IS NULL - OR OA."IncidentCategory" = @quickReportIncidentCategory) - """; + var sql = + """ + WITH + "ObserverPSI" AS ( + SELECT + F."Id" AS "FormId", + F."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'All' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'Some' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'None' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "PollingStationInformation" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "PollingStationInformationForms" F ON F."ElectionRoundId" = @ELECTIONROUNDID + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversFormSubmissions" AS ( + SELECT + AF."FormId" AS "FormId", + AF."FormType" AS "FormType", + MO."Id" AS "MonitoringObserverId", + FS."PollingStationId" AS "PollingStationId", + FS."FollowUpStatus" AS "FollowUpStatus", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") AS "LastModifiedOn", + FS."IsCompleted" AS "IsCompleted", + ( + SELECT + COUNT(*) + FROM + "Attachments" A + WHERE + A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = A."PollingStationId" + AND A."IsDeleted" = FALSE + AND A."IsCompleted" = TRUE + ) AS "MediaFilesCount", + ( + SELECT + COUNT(*) + FROM + "Notes" N + WHERE + N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ) AS "NotesCount", + ( + CASE + WHEN FS."NumberOfQuestionsAnswered" = F."NumberOfQuestions" THEN 'ALL' + WHEN FS."NumberOfQuestionsAnswered" > 0 THEN 'SOME' + WHEN FS."NumberOfQuestionsAnswered" = 0 THEN 'NONE' + END + ) "QuestionsAnswered", + ( + CASE + WHEN FS."NumberOfFlaggedAnswers" > 0 THEN TRUE + ELSE FALSE + END + ) "HasFlaggedAnswers", + CAST(NULL AS UUID) AS "QuickReportId", + NULL AS "IncidentCategory", + NULL AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "FormSubmissions" FS ON MO."Id" = FS."MonitoringObserverId" + INNER JOIN "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, 'Coalition') AF ON AF."FormId" = FS."FormId" + INNER JOIN "Forms" F ON AF."FormId" = F."Id" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversQuickReports" AS ( + SELECT + CAST(NULL AS UUID) AS "FormId", + NULL AS "FormType", + MO."Id" AS "MonitoringObserverId", + QR."PollingStationId" AS "PollingStationId", + NULL AS "FollowUpStatus", + COALESCE(QR."LastModifiedOn", QR."CreatedOn") AS "LastModifiedOn", + CAST(NULL AS BOOLEAN) AS "IsCompleted", + CAST(NULL AS BIGINT) AS "MediaFilesCount", + CAST(NULL AS BIGINT) AS "NotesCount", + NULL AS "QuestionsAnswered", + CAST(NULL AS BOOLEAN) AS "HasFlaggedAnswers", + QR."Id" AS "QuickReportId", + QR."IncidentCategory" AS "IncidentCategory", + QR."FollowUpStatus" AS "QuickReportFollowUpStatus" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "QuickReports" QR ON MO."Id" = QR."MonitoringObserverId" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + ), + "ObserversActivity" AS ( + SELECT + * + FROM + "ObserversFormSubmissions" + UNION ALL + SELECT + * + FROM + "ObserversQuickReports" + UNION ALL + SELECT + * + FROM + "ObserverPSI" + ), + "FilteredObservers" AS ( + SELECT DISTINCT + MO."Id" AS "MonitoringObserverId", + NT."Token" + FROM + "MonitoringObservers" MO + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + INNER JOIN "MonitoringNgos" MN ON MO."MonitoringNgoId" = MN."Id" + LEFT JOIN "ObserversActivity" OA ON MO."Id" = OA."MonitoringObserverId" + LEFT JOIN "PollingStations" PS ON OA."PollingStationId" = PS."Id" + LEFT JOIN "NotificationTokens" NT ON NT."ObserverId" = MO."ObserverId" + WHERE + MN."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + AND ( + @SEARCHTEXT IS NULL + OR @SEARCHTEXT = '' + OR (U."DisplayName") ILIKE @SEARCHTEXT + OR U."Email" ILIKE @SEARCHTEXT + OR U."PhoneNumber" ILIKE @SEARCHTEXT + OR MO."Id"::TEXT ILIKE @SEARCHTEXT + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR MO."Tags" && @TAGSFILTER + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMTYPE IS NULL + OR OA."FormType" = @FORMTYPE + ) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR PS."Number" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR OA."HasFlaggedAnswers" = @HASFLAGGEDANSWERS + ) + AND ( + @SUBMISSIONSFOLLOWUPSTATUS IS NULL + OR OA."FollowUpStatus" = @SUBMISSIONSFOLLOWUPSTATUS + ) + AND ( + @FORMID IS NULL + OR OA."FormId" = @FORMID + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR OA."QuestionsAnswered" = @QUESTIONSANSWERED + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + @HASATTACHMENTS = TRUE + AND OA."MediaFilesCount" > 0 + ) + OR ( + @HASATTACHMENTS = FALSE + AND OA."MediaFilesCount" = 0 + ) + ) + AND ( + @HASNOTES IS NULL + OR ( + OA."NotesCount" = 0 + AND @HASNOTES = FALSE + ) + OR ( + OA."NotesCount" > 0 + AND @HASNOTES = TRUE + ) + ) + AND ( + @FROMDATE IS NULL + OR OA."LastModifiedOn" >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR OA."LastModifiedOn" <= @TODATE::TIMESTAMP + ) + AND ( + @HASQUICKREPORTS IS NULL + OR ( + @HASQUICKREPORTS = TRUE + AND OA."QuickReportId" IS NOT NULL + ) + OR ( + @HASQUICKREPORTS = FALSE + AND OA."QuickReportId" IS NULL + ) + ) + AND ( + @QUICKREPORTFOLLOWUPSTATUS IS NULL + OR OA."QuickReportFollowUpStatus" = @QUICKREPORTFOLLOWUPSTATUS + ) + AND ( + @QUICKREPORTINCIDENTCATEGORY IS NULL + OR OA."IncidentCategory" = @QUICKREPORTINCIDENTCATEGORY + ) + ) + SELECT + * + FROM + "FilteredObservers" + """; var queryArgs = new { @@ -214,11 +315,10 @@ OR cardinality(@tagsFilter) = 0 questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, hasQuickReports = req.HasQuickReports, quickReportFollowUpStatus = req.QuickReportFollowUpStatus?.ToString(), - quickReportIncidentCategory = req.QuickReportIncidentCategory?.ToString(), + quickReportIncidentCategory = req.QuickReportIncidentCategory?.ToString() }; IEnumerable result = []; @@ -254,7 +354,7 @@ OR cardinality(@tagsFilter) = 0 return TypedResults.Ok(new Response { - Status = "Success", + Status = "Success" }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Send/Request.cs b/api/src/Feature.Notifications/Send/Request.cs index d7636af70..12064b2a9 100644 --- a/api/src/Feature.Notifications/Send/Request.cs +++ b/api/src/Feature.Notifications/Send/Request.cs @@ -49,8 +49,7 @@ public class Request public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } - public bool? IsCompletedFilter { get; set; } public QuickReportFollowUpStatus? QuickReportFollowUpStatus { get; set; } public IncidentCategory? QuickReportIncidentCategory { get; set; } public bool? HasQuickReports { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs index f244d43ca..de626ba5a 100644 --- a/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs @@ -8,8 +8,9 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid ngoId, List { Query .Where(x => - x.ElectionRoundId == electionRoundId - && x.MonitoringNgo.NgoId == ngoId - && monitoringObserverIds.Contains(x.Id)); + x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.MonitoringNgo.ElectionRoundId == electionRoundId + && monitoringObserverIds.Contains(x.Id)); } } diff --git a/api/src/Feature.ObserverGuide/GetById/Endpoint.cs b/api/src/Feature.ObserverGuide/GetById/Endpoint.cs index 07e10dc31..f04c93d2b 100644 --- a/api/src/Feature.ObserverGuide/GetById/Endpoint.cs +++ b/api/src/Feature.ObserverGuide/GetById/Endpoint.cs @@ -94,7 +94,7 @@ public override async Task, NotFound>> ExecuteAsy return TypedResults.Ok(citizenGuideModel with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }); } diff --git a/api/src/Feature.ObserverGuide/List/Endpoint.cs b/api/src/Feature.ObserverGuide/List/Endpoint.cs index cdf6dc9d8..7d15ab068 100644 --- a/api/src/Feature.ObserverGuide/List/Endpoint.cs +++ b/api/src/Feature.ObserverGuide/List/Endpoint.cs @@ -35,8 +35,7 @@ await authorizationService.AuthorizeAsync(User, var isNgoAdmin = currentUserRoleProvider.IsNgoAdmin(); var isObserver = currentUserRoleProvider.IsObserver(); - - + var observerId = currentUserProvider.GetUserId(); var ngoId = currentUserProvider.GetNgoId(); @@ -44,8 +43,8 @@ await authorizationService.AuthorizeAsync(User, var guides = await context .ObserversGuides .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted) - .Where(x => isObserver || x.MonitoringNgo.NgoId == ngoId) - .Where(x => isNgoAdmin || x.MonitoringNgo.MonitoringObservers.Any(mo => mo.ObserverId == observerId)) + .Where(x => isObserver || (x.MonitoringNgo.NgoId == ngoId && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId)) + .Where(x => isNgoAdmin || x.MonitoringNgo.MonitoringObservers.Any(mo => mo.ObserverId == observerId) && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .OrderByDescending(x => x.CreatedOn) .Join(context.NgoAdmins, guide => guide.LastModifiedBy == Guid.Empty ? guide.CreatedBy : guide.LastModifiedBy, user => user.Id, (guide, ngoAdmin) => new { @@ -119,4 +118,4 @@ await authorizationService.AuthorizeAsync(User, return TypedResults.Ok(new Response { Guides = mappedGuides.ToList() }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs b/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs index 4e367d1fc..f0ae8676c 100644 --- a/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs +++ b/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs @@ -30,10 +30,12 @@ public override async Task> ExecuteAsync(Request re await context.PollingStationInformation .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs index 6b42d3ea7..c6308e2c4 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs @@ -3,10 +3,12 @@ namespace Feature.PollingStation.Information.Specifications; -public sealed class GetMonitoringObserverSpecification: SingleResultSpecification +public sealed class GetMonitoringObserverSpecification : SingleResultSpecification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => + x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs index 5e139c1ca..d41ab16d9 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs @@ -3,16 +3,20 @@ namespace Feature.PollingStation.Information.Specifications; -public sealed class GetPollingStationInformationForObserverSpecification : Specification +public sealed class + GetPollingStationInformationForObserverSpecification : Specification { - public GetPollingStationInformationForObserverSpecification(Guid electionRoundId, Guid observerId, List? pollingStationIds) + public GetPollingStationInformationForObserverSpecification(Guid electionRoundId, Guid observerId, + List? pollingStationIds) { Query.Where(x => - x.ElectionRoundId == electionRoundId && - x.MonitoringObserver.ObserverId == observerId) - .Where(x => pollingStationIds.Contains(x.PollingStationId), pollingStationIds != null && pollingStationIds.Any()); + x.ElectionRoundId == electionRoundId && + x.MonitoringObserver.ObserverId == observerId && + x.MonitoringObserver.ElectionRoundId == electionRoundId) + .Where(x => pollingStationIds.Contains(x.PollingStationId), + pollingStationIds != null && pollingStationIds.Any()); Query.Select(x => PollingStationInformationModel.FromEntity(x)); - } } diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs index 92cdc2782..267696b1d 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs @@ -8,7 +8,9 @@ public sealed class GetPollingStationInformationSpecification : SingleResultSpec public GetPollingStationInformationSpecification(Guid electionRoundId, Guid pollingStationId, Guid observerId) { Query.Where(x => - x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && - x.MonitoringObserver.ObserverId == observerId); + x.ElectionRoundId == electionRoundId + && x.PollingStationId == pollingStationId + && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs b/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs index b5a2e71a5..7913e630f 100644 --- a/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs +++ b/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs @@ -127,7 +127,7 @@ DELETE FROM "QuickReports" { electionRoundId = req.ElectionRoundId, observerId = req.ObserverId, - pollingStationId = req.PollingStationId, + pollingStationId = req.PollingStationId }; await connection.ExecuteAsync(deletePSISubmissionsSql, queryParams, transaction); diff --git a/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs b/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs index 73212f216..2c4651867 100644 --- a/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs +++ b/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs @@ -73,7 +73,7 @@ public override async Task, NotFound, Bad FileName = attachment.FileName, PresignedUrl = result!.Url, MimeType = attachment.MimeType, - UrlValidityInSeconds = result.UrlValidityInSeconds, + UrlValidityInSeconds = result.UrlValidityInSeconds }); } } diff --git a/api/src/Feature.QuickReports/Delete/Endpoint.cs b/api/src/Feature.QuickReports/Delete/Endpoint.cs index d65a866ba..c967a7cda 100644 --- a/api/src/Feature.QuickReports/Delete/Endpoint.cs +++ b/api/src/Feature.QuickReports/Delete/Endpoint.cs @@ -55,6 +55,8 @@ private async Task DeleteAttachmentsAsync(Request req, CancellationToken ct) await context.QuickReportAttachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.QuickReportId == req.Id) .ExecuteUpdateAsync(setters => setters .SetProperty(qra => qra.IsDeleted, true) diff --git a/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs b/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs index 64fc3c9c5..2730121f9 100644 --- a/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs +++ b/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs @@ -32,6 +32,8 @@ await context .QuickReportAttachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.QuickReportId == req.QuickReportId && x.Id == req.Id) .ExecuteUpdateAsync(setters => setters diff --git a/api/src/Feature.QuickReports/Get/Endpoint.cs b/api/src/Feature.QuickReports/Get/Endpoint.cs index 852768e23..4020e3904 100644 --- a/api/src/Feature.QuickReports/Get/Endpoint.cs +++ b/api/src/Feature.QuickReports/Get/Endpoint.cs @@ -1,17 +1,18 @@ using Authorization.Policies.Requirements; -using Feature.QuickReports.Specifications; +using Dapper; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Services.FileStorage.Contracts; -using Vote.Monitor.Domain.Entities.QuickReportAggregate; -using Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate; +using Vote.Monitor.Core.Services.Security; +using Vote.Monitor.Domain.ConnectionFactory; namespace Feature.QuickReports.Get; public class Endpoint( IAuthorizationService authorizationService, - IReadRepository quickReportRepository, - IReadRepository quickReportAttachmentRepository, - IFileStorageService fileStorageService) + INpgsqlConnectionFactory dbConnectionFactory, + IFileStorageService fileStorageService, + ICurrentUserRoleProvider userRoleProvider, + ICurrentUserProvider userProvider) : Endpoint, NotFound>> { public override void Configure() @@ -26,43 +27,160 @@ public override void Configure() }); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) { - var authorizationResult = await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminOrObserverRequirement(req.ElectionRoundId)); + var authorizationResult = + await authorizationService.AuthorizeAsync(User, + new MonitoringNgoAdminOrObserverRequirement(req.ElectionRoundId)); if (!authorizationResult.Succeeded) { return TypedResults.NotFound(); } - var quickReport = await quickReportRepository.FirstOrDefaultAsync(new GetQuickReportByIdSpecification(req.ElectionRoundId, req.Id), ct); + if (userRoleProvider.IsObserver()) + { + return await GetQuickReportAsObserverAsync(req.ElectionRoundId, userProvider.GetUserId()!.Value, req.Id, + ct); + } + + if (userRoleProvider.IsNgoAdmin()) + { + return await GetQuickReportAsNgoAdminAsync(req.ElectionRoundId, userProvider.GetNgoId()!.Value, req.Id, ct); + } + + return TypedResults.NotFound(); + } + + private async Task, NotFound>> GetQuickReportAsNgoAdminAsync( + Guid electionRoundId, Guid ngoId, Guid quickReportId, CancellationToken ct) + { + var sql = """ + SELECT qr."Id", + qr."ElectionRoundId", + qr."QuickReportLocationType", + coalesce(qr."LastModifiedOn", qr."CreatedOn") as "Timestamp", + qr."Title", + qr."Description", + qr."MonitoringObserverId", + AMO."DisplayName" as "ObserverName", + AMO."PhoneNumber", + AMO."Email", + AMO."Tags", + AMO."NgoName", + AMO."IsOwnObserver", + qr."PollingStationId", + ps."Level1", + ps."Level2", + ps."Level3", + ps."Level4", + ps."Level5", + ps."Number", + ps."Address", + qr."PollingStationDetails", + qr."IncidentCategory", + qr."FollowUpStatus", + COALESCE((select jsonb_agg(jsonb_build_object('QuickReportId', "Id", 'FileName', "FileName", 'MimeType', "MimeType", 'FilePath', "FilePath", 'UploadedFileName', "UploadedFileName", 'TimeSubmitted', COALESCE("LastModifiedOn", "CreatedOn"))) + FROM "QuickReportAttachments" qra + WHERE + qra."ElectionRoundId" = @electionRoundId + AND qra."MonitoringObserverId" = qr."MonitoringObserverId" + AND qra."IsDeleted" = false AND qra."IsCompleted" = true),'[]'::JSONB) AS "Attachments" + + FROM "QuickReports" QR + INNER JOIN "MonitoringObservers" mo on mo."Id" = qr."MonitoringObserverId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, 'Coalition') AMO on AMO."MonitoringObserverId" = qr."MonitoringObserverId" + LEFT JOIN "PollingStations" ps on ps."Id" = qr."PollingStationId" + WHERE qr."ElectionRoundId" = @electionRoundId + and qr."Id" = @quickReportId + """; + + var queryArgs = new { electionRoundId, quickReportId, ngoId }; + + QuickReportDetailedModel submission = null; + + using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) + { + submission = await dbConnection.QueryFirstOrDefaultAsync(sql, queryArgs); + } - if (quickReport is null) + if (submission is null) { return TypedResults.NotFound(); } - var quickReportAttachments = await quickReportAttachmentRepository.ListAsync(new ListQuickReportAttachmentsSpecification(req.ElectionRoundId, quickReport.Id), ct); + await SetPresignUrls(submission); + + return TypedResults.Ok(submission); + } - var tasks = quickReportAttachments - .Select(async attachment => + private async Task SetPresignUrls(QuickReportDetailedModel submission) + { + foreach (var attachment in submission.Attachments) + { + var result = + await fileStorageService.GetPresignedUrlAsync(attachment.FilePath, attachment.UploadedFileName); + if (result is GetPresignedUrlResult.Ok(var url, _, var urlValidityInSeconds)) { - var presignedUrl = await fileStorageService.GetPresignedUrlAsync( - attachment.FilePath, - attachment.UploadedFileName); - - return new QuickReportAttachmentModel - { - FileName = attachment.FileName, - PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - MimeType = attachment.MimeType, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, - Id = attachment.Id, - QuickReportId = attachment.QuickReportId - }; - }); - - var attachments = await Task.WhenAll(tasks); - - return TypedResults.Ok(QuickReportDetailedModel.FromEntity(quickReport, attachments)); + attachment.PresignedUrl = url; + attachment.UrlValidityInSeconds = urlValidityInSeconds; + } + } + } + + private async Task, NotFound>> GetQuickReportAsObserverAsync( + Guid electionRoundId, Guid observerId, Guid quickReportId, CancellationToken ct) + { + var sql = """ + SELECT qr."Id", + qr."ElectionRoundId", + qr."QuickReportLocationType", + coalesce(qr."LastModifiedOn", qr."CreatedOn") as "Timestamp", + qr."Title", + qr."Description", + qr."PollingStationId", + ps."Level1", + ps."Level2", + ps."Level3", + ps."Level4", + ps."Level5", + ps."Number", + ps."Address", + qr."PollingStationDetails", + qr."IncidentCategory", + qr."FollowUpStatus", + COALESCE((select jsonb_agg(jsonb_build_object('QuickReportId', "Id", 'FileName', "FileName", 'MimeType', "MimeType", 'FilePath', "FilePath", 'UploadedFileName', "UploadedFileName", 'TimeSubmitted', COALESCE("LastModifiedOn", "CreatedOn"))) + FROM "QuickReportAttachments" qra + WHERE + qra."ElectionRoundId" = @electionRoundId + AND qra."MonitoringObserverId" = qr."MonitoringObserverId" + AND qra."IsDeleted" = false AND qra."IsCompleted" = true),'[]'::JSONB) AS "Attachments" + + FROM "QuickReports" QR + INNER JOIN "MonitoringObservers" mo on mo."Id" = qr."MonitoringObserverId" + INNER JOIN "AspNetUsers" u on u."Id" = mo."ObserverId" + LEFT JOIN "PollingStations" ps on ps."Id" = qr."PollingStationId" + WHERE qr."ElectionRoundId" = @electionRoundId + and qr."Id" = @quickReportId + and mo."ObserverId" = @observerId; + """; + + var queryArgs = new { electionRoundId, quickReportId, observerId }; + + QuickReportDetailedModel? submission = null!; + + using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) + { + submission = await dbConnection.QueryFirstOrDefaultAsync(sql, queryArgs); + } + + if (submission is null) + { + return TypedResults.NotFound(); + } + + await SetPresignUrls(submission); + + return TypedResults.Ok(submission); } } diff --git a/api/src/Feature.QuickReports/Get/QuickReportDetailedModel.cs b/api/src/Feature.QuickReports/Get/QuickReportDetailedModel.cs index c061a8e1c..7f434bd2c 100644 --- a/api/src/Feature.QuickReports/Get/QuickReportDetailedModel.cs +++ b/api/src/Feature.QuickReports/Get/QuickReportDetailedModel.cs @@ -15,7 +15,11 @@ public record QuickReportDetailedModel public string Title { get; init; } public string Description { get; init; } public Guid MonitoringObserverId { get; init; } + public bool IsOwnObserver { get; init; } public string ObserverName { get; init; } + public string Email { get; init; } + public string PhoneNumber { get; init; } + public string[] Tags { get; init; } = []; public Guid? PollingStationId { get; init; } public string? Level1 { get; init; } public string? Level2 { get; init; } @@ -27,11 +31,11 @@ public record QuickReportDetailedModel public string? PollingStationDetails { get; init; } [JsonConverter(typeof(SmartEnumNameConverter))] - public IncidentCategory IncidentCategory { get; set; } + public IncidentCategory IncidentCategory { get; init; } [JsonConverter(typeof(SmartEnumNameConverter))] - public QuickReportFollowUpStatus FollowUpStatus { get; set; } - public List Attachments { get; init; } + public QuickReportFollowUpStatus FollowUpStatus { get; init; } + public QuickReportAttachmentModel[] Attachments { get; init; } public static QuickReportDetailedModel FromEntity(QuickReport quickReport, IEnumerable attachments) { @@ -54,7 +58,7 @@ public static QuickReportDetailedModel FromEntity(QuickReport quickReport, IEnum Address = quickReport.PollingStation?.Address, PollingStationDetails = quickReport.PollingStationDetails, Timestamp = quickReport.LastModifiedOn ?? quickReport.CreatedOn, - Attachments = attachments.ToList(), + Attachments = attachments.ToArray(), FollowUpStatus = quickReport.FollowUpStatus, IncidentCategory = quickReport.IncidentCategory }; diff --git a/api/src/Feature.QuickReports/GetFilters/Endpoint.cs b/api/src/Feature.QuickReports/GetFilters/Endpoint.cs index a0510700a..9e1910574 100644 --- a/api/src/Feature.QuickReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.QuickReports/GetFilters/Endpoint.cs @@ -33,16 +33,15 @@ public override async Task, NotFound>> ExecuteAsync(Request SELECT MIN(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "FirstSubmissionTimestamp", MAX(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "LastSubmissionTimestamp" FROM "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON QR."MonitoringObserverId" = MO."MonitoringObserverId" WHERE QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId; """; var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, + DataSource = req.DataSource.ToString() }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -55,7 +54,7 @@ SELECT MIN(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { - TimestampsFilterOptions = timestampFilterOptions, + TimestampsFilterOptions = timestampFilterOptions }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.QuickReports/GetFilters/Request.cs b/api/src/Feature.QuickReports/GetFilters/Request.cs index 32bea00f5..08b284f6c 100644 --- a/api/src/Feature.QuickReports/GetFilters/Request.cs +++ b/api/src/Feature.QuickReports/GetFilters/Request.cs @@ -1,4 +1,5 @@ -using Vote.Monitor.Core.Security; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.Security; namespace Feature.QuickReports.GetFilters; @@ -8,4 +9,6 @@ public class Request [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + + public DataSource DataSource { get; set; } = DataSource.Ngo; } diff --git a/api/src/Feature.QuickReports/GetFilters/Validator.cs b/api/src/Feature.QuickReports/GetFilters/Validator.cs index 4a6439ea6..c6401ac44 100644 --- a/api/src/Feature.QuickReports/GetFilters/Validator.cs +++ b/api/src/Feature.QuickReports/GetFilters/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Feature.QuickReports/List/Endpoint.cs b/api/src/Feature.QuickReports/List/Endpoint.cs index 55324d470..748801b6d 100644 --- a/api/src/Feature.QuickReports/List/Endpoint.cs +++ b/api/src/Feature.QuickReports/List/Endpoint.cs @@ -28,15 +28,20 @@ public override async Task> ExecuteAsync COUNT(QR."Id") as "TotalNumberOfRows" FROM "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) AMO on AMO."MonitoringObserverId" = qr."MonitoringObserverId" LEFT JOIN "PollingStations" PS ON PS."Id" = QR."PollingStationId" WHERE QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId + AND (@COALITIONMEMBERID IS NULL OR AMO."NgoId" = @COALITIONMEMBERID) AND (@followUpStatus IS NULL or QR."FollowUpStatus" = @followUpStatus) AND (@quickReportLocationType IS NULL or QR."QuickReportLocationType" = @quickReportLocationType) AND (@incidentCategory IS NULL or QR."IncidentCategory" = @incidentCategory) + AND (@searchText IS NULL + OR @searchText = '' + OR AMO."DisplayName" ILIKE @searchText + OR AMO."Email" ILIKE @searchText + OR AMO."PhoneNumber" ILIKE @searchText + OR AMO."MonitoringObserverId"::TEXT ILIKE @searchText) AND ( @level1 IS NULL OR PS."Level1" = @level1 @@ -68,10 +73,18 @@ @level5 IS NULL QR."Description", QR."IncidentCategory", QR."FollowUpStatus", - COUNT(QRA."Id") FILTER(WHERE QRA."IsDeleted" = FALSE AND QRA."IsCompleted" = TRUE) AS "NumberOfAttachments", - O."FirstName" || ' ' ||O."LastName" "ObserverName", - O."Email", - O."PhoneNumber", + (SELECT COUNT(*) + FROM "QuickReportAttachments" QRA + WHERE QRA."QuickReportId" = QR."Id" + AND Qr."MonitoringObserverId" = QRA."MonitoringObserverId" + AND qra."IsDeleted" = FALSE + AND qra."IsCompleted" = TRUE) AS "NumberOfAttachments", + AMO."MonitoringObserverId", + AMO."DisplayName" "ObserverName", + AMO."PhoneNumber", + AMO."Email", + AMO."Tags", + AMO."NgoName", QR."PollingStationDetails", PS."Id" AS "PollingStationId", PS."Level1", @@ -83,17 +96,20 @@ @level5 IS NULL PS."Address" FROM "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "AspNetUsers" O ON MO."ObserverId" = O."Id" - LEFT JOIN "QuickReportAttachments" QRA ON QR."Id" = QRA."QuickReportId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @datasource) AMO on AMO."MonitoringObserverId" = qr."MonitoringObserverId" LEFT JOIN "PollingStations" PS ON PS."Id" = QR."PollingStationId" WHERE QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId + AND (@COALITIONMEMBERID IS NULL OR AMO."NgoId" = @COALITIONMEMBERID) AND (@followUpStatus IS NULL or QR."FollowUpStatus" = @followUpStatus) AND (@quickReportLocationType IS NULL or QR."QuickReportLocationType" = @quickReportLocationType) AND (@incidentCategory IS NULL or QR."IncidentCategory" = @incidentCategory) + AND (@searchText IS NULL + OR @searchText = '' + OR AMO."DisplayName" ILIKE @searchText + OR AMO."Email" ILIKE @searchText + OR AMO."PhoneNumber" ILIKE @searchText + OR AMO."MonitoringObserverId"::TEXT ILIKE @searchText) AND ( @level1 IS NULL OR PS."Level1" = @level1 @@ -116,11 +132,6 @@ @level5 IS NULL ) AND (@fromDate is NULL OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") <= @toDate::timestamp) - GROUP BY - QR."Id", - O."Id", - PS."Id", - MN."Id" ORDER BY CASE WHEN @sortExpression = 'Timestamp ASC' THEN COALESCE(QR."LastModifiedOn", QR."CreatedOn") END ASC, CASE WHEN @sortExpression = 'Timestamp DESC' THEN COALESCE(QR."LastModifiedOn", QR."CreatedOn") END DESC @@ -133,7 +144,10 @@ FETCH NEXT var queryArgs = new { electionRoundId = req.ElectionRoundId, + searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", ngoId = req.NgoId, + coalitionMemberId = req.CoalitionMemberId, + dataSource = req.DataSource.ToString(), offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), pageSize = req.PageSize, level1 = req.Level1Filter, diff --git a/api/src/Feature.QuickReports/List/QuickReportOverviewModel.cs b/api/src/Feature.QuickReports/List/QuickReportOverviewModel.cs index d4850b218..83f278119 100644 --- a/api/src/Feature.QuickReports/List/QuickReportOverviewModel.cs +++ b/api/src/Feature.QuickReports/List/QuickReportOverviewModel.cs @@ -10,13 +10,16 @@ public class QuickReportOverviewModel [JsonConverter(typeof(SmartEnumNameConverter))] public QuickReportLocationType QuickReportLocationType { get; set; } + public DateTime Timestamp { get; set; } public string Title { get; set; } public string Description { get; set; } public int NumberOfAttachments { get; set; } + public Guid MonitoringObserverId { get; set; } public string ObserverName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } + public string NgoName { get; set; } public string? PollingStationDetails { get; set; } public Guid? PollingStationId { get; set; } public string? Level1 { get; set; } @@ -29,7 +32,7 @@ public class QuickReportOverviewModel [JsonConverter(typeof(SmartEnumNameConverter))] public QuickReportFollowUpStatus FollowUpStatus { get; set; } - + [JsonConverter(typeof(SmartEnumNameConverter))] - public IncidentCategory IncidentCategory { get; set; } + public IncidentCategory IncidentCategory { get; set; } } diff --git a/api/src/Feature.QuickReports/List/Request.cs b/api/src/Feature.QuickReports/List/Request.cs index 501dd83b5..2c45c51e3 100644 --- a/api/src/Feature.QuickReports/List/Request.cs +++ b/api/src/Feature.QuickReports/List/Request.cs @@ -10,7 +10,9 @@ public class Request : BaseSortPaginatedRequest [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + [QueryParam] public string? SearchText { get; set; } + [QueryParam] public DataSource DataSource { get; set; } = DataSource.Ngo; [QueryParam] public string? Level1Filter { get; set; } [QueryParam] public string? Level2Filter { get; set; } [QueryParam] public string? Level3Filter { get; set; } @@ -21,4 +23,5 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public IncidentCategory? IncidentCategory { get; set; } [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } -} \ No newline at end of file + [QueryParam] public Guid? CoalitionMemberId { get; set; } +} diff --git a/api/src/Feature.QuickReports/List/Validator.cs b/api/src/Feature.QuickReports/List/Validator.cs index 561efc03b..6b22b1cfa 100644 --- a/api/src/Feature.QuickReports/List/Validator.cs +++ b/api/src/Feature.QuickReports/List/Validator.cs @@ -6,6 +6,7 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); RuleFor(x => x.PageNumber) .GreaterThanOrEqualTo(1); diff --git a/api/src/Feature.QuickReports/ListMy/Endpoint.cs b/api/src/Feature.QuickReports/ListMy/Endpoint.cs index 488b9e679..19a60dbfd 100644 --- a/api/src/Feature.QuickReports/ListMy/Endpoint.cs +++ b/api/src/Feature.QuickReports/ListMy/Endpoint.cs @@ -51,11 +51,11 @@ public override async Task>, NotFound>> Execut return new QuickReportAttachmentModel { + Id = attachment.Id, FileName = attachment.FileName, PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, MimeType = attachment.MimeType, UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, - Id = attachment.Id, QuickReportId = attachment.QuickReportId }; }); diff --git a/api/src/Feature.QuickReports/QuickReportAttachmentModel.cs b/api/src/Feature.QuickReports/QuickReportAttachmentModel.cs index 890e940e3..0332df2d8 100644 --- a/api/src/Feature.QuickReports/QuickReportAttachmentModel.cs +++ b/api/src/Feature.QuickReports/QuickReportAttachmentModel.cs @@ -4,8 +4,12 @@ public class QuickReportAttachmentModel { public Guid Id { get; set; } public Guid QuickReportId { get; set; } + public string FilePath { get; set; } = String.Empty; public string FileName { get; set; } = string.Empty; public string MimeType { get; set; } = string.Empty; public string PresignedUrl { get; set; } = string.Empty; public int UrlValidityInSeconds { get; set; } + public string UploadedFileName { get; set; } = string.Empty; + + public DateTime TimeSubmitted { get; set; } } diff --git a/api/src/Feature.QuickReports/QuickReportsInstaller.cs b/api/src/Feature.QuickReports/QuickReportsInstaller.cs index 1ba482093..67aaf40e5 100644 --- a/api/src/Feature.QuickReports/QuickReportsInstaller.cs +++ b/api/src/Feature.QuickReports/QuickReportsInstaller.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using Dapper; +using Microsoft.Extensions.DependencyInjection; +using Vote.Monitor.Core.Converters; namespace Feature.QuickReports; @@ -6,6 +8,8 @@ public static class QuickReportsInstaller { public static IServiceCollection AddQuickReportsFeature(this IServiceCollection services) { + SqlMapper.AddTypeHandler(typeof(QuickReportAttachmentModel[]), new JsonToObjectConverter()); + return services; } } diff --git a/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs index db7515df2..dafcb1f19 100644 --- a/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs @@ -6,6 +6,6 @@ public sealed class GetMonitoringObserverSpecification : SingleResultSpecificati { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId); + Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId && o.MonitoringNgo.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs b/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs index 21be6ed92..5662483ab 100644 --- a/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs @@ -10,6 +10,7 @@ public GetQuickReportAttachmentByIdSpecification(Guid electionRoundId, Guid obse Query.Where(qr => qr.Id == id && qr.QuickReportId == quickReportId && qr.ElectionRoundId == electionRoundId - && qr.MonitoringObserver.ObserverId == observerId); + && qr.MonitoringObserver.ObserverId == observerId + && qr.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs b/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs index 0758b859e..8d1029efa 100644 --- a/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs @@ -6,7 +6,7 @@ public sealed class ListObserverQuickReportsSpecification : Specification qr.ElectionRoundId == electionRoundId && qr.MonitoringObserver.ObserverId == observerId) + Query.Where(qr => qr.ElectionRoundId == electionRoundId && qr.MonitoringObserver.ObserverId == observerId && qr.MonitoringObserver.ElectionRoundId == electionRoundId) .Include(x => x.PollingStation) .Include(x => x.MonitoringObserver) .ThenInclude(x => x.Observer) diff --git a/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs b/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs index b74b72d3d..0f8cae6bf 100644 --- a/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs @@ -23,6 +23,7 @@ public override async Task ExecuteAsync(Request req, CancellationToke { await context.QuickReports .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); diff --git a/api/src/Feature.QuickReports/Upsert/Endpoint.cs b/api/src/Feature.QuickReports/Upsert/Endpoint.cs index 5572f7f24..6eb45605e 100644 --- a/api/src/Feature.QuickReports/Upsert/Endpoint.cs +++ b/api/src/Feature.QuickReports/Upsert/Endpoint.cs @@ -100,7 +100,7 @@ private async Task GetPresignedAttachments(Guid el PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, MimeType = attachment.MimeType, UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, - Id = attachment.Id, + Id = attachment.Id }; }); var attachments = await Task.WhenAll(tasks); diff --git a/api/src/Feature.Statistics/GetElectionsOverview/Endpoint.cs b/api/src/Feature.Statistics/GetElectionsOverview/Endpoint.cs index 8c05255d4..1aed3f2cf 100644 --- a/api/src/Feature.Statistics/GetElectionsOverview/Endpoint.cs +++ b/api/src/Feature.Statistics/GetElectionsOverview/Endpoint.cs @@ -126,7 +126,7 @@ AND FS."NumberOfQuestionsAnswered" > 0 WHERE "ElectionRoundId" = ANY (@electionRoundIds) AND "NumberOfQuestionsAnswered" > 0 - ) AS "NumberOfFormSubmissions"; + ) AS ""NumberOfQuestionsAnswered""; ----------------------------- -- number of questions answered diff --git a/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs b/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs index ee85fc3e3..972add650 100644 --- a/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs +++ b/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs @@ -17,7 +17,7 @@ public override void Configure() public override async Task ExecuteAsync(Request req, CancellationToken ct) { - var cacheKey = $"statistics-{req.ElectionRoundId}-{req.NgoId}"; + var cacheKey = $"statistics-{req.ElectionRoundId}-{req.NgoId}-{req.DataSource}"; return await cache.GetOrCreateAsync(cacheKey, async (e) => { @@ -28,478 +28,478 @@ public override async Task ExecuteAsync(Request req, CancellationToken private async Task GetNgoStatistics(Request req, CancellationToken ct) { - string sql = """ - -- get observer stats - SELECT - COUNT(MO."Id") FILTER ( - WHERE - MO."Status" = 'Active' - AND U."Status" = 'Active' - ) "ActiveObservers", - COUNT(MO."Id") FILTER ( - WHERE - MO."Status" = 'Pending' - OR U."Status" = 'Pending' - ) "PendingObservers", - COUNT(MO."Id") FILTER ( - WHERE - MO."Status" = 'Suspended' - OR U."Status" = 'Suspended' - ) "SuspendedObservers" - FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "AspNetUsers" U ON MO."ObserverId" = U."Id" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId; - - - ----------------------------Levels stats-------------------------------------- - WITH - "ActiveObservers" AS ( - SELECT - "PollingStationId", - FS."MonitoringObserverId" - FROM - "FormSubmissions" FS - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - UNION - SELECT - "PollingStationId", - PSI."MonitoringObserverId" - FROM - "PollingStationInformation" PSI - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - UNION - SELECT - "PollingStationId", - QR."MonitoringObserverId" - FROM - "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - UNION - SELECT - "PollingStationId", - IR."MonitoringObserverId" - FROM - "IncidentReports" IR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = IR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - IR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - ), - "ActiveObserversPerLevel" AS ( - SELECT - '/' AS "Path", - 0 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - UNION ALL - SELECT - PS."Level1" AS "Path", - 1 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" - WHERE - PS."Level1" != '' - GROUP BY - PS."Level1" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" AS "Path", - 2 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" - WHERE - PS."Level2" IS NOT NULL - AND PS."Level2" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", - 3 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" - WHERE - PS."Level3" IS NOT NULL - AND PS."Level3" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", - 4 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" - WHERE - PS."Level4" IS NOT NULL - AND PS."Level4" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", - 5 AS "Level", - COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" - FROM - "ActiveObservers" AO - INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" - WHERE - PS."Level5" IS NOT NULL - AND PS."Level5" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" - ), - "PollingStationsStats" AS ( - SELECT - "PollingStationId", - 0 AS "NumberOfIncidentReports", - 0 AS "NumberOfQuickReports", - COUNT(1) AS "NumberOfSubmissions", - 0 AS "MinutesMonitoring", - SUM(FS."NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM(FS."NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - COUNT(FS."MonitoringObserverId") AS "ActiveObservers" - FROM - "FormSubmissions" FS - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY - "PollingStationId" - UNION ALL - SELECT - "PollingStationId", - 0 AS "NumberOfIncidentReports", - 0 AS "NumberOfQuickReports", - COUNT(1) AS "NumberOfSubmissions", - COALESCE(SUM(COALESCE(PSI."MinutesMonitoring", 0)), 0) AS "MinutesMonitoring", - SUM(PSI."NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM(PSI."NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - COUNT(PSI."MonitoringObserverId") AS "ActiveObservers" - FROM - "PollingStationInformation" PSI - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY - "PollingStationId" - UNION ALL - SELECT - "PollingStationId", - 0 AS "NumberOfIncidentReports", - COUNT(1) AS "NumberOfQuickReports", - 0 AS "NumberOfSubmissions", - 0 AS "MinutesMonitoring", - 0 AS "NumberOfFlaggedAnswers", - 0 AS "NumberOfQuestionsAnswered", - COUNT(QR."MonitoringObserverId") AS "ActiveObservers" - FROM - "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY - "PollingStationId" - UNION ALL - SELECT - "PollingStationId", - COUNT(1) AS "NumberOfIncidentReports", - 0 AS "NumberOfQuickReports", - 0 AS "NumberOfSubmissions", - 0 AS "MinutesMonitoring", - 0 AS "NumberOfFlaggedAnswers", - 0 AS "NumberOfQuestionsAnswered", - COUNT(IR."MonitoringObserverId") AS "ActiveObservers" - FROM - "IncidentReports" IR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = IR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - IR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY - "PollingStationId" - ), - "PollingStationLevelsStats" AS ( - SELECT - '/' AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 0 AS "Level" - FROM - "PollingStationsStats" PSV - UNION ALL - SELECT - PS."Level1" AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 1 AS "Level" - FROM - "PollingStationsStats" PSV - INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" - WHERE - PS."Level1" != '' - GROUP BY - PS."Level1" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 2 AS "Level" - FROM - "PollingStationsStats" PSV - INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" - INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" - WHERE - PS."Level2" IS NOT NULL - AND PS."Level2" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 3 AS "Level" - FROM - "PollingStationsStats" PSV - INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" - INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" - WHERE - PS."Level3" IS NOT NULL - AND PS."Level3" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 4 AS "Level" - FROM - "PollingStationsStats" PSV - INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" - INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" - WHERE - PS."Level4" IS NOT NULL - AND PS."Level4" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", - COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", - SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", - SUM("NumberOfQuickReports") AS "NumberOfQuickReports", - SUM("NumberOfSubmissions") AS "NumberOfSubmissions", - SUM("MinutesMonitoring") AS "MinutesMonitoring", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - 5 AS "Level" - FROM - "PollingStationsStats" PSV - INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" - INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" - WHERE - PS."Level5" IS NOT NULL - AND PS."Level5" != '' - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" - ), - "PollingStationsPerLevel" AS ( - SELECT - '/' AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 0 AS "Level" - FROM - "PollingStations" PS - WHERE - PS."Level1" != '' - AND PS."ElectionRoundId" = @electionRoundId - UNION ALL - SELECT - PS."Level1" AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 1 AS "Level" - FROM - "PollingStations" PS - WHERE - PS."Level1" != '' - AND PS."ElectionRoundId" = @electionRoundId - GROUP BY - PS."Level1" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 2 AS "Level" - FROM - "PollingStations" PS - WHERE - "Level2" IS NOT NULL - AND "Level2" != '' - AND PS."ElectionRoundId" = @electionRoundId - GROUP BY - PS."Level1" || ' / ' || PS."Level2" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 3 AS "Level" - FROM - "PollingStations" PS - WHERE - "Level3" IS NOT NULL - AND "Level3" != '' - AND PS."ElectionRoundId" = @electionRoundId - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 4 AS "Level" - FROM - "PollingStations" PS - WHERE - "Level4" IS NOT NULL - AND "Level4" != '' - AND PS."ElectionRoundId" = @electionRoundId - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" - UNION ALL - SELECT - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", - COUNT(PS."Id") AS "NumberOfPollingStations", - 5 AS "Level" - FROM - "PollingStations" PS - WHERE - "Level5" IS NOT NULL - AND "Level5" != '' - AND PS."ElectionRoundId" = @electionRoundId - GROUP BY - PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" - ), - "AllPollingStationLevelsStats" AS ( - SELECT - S.*, - PS."NumberOfPollingStations" AS "NumberOfPollingStations" - FROM - "PollingStationLevelsStats" S - INNER JOIN "PollingStationsPerLevel" PS ON S."Level" = PS."Level" - AND S."Path" = PS."Path" - ) - SELECT - A.*, - AOL."ActiveObservers" as "ActiveObservers", - (A."NumberOfVisitedPollingStations" * 100.0 / NULLIF(A."NumberOfPollingStations", 0)) AS "CoveragePercentage" - FROM - "AllPollingStationLevelsStats" A - INNER JOIN "ActiveObserversPerLevel" AOL ON A."Level" = AOL."Level" - AND A."Path" = AOL."Path"; + string sql = + """ + -- get observer stats + SELECT + COUNT("MonitoringObserverId") FILTER ( + WHERE + "Status" = 'Active' + AND "AccountStatus" = 'Active' + ) "ActiveObservers", + COUNT("MonitoringObserverId") FILTER ( + WHERE + "Status" = 'Pending' + OR "AccountStatus" = 'Pending' + ) "PendingObservers", + COUNT("MonitoringObserverId") FILTER ( + WHERE + "Status" = 'Suspended' + OR "AccountStatus" = 'Suspended' + ) "SuspendedObservers" + FROM + "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE); + + ----------------------------Levels stats-------------------------------------- + WITH + "ActiveObservers" AS ( + SELECT + "PollingStationId", + FS."MonitoringObserverId" + FROM + "FormSubmissions" FS + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + UNION + SELECT + "PollingStationId", + PSI."MonitoringObserverId" + FROM + "PollingStationInformation" PSI + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = PSI."MonitoringObserverId" + UNION + SELECT + "PollingStationId", + QR."MonitoringObserverId" + FROM + "QuickReports" QR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = QR."MonitoringObserverId" + WHERE + QR."PollingStationId" IS NOT NULL + UNION + SELECT + "PollingStationId", + IR."MonitoringObserverId" + FROM + "IncidentReports" IR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = IR."MonitoringObserverId" + WHERE + IR."PollingStationId" IS NOT NULL + ), + "ActiveObserversPerLevel" AS ( + SELECT + '/' AS "Path", + 0 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + UNION ALL + SELECT + PS."Level1" AS "Path", + 1 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" + WHERE + PS."Level1" != '' + GROUP BY + PS."Level1" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" AS "Path", + 2 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" + WHERE + PS."Level2" IS NOT NULL + AND PS."Level2" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", + 3 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" + WHERE + PS."Level3" IS NOT NULL + AND PS."Level3" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", + 4 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" + WHERE + PS."Level4" IS NOT NULL + AND PS."Level4" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", + 5 AS "Level", + COUNT(DISTINCT AO."MonitoringObserverId") "ActiveObservers" + FROM + "ActiveObservers" AO + INNER JOIN "PollingStations" PS ON PS."Id" = AO."PollingStationId" + WHERE + PS."Level5" IS NOT NULL + AND PS."Level5" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" + ), + "PollingStationsStats" AS ( + SELECT + "PollingStationId", + 0 AS "NumberOfIncidentReports", + 0 AS "NumberOfQuickReports", + COUNT(1) AS "NumberOfSubmissions", + 0 AS "MinutesMonitoring", + SUM(FS."NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM(FS."NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + COUNT(FS."MonitoringObserverId") AS "ActiveObservers" + FROM + "FormSubmissions" FS + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + GROUP BY + "PollingStationId" + UNION ALL + SELECT + "PollingStationId", + 0 AS "NumberOfIncidentReports", + 0 AS "NumberOfQuickReports", + COUNT(1) AS "NumberOfSubmissions", + COALESCE(SUM(COALESCE(PSI."MinutesMonitoring", 0)), 0) AS "MinutesMonitoring", + SUM(PSI."NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM(PSI."NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + COUNT(PSI."MonitoringObserverId") AS "ActiveObservers" + FROM + "PollingStationInformation" PSI + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = PSI."MonitoringObserverId" + GROUP BY + "PollingStationId" + UNION ALL + SELECT + "PollingStationId", + 0 AS "NumberOfIncidentReports", + COUNT(1) AS "NumberOfQuickReports", + 0 AS "NumberOfSubmissions", + 0 AS "MinutesMonitoring", + 0 AS "NumberOfFlaggedAnswers", + 0 AS "NumberOfQuestionsAnswered", + COUNT(QR."MonitoringObserverId") AS "ActiveObservers" + FROM + "QuickReports" QR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = QR."MonitoringObserverId" + GROUP BY + "PollingStationId" + UNION ALL + SELECT + "PollingStationId", + COUNT(1) AS "NumberOfIncidentReports", + 0 AS "NumberOfQuickReports", + 0 AS "NumberOfSubmissions", + 0 AS "MinutesMonitoring", + 0 AS "NumberOfFlaggedAnswers", + 0 AS "NumberOfQuestionsAnswered", + COUNT(IR."MonitoringObserverId") AS "ActiveObservers" + FROM + "IncidentReports" IR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = IR."MonitoringObserverId" + GROUP BY + "PollingStationId" + ), + "PollingStationLevelsStats" AS ( + SELECT + '/' AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 0 AS "Level" + FROM + "PollingStationsStats" PSV + UNION ALL + SELECT + PS."Level1" AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 1 AS "Level" + FROM + "PollingStationsStats" PSV + INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" + WHERE + PS."Level1" != '' + GROUP BY + PS."Level1" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 2 AS "Level" + FROM + "PollingStationsStats" PSV + INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" + INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" + WHERE + PS."Level2" IS NOT NULL + AND PS."Level2" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 3 AS "Level" + FROM + "PollingStationsStats" PSV + INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" + INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" + WHERE + PS."Level3" IS NOT NULL + AND PS."Level3" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 4 AS "Level" + FROM + "PollingStationsStats" PSV + INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" + INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" + WHERE + PS."Level4" IS NOT NULL + AND PS."Level4" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", + COUNT(DISTINCT PSV."PollingStationId") AS "NumberOfVisitedPollingStations", + SUM("NumberOfIncidentReports") AS "NumberOfIncidentReports", + SUM("NumberOfQuickReports") AS "NumberOfQuickReports", + SUM("NumberOfSubmissions") AS "NumberOfSubmissions", + SUM("MinutesMonitoring") AS "MinutesMonitoring", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + 5 AS "Level" + FROM + "PollingStationsStats" PSV + INNER JOIN "PollingStations" PS ON PSV."PollingStationId" = PS."Id" + INNER JOIN "ActiveObservers" AO ON AO."PollingStationId" = PS."Id" + WHERE + PS."Level5" IS NOT NULL + AND PS."Level5" != '' + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" + ), + "PollingStationsPerLevel" AS ( + SELECT + '/' AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 0 AS "Level" + FROM + "PollingStations" PS + WHERE + PS."Level1" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + UNION ALL + SELECT + PS."Level1" AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 1 AS "Level" + FROM + "PollingStations" PS + WHERE + PS."Level1" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + GROUP BY + PS."Level1" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 2 AS "Level" + FROM + "PollingStations" PS + WHERE + "Level2" IS NOT NULL + AND "Level2" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + GROUP BY + PS."Level1" || ' / ' || PS."Level2" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 3 AS "Level" + FROM + "PollingStations" PS + WHERE + "Level3" IS NOT NULL + AND "Level3" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 4 AS "Level" + FROM + "PollingStations" PS + WHERE + "Level4" IS NOT NULL + AND "Level4" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" + UNION ALL + SELECT + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" AS "Path", + COUNT(PS."Id") AS "NumberOfPollingStations", + 5 AS "Level" + FROM + "PollingStations" PS + WHERE + "Level5" IS NOT NULL + AND "Level5" != '' + AND PS."ElectionRoundId" = @ELECTIONROUNDID + GROUP BY + PS."Level1" || ' / ' || PS."Level2" || ' / ' || PS."Level3" || ' / ' || PS."Level4" || ' / ' || PS."Level5" + ), + "AllPollingStationLevelsStats" AS ( + SELECT + S.*, + PS."NumberOfPollingStations" AS "NumberOfPollingStations" + FROM + "PollingStationLevelsStats" S + INNER JOIN "PollingStationsPerLevel" PS ON S."Level" = PS."Level" + AND S."Path" = PS."Path" + ) + SELECT + A.*, + AOL."ActiveObservers" AS "ActiveObservers", + ( + A."NumberOfVisitedPollingStations" * 100.0 / NULLIF(A."NumberOfPollingStations", 0) + ) AS "CoveragePercentage" + FROM + "AllPollingStationLevelsStats" A + INNER JOIN "ActiveObserversPerLevel" AOL ON A."Level" = AOL."Level" + AND A."Path" = AOL."Path"; + + ------------------------------------------------------------------------------ + -------------------------- read hourly histogram------------------------------ + SELECT + DATE_TRUNC( + 'hour', + TIMEZONE ( + 'utc', + COALESCE(FS."LastModifiedOn", FS."CreatedOn") + ) + )::TIMESTAMPTZ AS "Bucket", + COUNT(1) AS "FormsSubmitted", + SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", + SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers" + FROM + "FormSubmissions" FS + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + GROUP BY + 1; + + SELECT + DATE_TRUNC( + 'hour', + TIMEZONE ( + 'utc', + COALESCE(QR."LastModifiedOn", QR."CreatedOn") + ) + )::TIMESTAMPTZ "Bucket", + COUNT(1) "Value" + FROM + "QuickReports" QR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = QR."MonitoringObserverId" + GROUP BY + 1; + + SELECT + DATE_TRUNC( + 'hour', + TIMEZONE ( + 'utc', + COALESCE(CR."LastModifiedOn", CR."CreatedOn") + ) + )::TIMESTAMPTZ "Bucket", + COUNT(1) "Value" + FROM + "CitizenReports" CR + INNER JOIN "ElectionRounds" ER ON ER."Id" = CR."ElectionRoundId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = ER."MonitoringNgoForCitizenReportingId" + WHERE + CR."ElectionRoundId" = @ELECTIONROUNDID + AND MN."NgoId" = @NGOID + GROUP BY + 1; + + SELECT + DATE_TRUNC( + 'hour', + TIMEZONE ( + 'utc', + COALESCE(IR."LastModifiedOn", IR."CreatedOn") + ) + )::TIMESTAMPTZ "Bucket", + COUNT(1) "Value" + FROM + "IncidentReports" IR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = IR."MonitoringObserverId" + GROUP BY + 1; + """; - ------------------------------------------------------------------------------ - - -------------------------- read hourly histogram------------------------------ - - SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(FS."LastModifiedOn", FS."CreatedOn")))::TIMESTAMPTZ AS "Bucket", - COUNT(1) AS "FormsSubmitted", - SUM("NumberOfQuestionsAnswered") AS "NumberOfQuestionsAnswered", - SUM("NumberOfFlaggedAnswers") AS "NumberOfFlaggedAnswers" - FROM "FormSubmissions" FS - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY 1; - - SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(QR."LastModifiedOn", QR."CreatedOn")))::TIMESTAMPTZ "Bucket", - COUNT(1) "Value" - FROM "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY 1; - - SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(CR."LastModifiedOn", CR."CreatedOn")))::TIMESTAMPTZ "Bucket", - COUNT(1) "Value" - FROM "CitizenReports" CR - INNER JOIN "ElectionRounds" ER ON ER."Id" = CR."ElectionRoundId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = ER."MonitoringNgoForCitizenReportingId" - WHERE CR."ElectionRoundId" = '5e55767e-6d9f-45e5-951f-1643f2153400' - AND MN."NgoId" = @ngoId - GROUP BY 1; - - SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(IR."LastModifiedOn", IR."CreatedOn")))::TIMESTAMPTZ "Bucket", - COUNT(1) "Value" - FROM "IncidentReports" IR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = IR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE IR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - GROUP BY 1; - """; - - var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId }; + var queryArgs = new + { + electionRoundId = req.ElectionRoundId, + ngoId = req.NgoId, + dataSource = req.DataSource.ToString() + }; ObserversStats observersStats; List visitedPollingStationsStats; @@ -547,7 +547,7 @@ SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(IR."LastModifiedOn", IR."Crea }).ToArray(), QuickReportsHistogram = quickReportsHistogram.ToArray(), IncidentReportsHistogram = incidentReportsHistogram.ToArray(), - CitizenReportsHistogram = citizenReportsHistogram.ToArray(), + CitizenReportsHistogram = citizenReportsHistogram.ToArray() }; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Statistics/GetNgoAdminStatistics/Request.cs b/api/src/Feature.Statistics/GetNgoAdminStatistics/Request.cs index 07be4c4cc..28be3e05c 100644 --- a/api/src/Feature.Statistics/GetNgoAdminStatistics/Request.cs +++ b/api/src/Feature.Statistics/GetNgoAdminStatistics/Request.cs @@ -1,4 +1,6 @@ -using Vote.Monitor.Core.Security; +using Microsoft.AspNetCore.Mvc; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.Security; namespace Feature.Statistics.GetNgoAdminStatistics; @@ -8,4 +10,6 @@ public class Request [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + + [FromQuery] public DataSource DataSource { get; set; } } diff --git a/api/src/Feature.Statistics/GetNgoAdminStatistics/Validator.cs b/api/src/Feature.Statistics/GetNgoAdminStatistics/Validator.cs index b97e26615..20348ada2 100644 --- a/api/src/Feature.Statistics/GetNgoAdminStatistics/Validator.cs +++ b/api/src/Feature.Statistics/GetNgoAdminStatistics/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotEmpty(); } } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/BaseAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/BaseAnswerAggregate.cs index cd87cda10..e701b10e8 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/BaseAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/BaseAnswerAggregate.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using PolyJson; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -40,4 +41,13 @@ public void Aggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswer a protected abstract void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswer answer); + + public void Aggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + AnswersAggregated += 1; + QuestionSpecificAggregate(submissionId, monitoringObserverId, answer); + } + + + protected abstract void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer); } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/CitizenReportFormSubmissionsAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/CitizenReportFormSubmissionsAggregate.cs index 9a2c6434b..5ecc47918 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/CitizenReportFormSubmissionsAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/CitizenReportFormSubmissionsAggregate.cs @@ -23,7 +23,7 @@ public class CitizenReportFormSubmissionsAggregate /// public IReadOnlyDictionary Aggregates { get; } - public CitizenReportFormSubmissionsAggregate(Form form) + public CitizenReportFormSubmissionsAggregate(Domain.Entities.FormAggregate.Form form) { ElectionRoundId = form.ElectionRoundId; MonitoringNgoId = form.MonitoringNgoId; diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/DateAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/DateAnswerAggregate.cs index 2f305e8c4..27ca1566a 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/DateAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/DateAnswerAggregate.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Aggregators.Extensions; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -19,6 +20,16 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito _answersHistogram.IncrementFor(GetBucketName(dateAnswer.Date)); } + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not DateAnswerModel dateAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + + _answersHistogram.IncrementFor(GetBucketName(dateAnswer.Date)); + } + private string GetBucketName(DateTime date) { // Truncate minutes, seconds, and milliseconds by creating a new DateTime diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/FormSubmissionsAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/FormSubmissionsAggregate.cs index 5a23ce99a..711b5b7d3 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/FormSubmissionsAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/FormSubmissionsAggregate.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using Ardalis.SmartEnum.SystemTextJson; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; @@ -9,12 +10,10 @@ namespace Vote.Monitor.Answer.Module.Aggregators; -public record Responder(Guid ResponderId, string FirstName, string LastName, string Email, string PhoneNumber); +public record Responder(Guid ResponderId, string DisplayName, string Email, string PhoneNumber); public class FormSubmissionsAggregate { - public Guid ElectionRoundId { get; } - public Guid MonitoringNgoId { get; } public Guid FormId { get; } public string FormCode { get; } @@ -38,10 +37,8 @@ public class FormSubmissionsAggregate /// public IReadOnlyDictionary Aggregates { get; } - public FormSubmissionsAggregate(Form form) + public FormSubmissionsAggregate(Domain.Entities.FormAggregate.Form form) { - ElectionRoundId = form.ElectionRoundId; - MonitoringNgoId = form.MonitoringNgoId; FormId = form.Id; FormCode = form.Code; FormType = form.FormType; @@ -59,8 +56,6 @@ public FormSubmissionsAggregate(Form form) public FormSubmissionsAggregate(PollingStationInformationForm form) { - ElectionRoundId = form.ElectionRoundId; - MonitoringNgoId = Guid.Empty; FormId = form.Id; FormCode = FormType.PSI; FormType = FormType.PSI; @@ -76,10 +71,31 @@ public FormSubmissionsAggregate(PollingStationInformationForm form) .AsReadOnly(); } + public FormSubmissionsAggregate AggregateAnswers(FormSubmissionView formSubmission) + { + _responders.Add(new Responder(formSubmission.MonitoringObserverId, formSubmission.ObserverName, + formSubmission.Email, formSubmission.PhoneNumber)); + + SubmissionCount++; + TotalNumberOfFlaggedAnswers += formSubmission.NumberOfFlaggedAnswers; + TotalNumberOfQuestionsAnswered += formSubmission.NumberOfQuestionsAnswered; + + foreach (var answer in formSubmission.Answers) + { + if (!Aggregates.TryGetValue(answer.QuestionId, out var aggregate)) + { + continue; + } + + aggregate.Aggregate(formSubmission.SubmissionId, formSubmission.MonitoringObserverId, answer); + } + + return this; + } public FormSubmissionsAggregate AggregateAnswers(FormSubmission formSubmission) { var observer = formSubmission.MonitoringObserver.Observer.ApplicationUser; - _responders.Add(new Responder(formSubmission.MonitoringObserverId, observer.FirstName, observer.LastName, + _responders.Add(new Responder(formSubmission.MonitoringObserverId, observer.DisplayName, observer.Email, observer.PhoneNumber)); SubmissionCount++; @@ -102,7 +118,7 @@ public FormSubmissionsAggregate AggregateAnswers(FormSubmission formSubmission) public FormSubmissionsAggregate AggregateAnswers(IncidentReport incidentReport) { var observer = incidentReport.MonitoringObserver.Observer.ApplicationUser; - _responders.Add(new Responder(incidentReport.MonitoringObserverId, observer.FirstName, observer.LastName, + _responders.Add(new Responder(incidentReport.MonitoringObserverId, observer.DisplayName, observer.Email, observer.PhoneNumber)); SubmissionCount++; @@ -125,7 +141,7 @@ public FormSubmissionsAggregate AggregateAnswers(IncidentReport incidentReport) public FormSubmissionsAggregate AggregateAnswers(PollingStationInformation formSubmission) { var observer = formSubmission.MonitoringObserver.Observer.ApplicationUser; - _responders.Add(new Responder(formSubmission.MonitoringObserverId, observer.FirstName, observer.LastName, + _responders.Add(new Responder(formSubmission.MonitoringObserverId, observer.DisplayName, observer.Email, observer.PhoneNumber)); SubmissionCount++; @@ -144,4 +160,4 @@ public FormSubmissionsAggregate AggregateAnswers(PollingStationInformation formS return this; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/MultiSelectAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/MultiSelectAnswerAggregate.cs index 191ad088a..d30ffe7ed 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/MultiSelectAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/MultiSelectAnswerAggregate.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Aggregators.Extensions; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -26,4 +27,17 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito _answersHistogram.IncrementFor(selectedOption.OptionId); } } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not MultiSelectAnswerModel multiSelectAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + + foreach (var selectedOption in multiSelectAnswer.Selection) + { + _answersHistogram.IncrementFor(selectedOption.OptionId); + } + } } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/NumberAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/NumberAnswerAggregate.cs index a30d407cb..c437dca77 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/NumberAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/NumberAnswerAggregate.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Aggregators.Extensions; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -25,4 +26,19 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito Average = Average.RecomputeAverage(numberAnswer.Value, _numberOfAnswersAggregated); } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not NumberAnswerModel numberAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + + _numberOfAnswersAggregated++; + + Min = Math.Min(numberAnswer.Value, Min); + Max = Math.Max(numberAnswer.Value, Max); + + Average = Average.RecomputeAverage(numberAnswer.Value, _numberOfAnswersAggregated); + } } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/RatingAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/RatingAnswerAggregate.cs index 7c6aa745d..981dd6618 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/RatingAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/RatingAnswerAggregate.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Aggregators.Extensions; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -35,4 +36,20 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito Average = Average.RecomputeAverage(ratingAnswer.Value, _numberOfAnswersAggregated); } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not RatingAnswerModel ratingAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + _numberOfAnswersAggregated++; + + _answersHistogram.IncrementFor(ratingAnswer.Value); + + Min = Math.Min(ratingAnswer.Value, Min); + Max = Math.Max(ratingAnswer.Value, Max); + + Average = Average.RecomputeAverage(ratingAnswer.Value, _numberOfAnswersAggregated); + } } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/SingleSelectAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/SingleSelectAnswerAggregate.cs index ddf73e21f..bdc22de6c 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/SingleSelectAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/SingleSelectAnswerAggregate.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Aggregators.Extensions; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; @@ -23,4 +24,14 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito _answersHistogram.IncrementFor(singleSelectAnswer.Selection.OptionId); } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not SingleSelectAnswerModel singleSelectAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + + _answersHistogram.IncrementFor(singleSelectAnswer.Selection.OptionId); + } } diff --git a/api/src/Vote.Monitor.Answer.Module/Aggregators/TextAnswerAggregate.cs b/api/src/Vote.Monitor.Answer.Module/Aggregators/TextAnswerAggregate.cs index 087893a93..d0a0f2c99 100644 --- a/api/src/Vote.Monitor.Answer.Module/Aggregators/TextAnswerAggregate.cs +++ b/api/src/Vote.Monitor.Answer.Module/Aggregators/TextAnswerAggregate.cs @@ -1,4 +1,5 @@ -using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; +using Vote.Monitor.Answer.Module.Models; +using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; namespace Vote.Monitor.Answer.Module.Aggregators; @@ -19,4 +20,14 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito _answers.Add(new TextResponse(submissionId, monitoringObserverId, textAnswer.Text)); } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + if (answer is not TextAnswerModel textAnswer) + { + throw new ArgumentException($"Invalid answer received: {answer.Discriminator}", nameof(answer)); + } + + _answers.Add(new TextResponse(submissionId, monitoringObserverId, textAnswer.Text)); + } } diff --git a/api/src/Feature.Form.Submissions/GetById/Response.cs b/api/src/Vote.Monitor.Answer.Module/Models/FormSubmissionView.cs similarity index 74% rename from api/src/Feature.Form.Submissions/GetById/Response.cs rename to api/src/Vote.Monitor.Answer.Module/Models/FormSubmissionView.cs index 3f9f2c271..686503eb8 100644 --- a/api/src/Feature.Form.Submissions/GetById/Response.cs +++ b/api/src/Vote.Monitor.Answer.Module/Models/FormSubmissionView.cs @@ -1,23 +1,18 @@ -using System.Text.Json.Serialization; -using Ardalis.SmartEnum.SystemTextJson; -using Feature.Form.Submissions.Models; -using Vote.Monitor.Answer.Module.Models; +using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; using Vote.Monitor.Form.Module.Models; -namespace Feature.Form.Submissions.GetById; +namespace Vote.Monitor.Answer.Module.Models; -public class Response +public class FormSubmissionView { public Guid SubmissionId { get; init; } public DateTime TimeSubmitted { get; init; } public string FormCode { get; init; } public string DefaultLanguage { get; init; } - - [JsonConverter(typeof(SmartEnumNameConverter))] public FormType FormType { get; init; } = default!; - [JsonConverter(typeof(SmartEnumNameConverter))] public SubmissionFollowUpStatus FollowUpStatus { get; init; } = default!; public Guid PollingStationId { get; init; } @@ -28,10 +23,15 @@ public class Response public string Level5 { get; init; } = default!; public string Number { get; init; } = default!; public Guid MonitoringObserverId { get; init; } + public bool IsOwnObserver { get; init; } public string ObserverName { get; init; } = default!; public string Email { get; init; } = default!; public string PhoneNumber { get; init; } = default!; - public string[] Tags { get; init; } + public string[] Tags { get; init; } = []; + public string NgoName { get; init; } = default!; + public int NumberOfFlaggedAnswers { get; init; } + public int NumberOfQuestionsAnswered { get; init; } + public BaseQuestionModel[] Questions { get; init; } public BaseAnswerModel[] Answers { get; init; } = []; public NoteModel[] Notes { get; init; } = []; @@ -40,5 +40,5 @@ public class Response public DateTime? ArrivalTime { get; init; } public DateTime? DepartureTime { get; init; } public ObservationBreakModel[] Breaks { get; init; } = []; - public bool IsCompleted { get; set; } -} \ No newline at end of file + public bool IsCompleted { get; init; } +} diff --git a/api/src/Vote.Monitor.Answer.Module/Vote.Monitor.Answer.Module.csproj b/api/src/Vote.Monitor.Answer.Module/Vote.Monitor.Answer.Module.csproj index 4468f9328..8658ae761 100644 --- a/api/src/Vote.Monitor.Answer.Module/Vote.Monitor.Answer.Module.csproj +++ b/api/src/Vote.Monitor.Answer.Module/Vote.Monitor.Answer.Module.csproj @@ -13,10 +13,10 @@ + - diff --git a/api/src/Vote.Monitor.Api.Feature.Auth/ForgotPassword/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.Auth/ForgotPassword/Endpoint.cs index 8d811dbda..e5ed28578 100644 --- a/api/src/Vote.Monitor.Api.Feature.Auth/ForgotPassword/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.Auth/ForgotPassword/Endpoint.cs @@ -1,4 +1,5 @@ -using Job.Contracts; +using System.Text; +using Job.Contracts; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -35,7 +36,9 @@ public override async Task> ExecuteAsync(Request // For more information on how to enable account confirmation and password reset please // visit https://go.microsoft.com/fwlink/?LinkID=532713 - string code = await userManager.GeneratePasswordResetTokenAsync(user); + var code = await userManager.GeneratePasswordResetTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "reset-password")); string passwordResetUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "token", code); diff --git a/api/src/Vote.Monitor.Api.Feature.Auth/ResetPassword/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.Auth/ResetPassword/Endpoint.cs index 9bded27ce..faf00d225 100644 --- a/api/src/Vote.Monitor.Api.Feature.Auth/ResetPassword/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.Auth/ResetPassword/Endpoint.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Identity; +using System.Text; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; namespace Vote.Monitor.Api.Feature.Auth.ResetPassword; @@ -22,8 +24,9 @@ public override async Task> ExecuteAsync(Request // Don't reveal that the user does not exist or is not confirmed return TypedResults.Ok(); } - - var result = await userManager.ResetPasswordAsync(user, request.Token, request.Password); + + var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(request.Token)); + var result = await userManager.ResetPasswordAsync(user, code, request.Password); if (!result.Succeeded) { diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs index 27ed55945..fdb3a91f0 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs @@ -59,6 +59,10 @@ public override async Task, Conflict))] public required ElectionRoundStatus Status { get; init; } public required DateTime CreatedOn { get; init; } public required DateTime? LastModifiedOn { get; init; } -} \ No newline at end of file + + public required bool IsMonitoringNgoForCitizenReporting { get; init; } + public required bool IsCoalitionLeader { get; init; } + + public required Guid? CoalitionId { get; init; } + public required string? CoalitionName { get; init; } +} diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Get/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Get/Endpoint.cs index 925f42e7d..c116afe51 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Get/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Get/Endpoint.cs @@ -1,6 +1,13 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.Get; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Core.Services.Security; +using Vote.Monitor.Domain; -public class Endpoint(IReadRepository repository) +namespace Vote.Monitor.Api.Feature.ElectionRound.Get; + +public class Endpoint( + VoteMonitorContext context, + ICurrentUserProvider userProvider, + ICurrentUserRoleProvider roleProvider) : Endpoint, NotFound>> { public override void Configure() @@ -9,10 +16,100 @@ public override void Configure() Policies(PolicyNames.AdminsOnly); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) + { + if (roleProvider.IsPlatformAdmin()) + { + return await GetElectionRoundAsPlatformAdmin(req, ct); + } + + return await GetElectionRoundAsNgoAdmin(req, ct); + } + + private async Task, NotFound>> GetElectionRoundAsPlatformAdmin(Request req, + CancellationToken ct) + { + var electionRound = await context.ElectionRounds.Where(x => x.Id == req.Id) + .Include(x => x.MonitoringNgos) + .ThenInclude(x => x.Ngo) + .Include(x => x.MonitoringNgos) + .ThenInclude(x => x.MonitoringObservers) + .Include(x => x.Country) + .AsSplitQuery() + .Select(electionRound => new ElectionRoundModel + { + Id = electionRound.Id, + CountryId = electionRound.CountryId, + CountryIso2 = electionRound.Country.Iso2, + CountryIso3 = electionRound.Country.Iso3, + CountryName = electionRound.Country.Name, + CountryFullName = electionRound.Country.FullName, + CountryNumericCode = electionRound.Country.NumericCode, + Title = electionRound.Title, + EnglishTitle = electionRound.EnglishTitle, + Status = electionRound.Status, + StartDate = electionRound.StartDate, + LastModifiedOn = electionRound.LastModifiedOn, + CreatedOn = electionRound.CreatedOn, + CoalitionId = null, + CoalitionName = null, + IsCoalitionLeader = false, + IsMonitoringNgoForCitizenReporting = false, + }) + .FirstOrDefaultAsync(ct); + + if (electionRound is null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(electionRound); + } + + private async Task, NotFound>> GetElectionRoundAsNgoAdmin(Request req, + CancellationToken ct) { - var specification = new GetElectionRoundByIdSpecification(req.Id); - var electionRound = await repository.SingleOrDefaultAsync(specification, ct); + var ngoId = userProvider.GetNgoId()!.Value; + + var electionRound = await context.MonitoringNgos + .Include(x => x.ElectionRound) + .ThenInclude(x => x.MonitoringNgoForCitizenReporting) + .Where(x => x.NgoId == ngoId) + .Where(x => x.ElectionRoundId == req.Id) + .OrderBy(x => x.ElectionRound.StartDate) + .Select(x => new ElectionRoundModel + { + Id = x.ElectionRound.Id, + CountryId = x.ElectionRound.CountryId, + CountryIso2 = x.ElectionRound.Country.Iso2, + CountryIso3 = x.ElectionRound.Country.Iso3, + CountryName = x.ElectionRound.Country.Name, + CountryFullName = x.ElectionRound.Country.FullName, + CountryNumericCode = x.ElectionRound.Country.NumericCode, + Title = x.ElectionRound.Title, + EnglishTitle = x.ElectionRound.EnglishTitle, + Status = x.ElectionRound.Status, + StartDate = x.ElectionRound.StartDate, + LastModifiedOn = x.ElectionRound.LastModifiedOn, + CreatedOn = x.ElectionRound.CreatedOn, + IsMonitoringNgoForCitizenReporting = x.ElectionRound.CitizenReportingEnabled && + x.ElectionRound.MonitoringNgoForCitizenReporting.NgoId == + ngoId, + IsCoalitionLeader = + context.Coalitions.Any(c => c.Leader.NgoId == ngoId && c.ElectionRoundId == x.ElectionRoundId), + CoalitionName = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Name) + .FirstOrDefault(), + CoalitionId = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Id) + .FirstOrDefault() + }) + .FirstOrDefaultAsync(ct); if (electionRound is null) { diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/ListAvailableForCitizenReporting/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/ListAvailableForCitizenReporting/Endpoint.cs index 30df02888..32a7b0a20 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/ListAvailableForCitizenReporting/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/ListAvailableForCitizenReporting/Endpoint.cs @@ -30,7 +30,7 @@ public override async Task ExecuteAsync(CancellationToken ct) CountryName = x.Country.Name, CountryFullName = x.Country.FullName, StartDate = x.StartDate, - Title = x.Title, + Title = x.Title }) .ToListAsync(ct); diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs index bb07762fc..7a6ef48be 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs @@ -1,8 +1,9 @@ -using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Domain; namespace Vote.Monitor.Api.Feature.ElectionRound.Monitoring; -public class Endpoint(IReadRepository repository) +public class Endpoint(VoteMonitorContext context) : Endpoint> { public override void Configure() @@ -21,7 +22,43 @@ public override void Configure() public override async Task> ExecuteAsync(Request req, CancellationToken ct) { - var electionRounds = await repository.ListAsync(new GetNgoElectionSpecification(req.NgoId), ct); + var electionRounds = await context.MonitoringNgos + .Include(x => x.ElectionRound) + .ThenInclude(x => x.MonitoringNgoForCitizenReporting) + .Include(x=>x.ElectionRound.Country) + .Where(x => x.NgoId == req.NgoId) + .OrderBy(x => x.ElectionRound.StartDate) + .Select(x => new ElectionRoundModel + { + Id = x.ElectionRound.Id, + CountryId = x.ElectionRound.CountryId, + CountryIso2 = x.ElectionRound.Country.Iso2, + CountryIso3 = x.ElectionRound.Country.Iso3, + CountryName = x.ElectionRound.Country.Name, + CountryFullName = x.ElectionRound.Country.FullName, + CountryNumericCode = x.ElectionRound.Country.NumericCode, + Title = x.ElectionRound.Title, + EnglishTitle = x.ElectionRound.EnglishTitle, + Status = x.ElectionRound.Status, + StartDate = x.ElectionRound.StartDate, + LastModifiedOn = x.ElectionRound.LastModifiedOn, + CreatedOn = x.ElectionRound.CreatedOn, + IsMonitoringNgoForCitizenReporting = x.ElectionRound.CitizenReportingEnabled && + x.ElectionRound.MonitoringNgoForCitizenReporting.NgoId == + req.NgoId, + IsCoalitionLeader = + context.Coalitions.Any(c => c.Leader.NgoId == req.NgoId && c.ElectionRoundId == x.ElectionRoundId), + CoalitionName = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Name) + .FirstOrDefault(), + CoalitionId = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Id) + .FirstOrDefault() + }).ToListAsync(ct); return TypedResults.Ok(new Result { ElectionRounds = electionRounds }); } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs deleted file mode 100644 index 4a584f033..000000000 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.Monitoring; - -public class NgoElectionRoundView -{ - public Guid MonitoringNgoId { get; set; } - public Guid ElectionRoundId { get; set; } - public string Title { get; set; } - public string EnglishTitle { get; set; } - public DateOnly StartDate { get; set; } - public string Country { get; set; } - public Guid CountryId { get; set; } - public bool IsMonitoringNgoForCitizenReporting { get; set; } - public ElectionRoundStatus Status { get; set; } -} diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Result.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Result.cs index d50ac717c..f19cfbc99 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Result.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Result.cs @@ -2,5 +2,5 @@ public class Result { - public List ElectionRounds { get; set; } + public List ElectionRounds { get; set; } } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionRoundByIdSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionRoundByIdSpecification.cs deleted file mode 100644 index 2f64d501b..000000000 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionRoundByIdSpecification.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.Specifications; - -public sealed class GetElectionRoundByIdSpecification : SingleResultSpecification -{ - public GetElectionRoundByIdSpecification(Guid id) - { - Query - .Where(x => x.Id == id) - .Include(x => x.MonitoringNgos) - .ThenInclude(x => x.Ngo) - .Include(x => x.MonitoringNgos) - .ThenInclude(x => x.MonitoringObservers) - .Include(x => x.Country) - .AsSplitQuery(); - - Query.Select(electionRound => new ElectionRoundModel - { - Id = electionRound.Id, - CountryId = electionRound.CountryId, - CountryIso2 = electionRound.Country.Iso2, - CountryIso3 = electionRound.Country.Iso3, - CountryName = electionRound.Country.Name, - CountryFullName = electionRound.Country.FullName, - CountryNumericCode = electionRound.Country.NumericCode, - Title = electionRound.Title, - EnglishTitle = electionRound.EnglishTitle, - Status = electionRound.Status, - StartDate = electionRound.StartDate, - LastModifiedOn = electionRound.LastModifiedOn, - CreatedOn = electionRound.CreatedOn - }); - } -} diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionsSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionsSpecification.cs deleted file mode 100644 index 3bb2d0c93..000000000 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetElectionsSpecification.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.Specifications; - -public sealed class GetElectionsSpecification : Specification -{ - public GetElectionsSpecification() - { - Query.Where(x => x.Status != ElectionRoundStatus.Archived); - - Query.Select(x => new ElectionRoundModel - { - Id = x.Id, - Title = x.Title, - EnglishTitle = x.EnglishTitle, - StartDate = x.StartDate, - Status = x.Status, - CreatedOn = x.CreatedOn, - LastModifiedOn = x.LastModifiedOn, - CountryIso2 = x.Country.Iso2, - CountryIso3 = x.Country.Iso3, - CountryName = x.Country.Name, - CountryFullName = x.Country.FullName, - CountryNumericCode = x.Country.NumericCode, - CountryId = x.CountryId - }); - } -} diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs deleted file mode 100644 index 7f7e0a4ae..000000000 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Vote.Monitor.Api.Feature.ElectionRound.Monitoring; -using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; - -namespace Vote.Monitor.Api.Feature.ElectionRound.Specifications; - -public sealed class GetNgoElectionSpecification : Specification -{ - public GetNgoElectionSpecification(Guid ngoId) - { - Query - .Include(x => x.ElectionRound) - .Where(x => x.NgoId == ngoId); - - Query.Select(x => new NgoElectionRoundView - { - MonitoringNgoId = x.Id, - ElectionRoundId = x.ElectionRoundId, - Title = x.ElectionRound.Title, - EnglishTitle = x.ElectionRound.EnglishTitle, - StartDate = x.ElectionRound.StartDate, - Country = x.ElectionRound.Country.FullName, - CountryId = x.ElectionRound.CountryId, - IsMonitoringNgoForCitizenReporting = x.ElectionRound.CitizenReportingEnabled && - x.ElectionRound.MonitoringNgoForCitizenReporting.NgoId == ngoId, - Status = x.ElectionRound.Status, - }); - } -} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs index c7b2bcc64..460dbaa31 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs @@ -25,6 +25,10 @@ public GetObserverElectionSpecification(Guid observerId) CountryName = x.Country.Name, CountryFullName = x.Country.FullName, CountryNumericCode = x.Country.NumericCode, + CoalitionId = null, + CoalitionName = null, + IsCoalitionLeader = false, + IsMonitoringNgoForCitizenReporting = false }); } } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs index 3410b232f..7f0794707 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs @@ -28,6 +28,10 @@ public ListElectionRoundsSpecification(List.Request request) CountryName = x.Country.Name, CountryFullName = x.Country.FullName, CountryNumericCode = x.Country.NumericCode, + CoalitionId = null, + CoalitionName = null, + IsCoalitionLeader = false, + IsMonitoringNgoForCitizenReporting = false }); } } diff --git a/api/src/Vote.Monitor.Api.Feature.Ngo/Specifications/ListNgosSpecification.cs b/api/src/Vote.Monitor.Api.Feature.Ngo/Specifications/ListNgosSpecification.cs index 67b2fe933..34d6eb58c 100644 --- a/api/src/Vote.Monitor.Api.Feature.Ngo/Specifications/ListNgosSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.Ngo/Specifications/ListNgosSpecification.cs @@ -7,7 +7,7 @@ public sealed class ListNgosSpecification : Specification public ListNgosSpecification(List.Request request) { Query - .Search(x => x.Name, "%" + request.SearchText + "%", !string.IsNullOrEmpty(request.SearchText)) + .Search(x => x.Name, $"%{request.SearchText?.Trim() ?? string.Empty}%", !string.IsNullOrEmpty(request.SearchText)) .Where(x => x.Status == request.Status, request.Status != null) .ApplyOrdering(request) .Paginate(request); diff --git a/api/src/Vote.Monitor.Api.Feature.NgoAdmin/Specifications/ListNgoAdminsSpecification.cs b/api/src/Vote.Monitor.Api.Feature.NgoAdmin/Specifications/ListNgoAdminsSpecification.cs index 51a6f1549..ec32d8e14 100644 --- a/api/src/Vote.Monitor.Api.Feature.NgoAdmin/Specifications/ListNgoAdminsSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.NgoAdmin/Specifications/ListNgoAdminsSpecification.cs @@ -9,8 +9,8 @@ public ListNgoAdminsSpecification(List.Request request) Query .Where(x => x.NgoId == request.NgoId) .Include(x => x.ApplicationUser) - .Search(x => x.ApplicationUser.FirstName, "%" + request.SearchText + "%", !string.IsNullOrEmpty(request.SearchText)) - .Search(x => x.ApplicationUser.LastName, "%" + request.SearchText + "%", !string.IsNullOrEmpty(request.SearchText)) + .Search(x => x.ApplicationUser.FirstName, $"%{request.SearchText?.Trim() ?? string.Empty}%", !string.IsNullOrEmpty(request.SearchText)) + .Search(x => x.ApplicationUser.LastName, $"%{request.SearchText?.Trim() ?? string.Empty}%", !string.IsNullOrEmpty(request.SearchText)) .Where(x => x.ApplicationUser.Status == request.Status, request.Status != null) .ApplyOrdering(request) .Paginate(request); diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs index 099bcec6d..c738f29c6 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs @@ -6,5 +6,5 @@ public class Request public string LastName { get; set; } public string Email { get; set; } public string Password { get; set; } - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } } diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs index cd1bc9e48..19c474f0e 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs @@ -19,8 +19,6 @@ public Validator() .NotEmpty(); RuleFor(x => x.PhoneNumber) - .NotEmpty() - .MinimumLength(3) - .MaximumLength(32); + .MaximumLength(32); } } diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/ObserverModel.cs b/api/src/Vote.Monitor.Api.Feature.Observer/ObserverModel.cs index 2ae45ae1f..1203ebb5e 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/ObserverModel.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/ObserverModel.cs @@ -3,14 +3,15 @@ public record ObserverModel { public Guid Id { get; init; } - public required string FirstName { get; init; } - public required string LastName { get; init; } - public required string Email { get; init; } + public string FirstName { get; init; } + public string LastName { get; init; } + public string Email { get; init; } - public required string PhoneNumber { get; init; } + public string PhoneNumber { get; init; } [JsonConverter(typeof(SmartEnumNameConverter))] - public required UserStatus Status { get; init; } - public required DateTime CreatedOn { get; init; } - public required DateTime? LastModifiedOn { get; init; } + public UserStatus Status { get; init; } + + public DateTime CreatedOn { get; init; } + public DateTime? LastModifiedOn { get; init; } } diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/Specifications/ListObserversSpecification.cs b/api/src/Vote.Monitor.Api.Feature.Observer/Specifications/ListObserversSpecification.cs index 6fb51557c..77a5c45a8 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/Specifications/ListObserversSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/Specifications/ListObserversSpecification.cs @@ -8,9 +8,9 @@ public ListObserversSpecification(List.Request request) { Query .Include(x => x.ApplicationUser) - .Search(x => x.ApplicationUser.FirstName, "%" + request.SearchText + "%", !string.IsNullOrEmpty(request.SearchText)) + .Search(x => x.ApplicationUser.FirstName, $"%{request.SearchText?.Trim() ?? string.Empty}%", !string.IsNullOrEmpty(request.SearchText)) - .Search(x => x.ApplicationUser.LastName, "%" + request.SearchText + "%", !string.IsNullOrEmpty(request.SearchText)) + .Search(x => x.ApplicationUser.LastName, $"%{request.SearchText?.Trim() ?? string.Empty}%", !string.IsNullOrEmpty(request.SearchText)) .Where(x => x.ApplicationUser.Status == request.Status, request.Status != null) .ApplyOrdering(request) .Paginate(request); diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs index 160aee0be..dc0347ee0 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs @@ -19,7 +19,14 @@ public override void Configure() public override async Task, Conflict, NotFound>> ExecuteAsync(Request req, CancellationToken ct) { - var specification = new GetPollingStationSpecification(req.ElectionRoundId, req.Address, req.Tags); + var specification = new GetPollingStationSpecification(req.ElectionRoundId, + req.Level1, + req.Level2, + req.Level3, + req.Level4, + req.Level5, + req.Number, + req.Address); var hasIdenticalPollingStation = await repository.AnyAsync(specification, ct); if (hasIdenticalPollingStation) diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs index e989e1b2e..0b5f3263b 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs @@ -41,7 +41,7 @@ public override async Task, NotFound>> ExecuteAsync(Request x.Level2, x.Level3, x.Level4, - x.Level5, + x.Level5 }) .Distinct() .ToListAsync(cancellationToken: ct); diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs index efca31b6b..e5b46a02c 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs @@ -2,11 +2,24 @@ public sealed class GetPollingStationSpecification : Specification { - public GetPollingStationSpecification(Guid electionRoundId, string address, Dictionary tags) + public GetPollingStationSpecification(Guid electionRoundId, + string level1, + string level2, + string level3, + string level4, + string level5, + string number, + string address) { Query .Where(x => x.ElectionRoundId == electionRoundId) - .Search(x => x.Address, "%" + address + "%", !string.IsNullOrEmpty(address)) - .Where(x => EF.Functions.JsonContains(x.Tags, tags), tags.Any()); + .Where(x => x.Level1 == level1) + .Where(x => x.Level2 == level2) + .Where(x => x.Level3 == level3) + .Where(x => x.Level4 == level4) + .Where(x => x.Level5 == level5) + .Where(x => x.Address == address) + .Where(x => x.Number == number); + } } diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs index 127e1f398..7a1131abc 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs @@ -30,7 +30,14 @@ public override async Task, Conflict return TypedResults.NotFound(new ProblemDetails(ValidationFailures)); } - var specification = new GetPollingStationSpecification(req.ElectionRoundId, req.Address, req.Tags); + var specification = new GetPollingStationSpecification(req.ElectionRoundId, + req.Level1, + req.Level2, + req.Level3, + req.Level4, + req.Level5, + req.Number, + req.Address); var hasIdenticalPollingStation = await repository.AnyAsync(specification, ct); if (hasIdenticalPollingStation) { diff --git a/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs b/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs index 908c9c79f..3932b7927 100644 --- a/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs +++ b/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs @@ -70,7 +70,7 @@ public CustomSentryUserFactory(IHttpContextAccessor httpContextAccessor) : new SentryUser { Id = identifier, - Other = extraData, + Other = extraData }; } diff --git a/api/src/Vote.Monitor.Api/Program.cs b/api/src/Vote.Monitor.Api/Program.cs index 3cb5aa88a..97b58ce30 100644 --- a/api/src/Vote.Monitor.Api/Program.cs +++ b/api/src/Vote.Monitor.Api/Program.cs @@ -44,6 +44,7 @@ using Feature.IncidentReports.Notes; using Feature.Locations; using Feature.Monitoring; +using Feature.NgoCoalitions; using Feature.Statistics; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; using Microsoft.AspNetCore.Http.Features; @@ -166,6 +167,7 @@ builder.Services.AddIncidentReportsNotesFeature(); builder.Services.AddIncidentReportAttachmentsFeature(); builder.Services.AddCitizenNotificationsFeature(); +builder.Services.AddCoalitionsFeature(); builder.Services.AddAuthorization(); @@ -218,6 +220,7 @@ x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.Converters.Add(new JsonBooleanStringConverter()); x.Serializer.Options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; diff --git a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj index a02d5d1e7..b9df6707e 100644 --- a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj +++ b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj @@ -60,6 +60,7 @@ + @@ -90,9 +91,6 @@ Always - - Always - .dockerignore diff --git a/api/src/Vote.Monitor.Api/appsettings.Development.json b/api/src/Vote.Monitor.Api/appsettings.Development.json index bff1a1627..e3af8b6cd 100644 --- a/api/src/Vote.Monitor.Api/appsettings.Development.json +++ b/api/src/Vote.Monitor.Api/appsettings.Development.json @@ -38,8 +38,8 @@ "FirstName": "John", "LastName": "Doe", "Email": "john.doe@example.com", - "PhoneNumber": "1234567890", - "Password": "password123" + "PhoneNumber": "string", + "Password": "string" } }, "Statistics": { diff --git a/api/src/Vote.Monitor.Api/appsettings.Testing.json b/api/src/Vote.Monitor.Api/appsettings.Testing.json deleted file mode 100644 index 1fec789fc..000000000 --- a/api/src/Vote.Monitor.Api/appsettings.Testing.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Domain": { - "DbConnectionConfig": { - "Server": "localhost", - "Port": "5432", - "Database": "vote-monitor", - "UserId": "postgres", - "Password": "docker" - } - }, - "Core": { - "EnableHangfire": false, - "HangfireConnectionConfig": { - "Server": "localhost", - "Port": "5432", - "Database": "vote-monitor-hangfire", - "UserId": "postgres", - "Password": "docker" - } - }, - "Sentry": { - "Enabled": false, - "Dsn": "", - "TracesSampleRate": 0.2 - }, - "Seeders": { - "PlatformAdminSeeder": { - "FirstName": "John", - "LastName": "Doe", - "Email": "admin@example.com", - "PhoneNumber": "1234567890", - "Password": "toTallyNotTestPassw0rd" - } - } -} diff --git a/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs b/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs index 80694914c..fb86370fb 100644 --- a/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs +++ b/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs @@ -14,7 +14,7 @@ public static string GetNpgsqlConnectionString(this IConfiguration config, strin Database = config[$"{section}:Database"]!, Username = config[$"{section}:UserId"], Password = config[$"{section}:Password"], - IncludeErrorDetail = true, + IncludeErrorDetail = true }; return connectionStringBuilder.ToString(); diff --git a/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs b/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs index ed0cee007..a778142e7 100644 --- a/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs +++ b/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using System.Text.Json; namespace Vote.Monitor.Core.Helpers; @@ -15,4 +16,17 @@ public static Guid Create(string text) return guid; } + + public static Guid Create(IEnumerable guids) + { + var guidsArray = guids.Distinct().Order().ToArray(); + + using var hasher = SHA256.Create(); + + var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(guidsArray))); + + var guid = new Guid(hash.Take(16).ToArray()); + + return guid; + } } diff --git a/api/src/Feature.Form.Submissions/Models/AttachmentModel.cs b/api/src/Vote.Monitor.Core/Models/AttachmentModel.cs similarity index 82% rename from api/src/Feature.Form.Submissions/Models/AttachmentModel.cs rename to api/src/Vote.Monitor.Core/Models/AttachmentModel.cs index 2b37f051c..e410b06f1 100644 --- a/api/src/Feature.Form.Submissions/Models/AttachmentModel.cs +++ b/api/src/Vote.Monitor.Core/Models/AttachmentModel.cs @@ -1,9 +1,9 @@ -namespace Feature.Form.Submissions.Models; +namespace Vote.Monitor.Core.Models; public class AttachmentModel { - public Guid QuestionId { get; set; } public Guid SubmissionId { get; set; } + public Guid QuestionId { get; set; } public Guid MonitoringObserverId { get; set; } public string FilePath { get; set; } public string UploadedFileName { get; set; } @@ -12,5 +12,5 @@ public class AttachmentModel public string PresignedUrl { get; set; } public int UrlValidityInSeconds { get; set; } - public DateTime TimeSubmitted { get; init; } + public DateTime TimeSubmitted { get; set; } } diff --git a/api/src/Vote.Monitor.Core/Models/DataSource.cs b/api/src/Vote.Monitor.Core/Models/DataSource.cs new file mode 100644 index 000000000..b419e7130 --- /dev/null +++ b/api/src/Vote.Monitor.Core/Models/DataSource.cs @@ -0,0 +1,30 @@ +using Ardalis.SmartEnum; + +namespace Vote.Monitor.Core.Models; + +[JsonConverter(typeof(SmartEnumValueConverter))] +[SmartEnumStringComparer(StringComparison.InvariantCultureIgnoreCase)] +public sealed class DataSource : SmartEnum +{ + public static readonly DataSource Ngo = new(nameof(Ngo), nameof(Ngo)); + public static readonly DataSource Coalition = new(nameof(Coalition), nameof(Coalition)); + + /// Gets an item associated with the specified value. Parses SmartEnum when used as query params + /// this issue + /// The value of the item to get. + /// + /// When this method returns, contains the item associated with the specified value, if the value is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified name; otherwise, false. + /// + public static bool TryParse(string value, out DataSource result) + { + return TryFromValue(value, out result); + } + + [JsonConstructor] + private DataSource(string name, string value) : base(name, value) + { + } +} diff --git a/api/src/Feature.Form.Submissions/Models/NoteModel.cs b/api/src/Vote.Monitor.Core/Models/NoteModel.cs similarity index 84% rename from api/src/Feature.Form.Submissions/Models/NoteModel.cs rename to api/src/Vote.Monitor.Core/Models/NoteModel.cs index a49c7a79e..4769f873f 100644 --- a/api/src/Feature.Form.Submissions/Models/NoteModel.cs +++ b/api/src/Vote.Monitor.Core/Models/NoteModel.cs @@ -1,4 +1,4 @@ -namespace Feature.Form.Submissions.Models; +namespace Vote.Monitor.Core.Models; public class NoteModel { diff --git a/api/src/Feature.Form.Submissions/Models/ObservationBreakModel.cs b/api/src/Vote.Monitor.Core/Models/ObservationBreakModel.cs similarity index 73% rename from api/src/Feature.Form.Submissions/Models/ObservationBreakModel.cs rename to api/src/Vote.Monitor.Core/Models/ObservationBreakModel.cs index 01b013d39..b082bd77d 100644 --- a/api/src/Feature.Form.Submissions/Models/ObservationBreakModel.cs +++ b/api/src/Vote.Monitor.Core/Models/ObservationBreakModel.cs @@ -1,7 +1,7 @@ -namespace Feature.Form.Submissions.Models; +namespace Vote.Monitor.Core.Models; public class ObservationBreakModel { public DateTime Start { get; init; } public DateTime? End { get; init; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs b/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs index e09ac1486..2cab56934 100644 --- a/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs +++ b/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs @@ -26,4 +26,4 @@ public static bool TryParse(string value, out TranslationStatus result) private TranslationStatus(string name, string value) : base(name, value) { } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs b/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs index c084dfcc3..853890cde 100644 --- a/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs +++ b/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs @@ -9,7 +9,7 @@ public IEnumerable Read(Stream stream) where TMap : ClassMap { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { - PrepareHeaderForMatch = args => args.Header.ToLower(), + PrepareHeaderForMatch = args => args.Header.ToLower() }; using var reader = new StreamReader(stream); @@ -24,7 +24,7 @@ public IEnumerable Read(Stream stream) { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { - PrepareHeaderForMatch = args => args.Header.ToLower(), + PrepareHeaderForMatch = args => args.Header.ToLower() }; using var reader = new StreamReader(stream); diff --git a/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs b/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs index fada97298..31a508af9 100644 --- a/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs +++ b/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs @@ -27,7 +27,7 @@ internal static class EmailTemplateLoader { EmailTemplateType.AttachmentFragment, "Fragments/attachment-fragment.html" }, { EmailTemplateType.AnswerNotesFragment, "Fragments/answer-notes-fragment.html" }, - { EmailTemplateType.NoteFragment, "Fragments/note-fragment.html" }, + { EmailTemplateType.NoteFragment, "Fragments/note-fragment.html" } }; public static string GetTemplate(EmailTemplateType templateType) diff --git a/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs b/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs index 5e92caf11..aa26095e8 100644 --- a/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs +++ b/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs @@ -53,7 +53,7 @@ public async Task GetPresignedUrlAsync(string uploadPath, { BucketName = _options.BucketName, Key = GetFileKey(uploadPath, fileName), - Expires = DateTime.UtcNow.AddSeconds(_options.PresignedUrlValidityInSeconds), + Expires = DateTime.UtcNow.AddSeconds(_options.PresignedUrlValidityInSeconds) }; try @@ -80,7 +80,7 @@ public async Task CreateMultipartUploadAsync(string uploa { BucketName = _options.BucketName, Key = fileKey, - ContentType = contentType, + ContentType = contentType }; var response = await client.InitiateMultipartUploadAsync(request, ct); @@ -95,7 +95,7 @@ public async Task CreateMultipartUploadAsync(string uploa PartNumber = partNumber, Key = fileKey, Verb = HttpVerb.PUT, - Expires = DateTime.UtcNow.AddHours(24), + Expires = DateTime.UtcNow.AddHours(24) }); presignedUrls.Add(partNumber, partPresignedUrl); diff --git a/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs b/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs index ee34b19bb..472a72af9 100644 --- a/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs +++ b/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs @@ -8,7 +8,7 @@ public class SerializerService(ILogger logger) : ISerializerS private readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true, - ReferenceHandler = ReferenceHandler.IgnoreCycles, + ReferenceHandler = ReferenceHandler.IgnoreCycles }; public string Serialize(T obj) diff --git a/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj b/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj index 301361ef7..ab286901a 100644 --- a/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj +++ b/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj @@ -33,8 +33,6 @@ - - diff --git a/api/src/Vote.Monitor.Domain/DomainInstaller.cs b/api/src/Vote.Monitor.Domain/DomainInstaller.cs index c86dabb42..e6c461c05 100644 --- a/api/src/Vote.Monitor.Domain/DomainInstaller.cs +++ b/api/src/Vote.Monitor.Domain/DomainInstaller.cs @@ -14,25 +14,35 @@ public static class DomainInstaller { public const string SectionKey = "Domain"; - public static IServiceCollection AddApplicationDomain(this IServiceCollection services, IConfiguration config, bool isProductionEnvironment) + public static IServiceCollection AddApplicationDomain(this IServiceCollection services, IConfiguration config, + bool isProductionEnvironment) { var connectionString = config.GetNpgsqlConnectionString("DbConnectionConfig"); NpgsqlConnection.GlobalTypeMapper.EnableDynamicJson(); - services.AddDbContext(options => + services.AddScoped(sp => + new AuditingInterceptor(sp.GetRequiredService(), + sp.GetRequiredService())); + + services.AddScoped(sp => + new AuditTrailInterceptor(sp.GetRequiredService(), + sp.GetRequiredService(), sp.GetRequiredService())); + + services.AddDbContext((sp, options) => { options.UseNpgsql(connectionString, sqlOptions => { sqlOptions.EnableRetryOnFailure( - maxRetryCount: 5, - maxRetryDelay: TimeSpan.FromSeconds(5), - errorCodesToAdd: null - ).UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - }); + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(5), + errorCodesToAdd: null + ) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + }).AddInterceptors(sp.GetRequiredService(), + sp.GetRequiredService()); options.EnableSensitiveDataLogging(!isProductionEnvironment); - }); services.AddSingleton(_ => new NpgsqlConnectionFactory(connectionString)); services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); @@ -65,7 +75,8 @@ public static IServiceCollection AddIdentity(this IServiceCollection services) = .AddDefaultTokenProviders() .Services; - public static async Task InitializeDatabasesAsync(this IServiceProvider services, CancellationToken cancellationToken = default) + public static async Task InitializeDatabasesAsync(this IServiceProvider services, + CancellationToken cancellationToken = default) { // Create a new scope to retrieve scoped services using var scope = services.CreateScope(); diff --git a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs index ebce442ca..41bf239c5 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs @@ -8,20 +8,21 @@ public class ApplicationUser : IdentityUser, IAggregateRoot #pragma warning disable CS8618 // Required by Entity Framework protected ApplicationUser() { - } #pragma warning restore CS8618 public UserRole Role { get; private set; } public string FirstName { get; private set; } public string LastName { get; private set; } + public string DisplayName { get; private set; } public string? RefreshToken { get; private set; } public DateTime RefreshTokenExpiryTime { get; private set; } public UserStatus Status { get; private set; } public UserPreferences Preferences { get; private set; } public string? InvitationToken { get; private set; } = null; - private ApplicationUser(UserRole role, string firstName, string lastName, string email, string phoneNumber, string password) + private ApplicationUser(UserRole role, string firstName, string lastName, string email, string? phoneNumber, + string password) { Role = role; FirstName = firstName.Trim(); @@ -30,9 +31,9 @@ private ApplicationUser(UserRole role, string firstName, string lastName, string UserName = email.Trim(); NormalizedEmail = email.Trim().ToUpperInvariant(); NormalizedUserName = email.Trim().ToUpperInvariant(); - PhoneNumber = phoneNumber.Trim(); + PhoneNumber = phoneNumber?.Trim(); - Status = UserStatus.Active; + Status = UserStatus.Pending; Preferences = UserPreferences.Defaults; if (string.IsNullOrEmpty(password.Trim())) @@ -46,18 +47,21 @@ private ApplicationUser(UserRole role, string firstName, string lastName, string } } - public static ApplicationUser Invite(string firstName, string lastName, string email, string phoneNumber) => + public static ApplicationUser Invite(string firstName, string lastName, string email, string? phoneNumber) => new(UserRole.Observer, firstName, lastName, email, phoneNumber, string.Empty); - public static ApplicationUser CreatePlatformAdmin(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.PlatformAdmin, firstName, lastName, email, phoneNumber, password); + public static ApplicationUser CreatePlatformAdmin(string firstName, string lastName, string email, string password) => + new(UserRole.PlatformAdmin, firstName, lastName, email, null, password); - public static ApplicationUser CreateNgoAdmin(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.NgoAdmin, firstName, lastName, email, phoneNumber, password); - public static ApplicationUser CreateObserver(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.Observer, firstName, lastName, email, phoneNumber, password); + public static ApplicationUser CreateNgoAdmin(string firstName, string lastName, string email, string? phoneNumber, + string password) => + new(UserRole.NgoAdmin, firstName, lastName, email, phoneNumber, password); - public void UpdateDetails(string firstName, string lastName, string phoneNumber) + public static ApplicationUser CreateObserver(string firstName, string lastName, string email, string? phoneNumber, + string password) => + new(UserRole.Observer, firstName, lastName, email, phoneNumber, password); + + public void UpdateDetails(string firstName, string lastName, string? phoneNumber) { FirstName = firstName; LastName = lastName; @@ -69,17 +73,19 @@ public void UpdateRefreshToken(string refreshToken, DateTime refreshTokenExpiryT RefreshToken = refreshToken; RefreshTokenExpiryTime = refreshTokenExpiryTime; } + public void AcceptInvite(string password) { InvitationToken = null; var hasher = new PasswordHasher(); PasswordHash = hasher.HashPassword(this, password); EmailConfirmed = true; + Status = UserStatus.Active; } public void NewInvite() { - InvitationToken = Guid.NewGuid().ToString(); + InvitationToken = Guid.NewGuid().ToString("N"); } public void Activate() @@ -93,6 +99,4 @@ public void Deactivate() // TODO: handle invariants Status = UserStatus.Deactivated; } - - } diff --git a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/UserStatus.cs b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/UserStatus.cs index f0fdc7019..17dde106c 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/UserStatus.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/UserStatus.cs @@ -4,6 +4,7 @@ public sealed class UserStatus : SmartEnum { public static readonly UserStatus Active = new(nameof(Active), nameof(Active)); + public static readonly UserStatus Pending = new(nameof(Pending), nameof(Pending)); public static readonly UserStatus Deactivated = new(nameof(Deactivated), nameof(Deactivated)); /// Gets an item associated with the specified value. Parses SmartEnum when used as query params diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs new file mode 100644 index 000000000..0ff7b3c40 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs @@ -0,0 +1,51 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class Coalition : AuditableBaseEntity, IAggregateRoot +{ + public Guid Id { get; private set; } + public Guid ElectionRoundId { get; private set; } + public ElectionRound ElectionRound { get; private set; } + public string Name { get; private set; } + + public Guid LeaderId { get; private set; } + public MonitoringNgo Leader { get; private set; } + public virtual List Memberships { get; internal set; } = []; + public virtual List FormAccess { get; internal set; } = []; + + internal Coalition(ElectionRound electionRound, string name, MonitoringNgo leader, + IEnumerable members) + { + Id = Guid.NewGuid(); + ElectionRound = electionRound; + ElectionRoundId = electionRound.Id; + Name = name; + Leader = leader; + LeaderId = leader.Id; + Memberships = members + .Select(member=>CoalitionMembership.Create(electionRound, this, member)) + .DistinctBy(x => x.MonitoringNgoId).ToList(); + } + + public static Coalition Create( + ElectionRound electionRound, + string name, + MonitoringNgo leaderNgo, + IEnumerable members) => new(electionRound, name, leaderNgo, members); + + public void Update(string coalitionName, IEnumerable memberships) + { + var leader = Memberships.FirstOrDefault(x => x.MonitoringNgoId == LeaderId) ?? + CoalitionMembership.Create(ElectionRoundId, Id, LeaderId); + Memberships = memberships.Union([leader]).DistinctBy(x => x.MonitoringNgoId).ToList(); + Name = coalitionName; + } + +#pragma warning disable CS8618 // Required by Entity Framework + private Coalition() + { + } +#pragma warning restore CS8618 + +} diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs new file mode 100644 index 000000000..c4ea20358 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs @@ -0,0 +1,39 @@ +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class CoalitionFormAccess +{ + public Guid CoalitionId { get; set; } + public Coalition Coalition { get; set; } + + public Guid MonitoringNgoId { get; set; } + public MonitoringNgo MonitoringNgo { get; set; } + + public Guid FormId { get; set; } + public Form Form { get; set; } + + public static CoalitionFormAccess Create(Guid coalitionId, Guid monitoringNgoId, Guid formId) => new() + { + CoalitionId = coalitionId, MonitoringNgoId = monitoringNgoId, FormId = formId + }; + + public static CoalitionFormAccess Create(Coalition coalition, + MonitoringNgo monitoringNgo, + Form form) => new() + { + Form = form, + FormId = form.Id, + Coalition = coalition, + CoalitionId = coalition.Id, + MonitoringNgo = monitoringNgo, + MonitoringNgoId = monitoringNgo.Id + }; + +#pragma warning disable CS8618 // Required by Entity Framework + private CoalitionFormAccess() + { + } +#pragma warning restore CS8618 +} diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs new file mode 100644 index 000000000..48218e918 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs @@ -0,0 +1,38 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class CoalitionMembership +{ + public Guid ElectionRoundId { get; set; } + public ElectionRound ElectionRound { get; set; } + + public Guid CoalitionId { get; set; } + public Coalition Coalition { get; set; } + + public Guid MonitoringNgoId { get; set; } + public MonitoringNgo MonitoringNgo { get; set; } + + public static CoalitionMembership Create(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId) => new() + { + ElectionRoundId = electionRoundId, CoalitionId = coalitionId, MonitoringNgoId = monitoringNgoId + }; + + public static CoalitionMembership Create(ElectionRound electionRound, + Coalition coalition, + MonitoringNgo monitoringNgo) => new() + { + ElectionRound = electionRound, + Coalition = coalition, + MonitoringNgo = monitoringNgo, + ElectionRoundId = electionRound.Id, + CoalitionId = coalition.Id, + MonitoringNgoId = monitoringNgo.Id + }; + +#pragma warning disable CS8618 // Required by Entity Framework + private CoalitionMembership() + { + } +#pragma warning restore CS8618 +} diff --git a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/ExportedData.cs b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/ExportedData.cs index a8d2daa0a..204e4d060 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/ExportedData.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/ExportedData.cs @@ -113,4 +113,4 @@ private ExportedData() { } #pragma warning restore CS8618 -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportFormSubmissionsFilters.cs b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportFormSubmissionsFilters.cs index f9f5f38c1..b31bb50ac 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportFormSubmissionsFilters.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportFormSubmissionsFilters.cs @@ -8,6 +8,8 @@ namespace Vote.Monitor.Domain.Entities.ExportedDataAggregate.Filters; public class ExportFormSubmissionsFilters { public string? SearchText { get; set; } + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } public FormType? FormTypeFilter { get; set; } public string? Level1Filter { get; set; } public string? Level2Filter { get; set; } @@ -33,4 +35,4 @@ public class ExportFormSubmissionsFilters public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportIncidentReportsFilters.cs b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportIncidentReportsFilters.cs index 95cbc905a..f0bf3cbe5 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportIncidentReportsFilters.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportIncidentReportsFilters.cs @@ -32,4 +32,6 @@ public class ExportIncidentReportsFilters public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } +} diff --git a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportQuickReportsFilters.cs b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportQuickReportsFilters.cs index a27a74ca6..ffbac7749 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportQuickReportsFilters.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ExportedDataAggregate/Filters/ExportQuickReportsFilters.cs @@ -1,9 +1,12 @@ +using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.QuickReportAggregate; namespace Vote.Monitor.Domain.Entities.ExportedDataAggregate.Filters; public class ExportQuickReportsFilters { + public DataSource DataSource { get; set; } = DataSource.Ngo; + public Guid? CoalitionMemberId { get; set; } public string? Level1Filter { get; set; } public string? Level2Filter { get; set; } public string? Level3Filter { get; set; } @@ -18,4 +21,4 @@ public class ExportQuickReportsFilters public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs b/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs index 4136699c3..dd1801fae 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs @@ -36,7 +36,8 @@ private Form( defaultLanguage, languages, icon, - questions) + questions, + FormStatus.Drafted) { MonitoringNgoId = monitoringNgo.Id; MonitoringNgo = monitoringNgo; @@ -61,7 +62,8 @@ private Form( defaultLanguage, languages, icon, - questions) + questions, + FormStatus.Drafted) { MonitoringNgoId = monitoringNgoId; } @@ -199,4 +201,4 @@ private Form() : base() { } #pragma warning restore CS8618 -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs b/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs index 513863e7e..9db7dce14 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs @@ -8,7 +8,6 @@ using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; using Vote.Monitor.Domain.Entities.PollingStationInfoAggregate; using Form = Vote.Monitor.Domain.Entities.FormAggregate.Form; @@ -43,7 +42,8 @@ protected BaseForm( string defaultLanguage, IEnumerable languages, string? icon, - IEnumerable questions) : this(electionRound.Id, + IEnumerable questions, + FormStatus status) : this(electionRound.Id, formType, code, name, @@ -51,7 +51,8 @@ protected BaseForm( defaultLanguage, languages, icon, - questions) + questions, + status) { ElectionRound = electionRound; ElectionRoundId = electionRound.Id; @@ -66,7 +67,8 @@ protected BaseForm( string defaultLanguage, IEnumerable languages, string? icon, - IEnumerable questions) + IEnumerable questions, + FormStatus status) { Id = Guid.NewGuid(); ElectionRoundId = electionRoundId; @@ -77,7 +79,7 @@ protected BaseForm( Description = description; DefaultLanguage = defaultLanguage; Languages = languages.ToArray(); - Status = FormStatus.Drafted; + Status = status; Questions = questions.ToList().AsReadOnly(); NumberOfQuestions = Questions.Count; Icon = icon; diff --git a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs similarity index 93% rename from api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs rename to api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs index 0b77cb6df..54549f8d1 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs @@ -7,7 +7,7 @@ namespace Vote.Monitor.Domain.Entities.FormTemplateAggregate; -public class Form : AuditableBaseEntity, IAggregateRoot +public class FormTemplate : AuditableBaseEntity, IAggregateRoot { public Guid Id { get; private set; } public FormType FormType { get; set; } @@ -21,7 +21,7 @@ public class Form : AuditableBaseEntity, IAggregateRoot public IReadOnlyList Questions { get; private set; } = new List().AsReadOnly(); [JsonConstructor] - internal Form(Guid id, + internal FormTemplate(Guid id, FormType formType, string code, string defaultLanguage, @@ -40,7 +40,7 @@ internal Form(Guid id, Languages = languages; } - private Form(FormType formTemplateType, + private FormTemplate(FormType formType, string code, string defaultLanguage, TranslatedString name, @@ -49,7 +49,7 @@ private Form(FormType formTemplateType, IEnumerable questions) { Id = Guid.NewGuid(); - FormType = formTemplateType; + FormType = formType; Code = code; DefaultLanguage = defaultLanguage; Name = name; @@ -59,14 +59,14 @@ private Form(FormType formTemplateType, Questions = questions.ToList(); } - public static Form Create(FormType formTemplateType, + public static FormTemplate Create(FormType formType, string code, string defaultLanguage, TranslatedString name, TranslatedString description, IEnumerable languages, IEnumerable questions) => - new(formTemplateType, code, defaultLanguage, name, description, languages, questions); + new(formType, code, defaultLanguage, name, description, languages, questions); public PublishResult Publish() { @@ -92,7 +92,7 @@ public void UpdateDetails(string code, string defaultLanguage, TranslatedString name, TranslatedString description, - FormType formTemplateType, + FormType formType, IEnumerable languages, IEnumerable questions) { @@ -100,7 +100,7 @@ public void UpdateDetails(string code, DefaultLanguage = defaultLanguage; Name = name; Description = description; - FormType = formTemplateType; + FormType = formType; Languages = languages.ToArray(); Questions = questions.ToList().AsReadOnly(); NumberOfQuestions = Questions.Count; @@ -162,7 +162,7 @@ public void SetDefaultLanguage(string languageCode) DefaultLanguage = languageCode; } - public Form Duplicate() => + public FormTemplate Duplicate() => new(FormType, Code, DefaultLanguage, Name, Description, Languages, Questions); public FormAggregate.Form Clone(Guid electionRoundId, Guid monitoringNgoId, string defaultLanguage, string[] languages) @@ -206,7 +206,7 @@ public FormAggregate.Form Clone(Guid electionRoundId, Guid monitoringNgoId, stri #pragma warning disable CS8618 // Required by Entity Framework - private Form() + private FormTemplate() { } #pragma warning restore CS8618 diff --git a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs index 623a76164..fd34f5ec0 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs @@ -5,7 +5,7 @@ namespace Vote.Monitor.Domain.Entities.FormTemplateAggregate; -public class FormTemplateValidator : Validator +public class FormTemplateValidator : Validator { public FormTemplateValidator() { diff --git a/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs b/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs index f2ea19d08..b1bd7cc19 100644 --- a/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs +++ b/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs @@ -1,4 +1,5 @@ -using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; using Vote.Monitor.Domain.Entities.NgoAggregate; using Vote.Monitor.Domain.Entities.ObserverAggregate; @@ -15,6 +16,7 @@ public class MonitoringNgo : AuditableBaseEntity, IAggregateRoot public virtual List MonitoringObservers { get; internal set; } = []; public MonitoringNgoStatus Status { get; private set; } + public virtual List Memberships { get; internal set; } = []; internal MonitoringNgo(ElectionRound electionRound, Ngo ngo) { diff --git a/api/src/Vote.Monitor.Domain/Entities/MonitoringObserverAggregate/MonitoringObserver.cs b/api/src/Vote.Monitor.Domain/Entities/MonitoringObserverAggregate/MonitoringObserver.cs index 3f00bb939..8dd872803 100644 --- a/api/src/Vote.Monitor.Domain/Entities/MonitoringObserverAggregate/MonitoringObserver.cs +++ b/api/src/Vote.Monitor.Domain/Entities/MonitoringObserverAggregate/MonitoringObserver.cs @@ -18,7 +18,8 @@ public class MonitoringObserver : AuditableBaseEntity, IAggregateRoot public string[] Tags { get; private set; } - private MonitoringObserver(Guid electionRoundId, Guid monitoringNgoId, Guid observerId, string[] tags, MonitoringObserverStatus status) + private MonitoringObserver(Guid electionRoundId, Guid monitoringNgoId, Guid observerId, string[] tags, + MonitoringObserverStatus status) { Id = Guid.NewGuid(); ElectionRoundId = electionRoundId; @@ -40,11 +41,23 @@ public void Suspend() public static MonitoringObserver Create(Guid electionRoundId, Guid monitoringNgoId, Guid observerId, string[] tags) { - return new MonitoringObserver(electionRoundId, monitoringNgoId, observerId, tags, MonitoringObserverStatus.Pending); + return new MonitoringObserver(electionRoundId, monitoringNgoId, observerId, tags, + MonitoringObserverStatus.Pending); } - public static MonitoringObserver CreateForExisting(Guid electionRoundId, Guid monitoringNgoId, Guid observerId, string[] tags) + + public static MonitoringObserver CreateForExisting(Guid electionRoundId, + Guid monitoringNgoId, + Guid observerId, + string[] tags, + UserStatus accountStatus) { - return new MonitoringObserver(electionRoundId, monitoringNgoId, observerId, tags, MonitoringObserverStatus.Active); + MonitoringObserverStatus status = accountStatus == UserStatus.Active + ? MonitoringObserverStatus.Active + : accountStatus == UserStatus.Pending + ? MonitoringObserverStatus.Pending + : MonitoringObserverStatus.Suspended; + + return new MonitoringObserver(electionRoundId, monitoringNgoId, observerId, tags, status); } public void Update(MonitoringObserverStatus status, string[] tags) @@ -60,7 +73,6 @@ public void Update(MonitoringObserverStatus status, string[] tags) #pragma warning disable CS8618 // Required by Entity Framework private MonitoringObserver() { - } #pragma warning restore CS8618 } diff --git a/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs b/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs index 7976d8d4b..db0cf42f5 100644 --- a/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs +++ b/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs @@ -20,7 +20,7 @@ private MonitoringObserverNotification(Guid monitoringObserverId, Guid notificat public static MonitoringObserverNotification Create(Guid monitoringObserverId, Guid notificationId) => new(monitoringObserverId, notificationId); - public MonitoringObserverNotification() + private MonitoringObserverNotification() { } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/Entities/PollingStationInfoFormAggregate/PollingStationInformationForm.cs b/api/src/Vote.Monitor.Domain/Entities/PollingStationInfoFormAggregate/PollingStationInformationForm.cs index 32ee707db..8f88235cf 100644 --- a/api/src/Vote.Monitor.Domain/Entities/PollingStationInfoFormAggregate/PollingStationInformationForm.cs +++ b/api/src/Vote.Monitor.Domain/Entities/PollingStationInfoFormAggregate/PollingStationInformationForm.cs @@ -24,7 +24,8 @@ private PollingStationInformationForm( defaultLanguage, languages, null, - questions) + questions, + FormStatus.Published) { } @@ -80,4 +81,4 @@ private PollingStationInformationForm() : base() { } #pragma warning restore CS8618 -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs index 00e14307a..ebf4ccc45 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs @@ -13,6 +13,11 @@ public void Configure(EntityTypeBuilder builder) builder.Property(u => u.RefreshTokenExpiryTime); builder.Property(u => u.InvitationToken).HasMaxLength(256); + builder + .Property(p => p.DisplayName) + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", stored: true) + .ValueGeneratedOnAddOrUpdate(); + builder.OwnsOne(u => u.Preferences, b => { b.ToJson(); diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs new file mode 100644 index 000000000..963a54f2c --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).IsRequired(); + builder.Property(x => x.Name).IsRequired().HasMaxLength(256); + builder.HasIndex(x => x.ElectionRoundId); + + builder.HasOne(x => x.ElectionRound).WithMany().HasForeignKey(e => e.ElectionRoundId); + builder.HasOne(x => x.Leader).WithMany().HasForeignKey(e => e.LeaderId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs new file mode 100644 index 000000000..df45fdd4e --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionFormAccessConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(m => new { m.CoalitionId, m.MonitoringNgoId , m.FormId}); // Composite primary key + + builder + .HasOne(m => m.MonitoringNgo) + .WithMany() + .HasForeignKey(m => m.MonitoringNgoId); + + builder + .HasOne(m => m.Coalition) + .WithMany(c => c.FormAccess) + .HasForeignKey(m => m.CoalitionId); + + builder + .HasOne(m => m.Form) + .WithMany() + .HasForeignKey(m => m.FormId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs new file mode 100644 index 000000000..64e94b7c0 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionMembershipConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(m => new { m.MonitoringNgoId, m.CoalitionId }); // Composite primary key + + builder.HasIndex(m => new { m.MonitoringNgoId, m.CoalitionId, m.ElectionRoundId }).IsUnique(); + builder.HasIndex(m => new { m.MonitoringNgoId, m.ElectionRoundId }).IsUnique(); // only one membership per election + + builder + .HasOne(m => m.MonitoringNgo) + .WithMany(mn => mn.Memberships) + .HasForeignKey(m => m.MonitoringNgoId); + + builder + .HasOne(m => m.Coalition) + .WithMany(c => c.Memberships) + .HasForeignKey(m => m.CoalitionId); + + builder.HasOne(x => x.ElectionRound).WithMany().HasForeignKey(e => e.ElectionRoundId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs index ee248ae15..2bcd08f80 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs @@ -5,9 +5,9 @@ namespace Vote.Monitor.Domain.EntitiesConfiguration; -public class FormTemplateConfiguration : IEntityTypeConfiguration +public class FormTemplateConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { builder.HasKey(e => e.Id); builder.Property(x => x.FormType).IsRequired(); diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.Designer.cs new file mode 100644 index 000000000..2b754b26c --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.Designer.cs @@ -0,0 +1,6801 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241104143346_RenameFormTypeColumn")] + partial class RenameFormTypeColumn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.cs b/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.cs new file mode 100644 index 000000000..be0285d48 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241104143346_RenameFormTypeColumn.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class RenameFormTypeColumn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "FormTemplateType", + table: "FormTemplates", + newName: "FormType"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "FormType", + table: "FormTemplates", + newName: "FormTemplateType"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs new file mode 100644 index 000000000..276fbc72c --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs @@ -0,0 +1,6967 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241111172627_AddCoalitions")] + partial class AddCoalitions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs new file mode 100644 index 000000000..84792c393 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs @@ -0,0 +1,162 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddCoalitions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Coalitions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ElectionRoundId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + LeaderId = table.Column(type: "uuid", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + LastModifiedOn = table.Column(type: "timestamp with time zone", nullable: true), + LastModifiedBy = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Coalitions", x => x.Id); + table.ForeignKey( + name: "FK_Coalitions_ElectionRounds_ElectionRoundId", + column: x => x.ElectionRoundId, + principalTable: "ElectionRounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Coalitions_MonitoringNgos_LeaderId", + column: x => x.LeaderId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CoalitionFormAccess", + columns: table => new + { + CoalitionId = table.Column(type: "uuid", nullable: false), + MonitoringNgoId = table.Column(type: "uuid", nullable: false), + FormId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CoalitionFormAccess", x => new { x.CoalitionId, x.MonitoringNgoId, x.FormId }); + table.ForeignKey( + name: "FK_CoalitionFormAccess_Coalitions_CoalitionId", + column: x => x.CoalitionId, + principalTable: "Coalitions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionFormAccess_Forms_FormId", + column: x => x.FormId, + principalTable: "Forms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionFormAccess_MonitoringNgos_MonitoringNgoId", + column: x => x.MonitoringNgoId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CoalitionMemberships", + columns: table => new + { + CoalitionId = table.Column(type: "uuid", nullable: false), + MonitoringNgoId = table.Column(type: "uuid", nullable: false), + ElectionRoundId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CoalitionMemberships", x => new { x.MonitoringNgoId, x.CoalitionId }); + table.ForeignKey( + name: "FK_CoalitionMemberships_Coalitions_CoalitionId", + column: x => x.CoalitionId, + principalTable: "Coalitions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionMemberships_ElectionRounds_ElectionRoundId", + column: x => x.ElectionRoundId, + principalTable: "ElectionRounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionMemberships_MonitoringNgos_MonitoringNgoId", + column: x => x.MonitoringNgoId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionFormAccess_FormId", + table: "CoalitionFormAccess", + column: "FormId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionFormAccess_MonitoringNgoId", + table: "CoalitionFormAccess", + column: "MonitoringNgoId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_CoalitionId", + table: "CoalitionMemberships", + column: "CoalitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_ElectionRoundId", + table: "CoalitionMemberships", + column: "ElectionRoundId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_MonitoringNgoId_CoalitionId_ElectionRo~", + table: "CoalitionMemberships", + columns: new[] { "MonitoringNgoId", "CoalitionId", "ElectionRoundId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_MonitoringNgoId_ElectionRoundId", + table: "CoalitionMemberships", + columns: new[] { "MonitoringNgoId", "ElectionRoundId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Coalitions_ElectionRoundId", + table: "Coalitions", + column: "ElectionRoundId"); + + migrationBuilder.CreateIndex( + name: "IX_Coalitions_LeaderId", + table: "Coalitions", + column: "LeaderId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CoalitionFormAccess"); + + migrationBuilder.DropTable( + name: "CoalitionMemberships"); + + migrationBuilder.DropTable( + name: "Coalitions"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs new file mode 100644 index 000000000..7754de642 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs @@ -0,0 +1,6973 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241112130734_AddDisplayNameInAspNetUsers")] + partial class AddDisplayNameInAspNetUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs new file mode 100644 index 000000000..4cceaf01f --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddDisplayNameInAspNetUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DisplayName", + table: "AspNetUsers", + type: "text", + nullable: false, + computedColumnSql: "\"FirstName\" || ' ' || \"LastName\"", + stored: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DisplayName", + table: "AspNetUsers"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.Designer.cs new file mode 100644 index 000000000..b5fff59e7 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.Designer.cs @@ -0,0 +1,6973 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241115142137_CoalitionResourcesView")] + partial class CoalitionResourcesView + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.cs b/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.cs new file mode 100644 index 000000000..70829b1d7 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241115142137_CoalitionResourcesView.cs @@ -0,0 +1,285 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class CoalitionResourcesView : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + CREATE OR REPLACE FUNCTION "GetMonitoringNgoDetails"( + electionRoundId UUID, + ngoId UUID + ) RETURNS TABLE ( + "MonitoringNgoId" UUID, + "CoalitionId" UUID, + "IsCoalitionLeader" BOOLEAN + ) AS $$ BEGIN RETURN QUERY + SELECT + MN."Id" AS "MonitoringNgoId", + -- Get coalition ID + ( + SELECT + C."Id" + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + LIMIT + 1 + ) AS "CoalitionId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = electionRoundId + AND MN."NgoId" = ngoId + LIMIT + 1; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + CREATE OR REPLACE FUNCTION "GetAvailableMonitoringObservers"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT + ) RETURNS TABLE ( + "MonitoringObserverId" UUID, + "NgoId" UUID, + "MonitoringNgoId" UUID, + "DisplayName" TEXT, + "Email" TEXT, + "PhoneNumber" TEXT, + "Tags" TEXT[], + "Status" TEXT, + "AccountStatus" TEXT, + "NgoName" varchar(256), + "IsOwnObserver" BOOLEAN + ) AS $$ BEGIN RETURN QUERY + SELECT + MO."Id" as "MonitoringObserverId", + MN."NgoId", + MO."MonitoringNgoId", + CASE WHEN ( + ( + SELECT + "IsCoalitionLeader" + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + ) + AND MN."NgoId" <> ngoId + ) THEN MO."Id"::TEXT ELSE U."DisplayName" END AS "DisplayName", + CASE WHEN ( + ( + SELECT + "IsCoalitionLeader" + FROM "GetMonitoringNgoDetails"(electionRoundId, ngoId) + ) + AND MN."NgoId" <> ngoId + ) THEN MO."Id"::TEXT ELSE U."Email"::TEXT END AS "Email", + CASE WHEN ( + ( + SELECT + "IsCoalitionLeader" + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + ) + AND MN."NgoId" <> ngoId + ) THEN MO."Id"::TEXT ELSE U."PhoneNumber" END AS "PhoneNumber", + CASE WHEN ( + ( + SELECT + "IsCoalitionLeader" + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + ) + AND MN."NgoId" <> ngoId + ) THEN '{}'::TEXT[] ELSE MO."Tags" END AS "Tags", + MO."Status" AS "Status", + U."Status" AS "AccountStatus", + N."Name" as "NgoName", + CASE + WHEN mn."NgoId" = ngoId THEN true + ELSE false + END AS "IsOwnObserver" + FROM + "Coalitions" C + INNER JOIN "GetMonitoringNgoDetails"(electionRoundId, ngoId) MND ON MND."CoalitionId" = C."Id" + INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" + INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "Ngos" N on MN."NgoId" = N."Id" + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + WHERE + MND."CoalitionId" IS NOT NULL + AND ( + ( + ( + dataSource = 'Ngo' + OR dataSource = 'Coalition' + ) + AND NOT EXISTS ( + SELECT + 1 + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + ) + AND MN."NgoId" = ngoId + ) + OR ( + dataSource = 'Coalition' + AND EXISTS ( + SELECT + 1 + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + WHERE + "IsCoalitionLeader" + ) + ) + OR ( + dataSource = 'Ngo' + AND EXISTS ( + SELECT + 1 + FROM + "GetMonitoringNgoDetails"(electionRoundId, ngoId) + WHERE + "IsCoalitionLeader" + ) + AND MN."NgoId" = ngoId + ) + OR MN."NgoId" = ngoId + ) + UNION + SELECT + MO."Id" as "MonitoringObserverId", + MN."NgoId", + MO."MonitoringNgoId", + U."DisplayName" AS "DisplayName", + U."Email"::TEXT AS "Email", + U."PhoneNumber" AS "PhoneNumber", + MO."Tags" AS "Tags", + MO."Status" AS "Status", + U."Status" AS "AccountStatus", + N."Name" as "NgoName", + TRUE as "IsOwnObserver" + FROM + "MonitoringObservers" MO + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + INNER JOIN "GetMonitoringNgoDetails"(electionRoundId, ngoId) MND ON MND."MonitoringNgoId" = MO."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "Ngos" N on MN."NgoId" = N."Id" + WHERE + (MND."CoalitionId" IS NULL or MND."IsCoalitionLeader" = false) AND MN."NgoId" = ngoId; + END; + $$ LANGUAGE plpgsql; + """); + + migrationBuilder.Sql( + """ + CREATE OR REPLACE FUNCTION "GetAvailableForms"( + electionRoundId UUID, + ngoId UUID, + dataSource TEXT + ) + RETURNS TABLE ( + "FormId" UUID, + "FormCode" varchar(256), + "FormType" TEXT, + "FormName" jsonb, + "FormDefaultLanguage" varchar(256), + "FormQuestions" jsonb, + "FormStatus" TEXT + ) AS $$ + BEGIN + RETURN QUERY + SELECT + F."Id" as "FormId", + F."Code" AS "FormCode", + F."FormType" AS "FormType", + F."Name" AS "FormName", + F."DefaultLanguage" AS "FormDefaultLanguage", + F."Questions" AS "FormQuestions", + F."Status" AS "FormStatus" + FROM "CoalitionFormAccess" CFA + INNER JOIN "Coalitions" C ON CFA."CoalitionId" = C."Id" + INNER JOIN "GetMonitoringNgoDetails"(electionRoundId, ngoId) MND ON MND."CoalitionId" = C."Id" + INNER JOIN "MonitoringNgos" MN ON MND."MonitoringNgoId" = MN."Id" + INNER JOIN "Forms" F ON CFA."FormId" = F."Id" + WHERE + MND."CoalitionId" IS NOT NULL + AND ( + ( + dataSource = 'Coalition' + AND EXISTS (SELECT 1 FROM "GetMonitoringNgoDetails"(electionRoundId, ngoId) WHERE "IsCoalitionLeader") + ) + OR ( + dataSource = 'Ngo' + --AND EXISTS (SELECT 1 FROM "GetMonitoringNgoDetails"(electionRoundId, ngoId) WHERE "IsCoalitionLeader") + AND MN."NgoId" = ngoId AND mn."Id" = cfa."MonitoringNgoId" + ) + OR MN."NgoId" = ngoId AND mn."Id" = cfa."MonitoringNgoId" + ) + UNION + SELECT + F."Id" as "FormId", + F."Code" AS "FormCode", + F."FormType" AS "FormType", + F."Name" AS "FormName", + F."DefaultLanguage" AS "FormDefaultLanguage", + F."Questions" AS "FormQuestions", + F."Status" AS "FormStatus" + FROM "Forms" F + INNER JOIN "GetMonitoringNgoDetails"(electionRoundId, ngoId) MND ON MND."MonitoringNgoId" = F."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MND."MonitoringNgoId" + WHERE + (MND."CoalitionId" IS NULL or MND."IsCoalitionLeader" = false) AND MN."NgoId" = ngoId + AND F."ElectionRoundId" = electionRoundId + UNION + SELECT + F."Id" as "FormId", + F."Code" AS "FormCode", + F."FormType" AS "FormType", + F."Name" AS "FormName", + F."DefaultLanguage" AS "FormDefaultLanguage", + F."Questions" AS "FormQuestions", + F."Status" AS "FormStatus" + FROM "PollingStationInformationForms" F + WHERE + F."ElectionRoundId" = electionRoundId; + END; + $$ LANGUAGE plpgsql; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS ""GetAvailableForms"";"); + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS ""GetAvailableMonitoringObservers"";"); + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS ""GetMonitoringNgoDetails"";"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs index 16bc49dce..5583d422d 100644 --- a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs +++ b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs @@ -189,6 +189,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsConcurrencyToken() .HasColumnType("text"); + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + b.Property("Email") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -652,6 +658,90 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CitizenReportNotes"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => { b.Property("Id") @@ -3508,7 +3598,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("jsonb"); - b.Property("FormTemplateType") + b.Property("FormType") .IsRequired() .HasColumnType("text"); @@ -6290,6 +6380,79 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Form"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => { b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") @@ -6766,6 +6929,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Notes"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => { b.Navigation("MonitoringNgos"); @@ -6780,6 +6950,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => { + b.Navigation("Memberships"); + b.Navigation("MonitoringObservers"); }); diff --git a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs index b208ebbb8..3f450352d 100644 --- a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs +++ b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs @@ -18,8 +18,7 @@ public async Task SeedAsync() if (await userManager.FindByEmailAsync(seedOption.Email) is not ApplicationUser adminUser) { - adminUser = ApplicationUser.CreatePlatformAdmin(seedOption.FirstName, seedOption.LastName, seedOption.Email, - seedOption.PhoneNumber, seedOption.Password); + adminUser = ApplicationUser.CreatePlatformAdmin(seedOption.FirstName, seedOption.LastName, seedOption.Email, seedOption.Password); logger.LogInformation("Seeding PlatformAdmin {user}", seedOption.Email); diff --git a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs index 96209c41d..5270bc0c0 100644 --- a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs +++ b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs @@ -5,7 +5,5 @@ public class PlatformAdminSeederSettings public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } - public string PhoneNumber { get; set; } public string Password { get; set; } - } diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index 2ce125b7f..8161f175b 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -7,9 +7,9 @@ using Vote.Monitor.Domain.Entities.CitizenReportAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate; using Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.ExportedDataAggregate; using Vote.Monitor.Domain.Entities.FeedbackAggregate; -using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; @@ -30,24 +30,13 @@ using Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate; using Vote.Monitor.Domain.Entities.QuickReportAggregate; using Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain; public class VoteMonitorContext : IdentityDbContext, Guid> { - private readonly ISerializerService _serializerService; - private readonly ITimeProvider _timeProvider; - private readonly ICurrentUserProvider _currentUserProvider; - - public VoteMonitorContext(DbContextOptions options, - ISerializerService serializerService, - ITimeProvider timeProvider, - ICurrentUserProvider currentUserProvider) : base(options) + public VoteMonitorContext(DbContextOptions options) : base(options) { - _serializerService = serializerService; - _timeProvider = timeProvider; - _currentUserProvider = currentUserProvider; } public DbSet Countries { get; set; } @@ -58,7 +47,7 @@ public VoteMonitorContext(DbContextOptions options, public DbSet ElectionRounds { get; set; } public DbSet ImportValidationErrors { set; get; } public DbSet AuditTrails => Set(); - public DbSet FormTemplates { set; get; } + public DbSet FormTemplates { set; get; } public DbSet Forms { set; get; } public DbSet FormSubmissions { set; get; } public DbSet PollingStationInformationForms { set; get; } @@ -86,6 +75,9 @@ public VoteMonitorContext(DbContextOptions options, public DbSet IncidentReports { get; set; } public DbSet IncidentReportNotes { get; set; } public DbSet IncidentReportAttachments { get; set; } + public DbSet Coalitions { get; set; } + public DbSet CoalitionMemberships { get; set; } + public DbSet CoalitionFormAccess { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -163,19 +155,16 @@ protected override void OnModelCreating(ModelBuilder builder) builder.ApplyConfiguration(new IncidentReportConfiguration()); builder.ApplyConfiguration(new IncidentReportNoteConfiguration()); builder.ApplyConfiguration(new IncidentReportAttachmentConfiguration()); - + builder.ApplyConfiguration(new LocationConfiguration()); + + builder.ApplyConfiguration(new CoalitionConfiguration()); + builder.ApplyConfiguration(new CoalitionMembershipConfiguration()); + builder.ApplyConfiguration(new CoalitionFormAccessConfiguration()); } protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder.ConfigureSmartEnum(); } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.AddInterceptors(new AuditingInterceptor(_currentUserProvider, _timeProvider)); - optionsBuilder.AddInterceptors(new AuditTrailInterceptor(_serializerService, _currentUserProvider, - _timeProvider)); - } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ExportFormSubmissionsJob.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ExportFormSubmissionsJob.cs index ddcae49c1..5ce96d077 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ExportFormSubmissionsJob.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ExportFormSubmissionsJob.cs @@ -51,17 +51,19 @@ public async Task Run(Guid electionRoundId, Guid ngoId, Guid exportedDataId, Can .Where(x => x.ElectionRoundId == electionRoundId) .AsNoTracking() .FirstOrDefaultAsync(ct); + var filters = exportedData.FormSubmissionsFilters ?? new ExportFormSubmissionsFilters(); var publishedForms = await context .Forms - .Where(x => x.ElectionRoundId == electionRoundId - && x.MonitoringNgo.NgoId == ngoId - && x.Status == FormStatus.Published) + .FromSqlInterpolated( + @$"SELECT f.* FROM ""Forms"" f + INNER JOIN ""GetAvailableForms""({electionRoundId}, {ngoId}, {filters.DataSource.ToString()}) af on af.""FormId"" = f.""Id"" ") + .Where(x=>x.Status == FormStatus.Published) .OrderBy(x => x.CreatedOn) .AsNoTracking() .ToListAsync(ct); - var submissions = await GetSubmissions(electionRoundId, ngoId, exportedData.FormSubmissionsFilters, ct); + var submissions = await GetSubmissions(electionRoundId, ngoId, filters, ct); foreach (var submission in submissions) { @@ -118,161 +120,314 @@ public async Task Run(Guid electionRoundId, Guid ngoId, Guid exportedDataId, Can } private async Task> GetSubmissions(Guid electionRoundId, Guid ngoId, - ExportFormSubmissionsFilters? filters, CancellationToken ct) + ExportFormSubmissionsFilters filters, CancellationToken ct) { - var sql = """ - WITH submissions AS - (SELECT psi."Id" AS "SubmissionId", - psi."PollingStationInformationFormId" AS "FormId", - 'PSI' AS "FormType", - psi."PollingStationId", - psi."MonitoringObserverId", - psi."Answers", - psi."FollowUpStatus", - psif."Questions", - psi."NumberOfQuestionsAnswered", - psi."NumberOfFlaggedAnswers", - '[]'::jsonb AS "Attachments", - '[]'::jsonb AS "Notes", - COALESCE(psi."LastModifiedOn", psi."CreatedOn") "TimeSubmitted", - psi."IsCompleted" - FROM "PollingStationInformation" psi - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) - AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR psi."IsCompleted" = @isCompleted) - AND (@questionsAnswered IS NULL - OR (@questionsAnswered = 'All' AND psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) - - UNION ALL - SELECT fs."Id" AS "SubmissionId", - f."Id" AS "FormId", - f."FormType", - fs."PollingStationId", - fs."MonitoringObserverId", - fs."Answers", - fs."FollowUpStatus", - f."Questions", - fs."NumberOfQuestionsAnswered", - fs."NumberOfFlaggedAnswers", - COALESCE((select jsonb_agg(jsonb_build_object('QuestionId', "QuestionId", 'FilePath', "FilePath", 'UploadedFileName', "UploadedFileName")) - FROM "Attachments" a - WHERE a."ElectionRoundId" = @electionRoundId - AND a."FormId" = fs."FormId" - AND a."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = a."PollingStationId"), '[]'::JSONB) AS "Attachments", - - COALESCE((select jsonb_agg(jsonb_build_object('QuestionId', "QuestionId", 'Text', "Text")) - FROM "Notes" n - WHERE n."ElectionRoundId" = @electionRoundId - AND n."FormId" = fs."FormId" - AND n."MonitoringObserverId" = fs."MonitoringObserverId" - AND fs."PollingStationId" = n."PollingStationId"), '[]'::JSONB) AS "Notes", - - COALESCE(fs."LastModifiedOn", fs."CreatedOn") "TimeSubmitted", - FS."IsCompleted" - FROM "FormSubmissions" fs - INNER JOIN "MonitoringObservers" mo ON fs."MonitoringObserverId" = mo."Id" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Forms" f on f."Id" = fs."FormId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR fs."FormId" = @formId) - AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) - AND (@questionsAnswered IS NULL - OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0)) - order by "TimeSubmitted" desc) - SELECT s."SubmissionId", - s."FormId", - s."FormType", - s."TimeSubmitted", - ps."Level1", - ps."Level2", - ps."Level3", - ps."Level4", - ps."Level5", - ps."Number", - s."MonitoringObserverId", - u."FirstName", - u."LastName", - u."Email", - u."PhoneNumber", - mo."Tags", - s."Attachments", - s."Notes", - s."Answers", - s."Questions", - s."FollowUpStatus", - s."IsCompleted" - FROM submissions s - INNER JOIN "PollingStations" ps on ps."Id" = s."PollingStationId" - INNER JOIN "MonitoringObservers" mo on mo."Id" = s."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' - OR u."FirstName" ILIKE @searchText - OR u."LastName" ILIKE @searchText - OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText) - AND (@formType IS NULL OR s."FormType" = @formType) - AND (@level1 IS NULL OR ps."Level1" = @level1) - AND (@level2 IS NULL OR ps."Level2" = @level2) - AND (@level3 IS NULL OR ps."Level3" = @level3) - AND (@level4 IS NULL OR ps."Level4" = @level4) - AND (@level5 IS NULL OR ps."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR ps."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL - OR (s."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = false) - OR (s."NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = true)) - AND (@followUpStatus IS NULL OR s."FollowUpStatus" = @followUpStatus) - AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) - AND (@hasNotes IS NULL - OR (jsonb_array_length(s."Notes") = 0 AND @hasNotes = false) - OR (jsonb_array_length(s."Notes") > 0 AND @hasNotes = true)) - AND (@hasAttachments IS NULL - OR (jsonb_array_length(s."Attachments") = 0 AND @hasAttachments = false) - OR (jsonb_array_length(s."Attachments") > 0 AND @hasAttachments = true)) - """; + var sql = + """ + WITH + SUBMISSIONS AS ( + SELECT + PSI."Id" AS "SubmissionId", + PSI."PollingStationInformationFormId" AS "FormId", + 'PSI' AS "FormType", + PSI."PollingStationId", + PSI."MonitoringObserverId", + PSI."Answers", + PSI."FollowUpStatus", + PSIF."Questions", + PSI."NumberOfQuestionsAnswered", + PSI."NumberOfFlaggedAnswers", + '[]'::JSONB AS "Attachments", + '[]'::JSONB AS "Notes", + COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") "TimeSubmitted", + PSI."IsCompleted", + MO."NgoName", + MO."DisplayName", + MO."Email", + MO."PhoneNumber", + MO."Tags" + FROM + "PollingStationInformation" PSI + INNER JOIN "PollingStationInformationForms" PSIF ON PSIF."Id" = PSI."PollingStationInformationFormId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = PSI."MonitoringObserverId" + WHERE + ( + @COALITIONMEMBERID IS NULL + OR MO."NgoId" = @COALITIONMEMBERID + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMID IS NULL + OR PSI."PollingStationInformationFormId" = @FORMID + ) + AND ( + @FROMDATE IS NULL + OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @TODATE::TIMESTAMP + ) + AND ( + @ISCOMPLETED IS NULL + OR PSI."IsCompleted" = @ISCOMPLETED + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR ( + @QUESTIONSANSWERED = 'All' + AND PSIF."NumberOfQuestions" = PSI."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'Some' + AND PSIF."NumberOfQuestions" <> PSI."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'None' + AND PSI."NumberOfQuestionsAnswered" = 0 + ) + ) + UNION ALL + SELECT + FS."Id" AS "SubmissionId", + F."Id" AS "FormId", + F."FormType", + FS."PollingStationId", + FS."MonitoringObserverId", + FS."Answers", + FS."FollowUpStatus", + F."Questions", + FS."NumberOfQuestionsAnswered", + FS."NumberOfFlaggedAnswers", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'FilePath', + "FilePath", + 'UploadedFileName', + "UploadedFileName" + ) + ) + FROM + "Attachments" A + WHERE + A."ElectionRoundId" = @ELECTIONROUNDID + AND A."FormId" = FS."FormId" + AND A."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = A."PollingStationId" + ), + '[]'::JSONB + ) AS "Attachments", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT('QuestionId', "QuestionId", 'Text', "Text") + ) + FROM + "Notes" N + WHERE + N."ElectionRoundId" = @ELECTIONROUNDID + AND N."FormId" = FS."FormId" + AND N."MonitoringObserverId" = FS."MonitoringObserverId" + AND FS."PollingStationId" = N."PollingStationId" + ), + '[]'::JSONB + ) AS "Notes", + COALESCE(FS."LastModifiedOn", FS."CreatedOn") "TimeSubmitted", + FS."IsCompleted", + MO."NgoName", + MO."DisplayName", + MO."Email", + MO."PhoneNumber", + MO."Tags" + FROM + "FormSubmissions" FS + INNER JOIN "Forms" F ON F."Id" = FS."FormId" + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + INNER JOIN "GetAvailableForms" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) AF ON FS."FormId" = AF."FormId" + WHERE + ( + @COALITIONMEMBERID IS NULL + OR MO."NgoId" = @COALITIONMEMBERID + ) + AND ( + @MONITORINGOBSERVERSTATUS IS NULL + OR MO."Status" = @MONITORINGOBSERVERSTATUS + ) + AND ( + @FORMID IS NULL + OR FS."FormId" = @FORMID + ) + AND ( + @FROMDATE IS NULL + OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @TODATE::TIMESTAMP + ) + AND ( + @ISCOMPLETED IS NULL + OR FS."IsCompleted" = @ISCOMPLETED + ) + AND ( + @QUESTIONSANSWERED IS NULL + OR ( + @QUESTIONSANSWERED = 'All' + AND F."NumberOfQuestions" = FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'Some' + AND F."NumberOfQuestions" <> FS."NumberOfQuestionsAnswered" + ) + OR ( + @QUESTIONSANSWERED = 'None' + AND FS."NumberOfQuestionsAnswered" = 0 + ) + ) + ORDER BY + "TimeSubmitted" DESC + ) + SELECT + S."SubmissionId", + S."FormId", + S."FormType", + S."TimeSubmitted", + PS."Level1", + PS."Level2", + PS."Level3", + PS."Level4", + PS."Level5", + PS."Number", + S."NgoName", + S."MonitoringObserverId", + S."DisplayName", + S."Email", + S."PhoneNumber", + S."Tags", + S."Attachments", + S."Notes", + S."Answers", + S."Questions", + S."FollowUpStatus", + S."IsCompleted" + FROM + SUBMISSIONS S + INNER JOIN "PollingStations" PS ON PS."Id" = S."PollingStationId" + WHERE + ( + @SEARCHTEXT IS NULL + OR @SEARCHTEXT = '' + OR S."DisplayName" ILIKE @SEARCHTEXT + OR S."Email" ILIKE @SEARCHTEXT + OR S."PhoneNumber" ILIKE @SEARCHTEXT + OR S."MonitoringObserverId"::TEXT ILIKE @SEARCHTEXT + ) + AND ( + @FORMTYPE IS NULL + OR S."FormType" = @FORMTYPE + ) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @POLLINGSTATIONNUMBER IS NULL + OR PS."Number" = @POLLINGSTATIONNUMBER + ) + AND ( + @HASFLAGGEDANSWERS IS NULL + OR ( + S."NumberOfFlaggedAnswers" = 0 + AND @HASFLAGGEDANSWERS = FALSE + ) + OR ( + S."NumberOfFlaggedAnswers" > 0 + AND @HASFLAGGEDANSWERS = TRUE + ) + ) + AND ( + @FOLLOWUPSTATUS IS NULL + OR S."FollowUpStatus" = @FOLLOWUPSTATUS + ) + AND ( + @TAGSFILTER IS NULL + OR CARDINALITY(@TAGSFILTER) = 0 + OR S."Tags" && @TAGSFILTER + ) + AND ( + @HASNOTES IS NULL + OR ( + JSONB_ARRAY_LENGTH(S."Notes") = 0 + AND @HASNOTES = FALSE + ) + OR ( + JSONB_ARRAY_LENGTH(S."Notes") > 0 + AND @HASNOTES = TRUE + ) + ) + AND ( + @HASATTACHMENTS IS NULL + OR ( + JSONB_ARRAY_LENGTH(S."Attachments") = 0 + AND @HASATTACHMENTS = FALSE + ) + OR ( + JSONB_ARRAY_LENGTH(S."Attachments") > 0 + AND @HASATTACHMENTS = TRUE + ) + ) + """; var queryParams = new { electionRoundId, ngoId, - searchText = $"%{filters?.SearchText?.Trim() ?? string.Empty}%", - formType = filters?.FormTypeFilter?.ToString(), - level1 = filters?.Level1Filter, - level2 = filters?.Level2Filter, - level3 = filters?.Level3Filter, - level4 = filters?.Level4Filter, - level5 = filters?.Level5Filter, - pollingStationNumber = filters?.PollingStationNumberFilter, - hasFlaggedAnswers = filters?.HasFlaggedAnswers, - followUpStatus = filters?.FollowUpStatus?.ToString(), - tagsFilter = filters?.TagsFilter ?? [], - monitoringObserverStatus = filters?.MonitoringObserverStatus?.ToString(), - formId = filters?.FormId, - hasNotes = filters?.HasNotes, - hasAttachments = filters?.HasAttachments, - questionsAnswered = filters?.QuestionsAnswered?.ToString(), - fromDate = filters?.FromDateFilter?.ToString("O"), - toDate = filters?.ToDateFilter?.ToString("O"), - isCompleted = filters?.IsCompletedFilter + coalitionMemberId = filters.CoalitionMemberId, + dataSource = filters.DataSource.ToString(), + searchText = $"%{filters.SearchText?.Trim() ?? string.Empty}%", + formType = filters.FormTypeFilter?.ToString(), + level1 = filters.Level1Filter, + level2 = filters.Level2Filter, + level3 = filters.Level3Filter, + level4 = filters.Level4Filter, + level5 = filters.Level5Filter, + pollingStationNumber = filters.PollingStationNumberFilter, + hasFlaggedAnswers = filters.HasFlaggedAnswers, + followUpStatus = filters.FollowUpStatus?.ToString(), + tagsFilter = filters.TagsFilter ?? [], + monitoringObserverStatus = filters.MonitoringObserverStatus?.ToString(), + formId = filters.FormId, + hasNotes = filters.HasNotes, + hasAttachments = filters.HasAttachments, + questionsAnswered = filters.QuestionsAnswered?.ToString(), + fromDate = filters.FromDateFilter?.ToString("O"), + toDate = filters.ToDateFilter?.ToString("O"), + isCompleted = filters.IsCompletedFilter }; IEnumerable submissions = []; @@ -284,4 +439,4 @@ OR u."Email" ILIKE @searchText var submissionsData = submissions.ToList(); return submissionsData; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTable.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTable.cs index 5e91043a9..b5ff6b7af 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTable.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTable.cs @@ -28,9 +28,9 @@ private FormSubmissionsDataTable(Guid formId, string defaultLanguage, IReadOnlyL "Level4", "Level5", "Number", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber" ]); diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTableGenerator.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTableGenerator.cs index 0d638f0eb..d77016459 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTableGenerator.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/FormSubmissionsDataTableGenerator.cs @@ -51,9 +51,9 @@ public FormSubmissionsDataTableGenerator ForSubmission(SubmissionModel submissio submission.Level4, submission.Level5, submission.Number, + submission.NgoName, submission.MonitoringObserverId.ToString(), - submission.FirstName, - submission.LastName, + submission.DisplayName, submission.Email, submission.PhoneNumber }; diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ReadModels/SubmissionModel.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ReadModels/SubmissionModel.cs index 199cbfb77..3f0addb20 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ReadModels/SubmissionModel.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/FormSubmissions/ReadModels/SubmissionModel.cs @@ -12,6 +12,7 @@ public class SubmissionModel [JsonConverter(typeof(SmartEnumNameConverter))] public SubmissionFollowUpStatus FollowUpStatus { get; set; } + public Guid FormId { get; set; } public DateTime TimeSubmitted { get; init; } public string Level1 { get; init; } = default!; @@ -21,8 +22,8 @@ public class SubmissionModel public string Level5 { get; init; } = default!; public string Number { get; init; } = default!; public Guid MonitoringObserverId { get; init; } - public string FirstName { get; init; } = default!; - public string LastName { get; init; } = default!; + public string NgoName { get; init; } = default!; + public string DisplayName { get; init; } = default!; public string Email { get; init; } = default!; public string PhoneNumber { get; init; } = default!; public bool IsCompleted { get; init; } = default!; diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ExportIncidentReportsJob.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ExportIncidentReportsJob.cs index f1ff33713..e159fa320 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ExportIncidentReportsJob.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ExportIncidentReportsJob.cs @@ -56,8 +56,8 @@ public async Task Run(Guid electionRoundId, Guid ngoId, Guid exportedDataId, Can .AsNoTracking() .ToListAsync(ct); - var incidentReports = - await GetIncidentReports(electionRoundId, ngoId, exportedData.IncidentReportsFilters, ct); + var filters = exportedData.IncidentReportsFilters ?? new ExportIncidentReportsFilters(); + var incidentReports = await GetIncidentReports(electionRoundId, ngoId, filters, ct); foreach (var attachment in incidentReports.SelectMany( incidentReportModel => incidentReportModel.Attachments)) @@ -102,175 +102,177 @@ public async Task Run(Guid electionRoundId, Guid ngoId, Guid exportedDataId, Can } private async Task GetIncidentReports(Guid electionRoundId, Guid ngoId, - ExportIncidentReportsFilters? filters, CancellationToken ct) + ExportIncidentReportsFilters filters, CancellationToken ct) { - var sql = """ - WITH - INCIDENT_REPORTS AS ( - SELECT - IR."Id" AS "IncidentReportId", - F."Id" as "FormId", - F."Code" AS "FormCode", - F."Name" AS "FormName", - F."DefaultLanguage" "FormDefaultLanguage", - IR."LocationType", - IR."LocationDescription", - IR."PollingStationId", - IR."MonitoringObserverId", - IR."NumberOfQuestionsAnswered", - IR."NumberOfFlaggedAnswers", - IR."Answers", - IR."IsCompleted", - COALESCE( - ( - SELECT - JSONB_AGG( - JSONB_BUILD_OBJECT( - 'QuestionId', - "QuestionId", - 'FilePath', - "FilePath", - 'UploadedFileName', - "UploadedFileName" - ) - ) - FROM - "IncidentReportAttachments" A - WHERE - A."ElectionRoundId" = @electionRoundId - AND A."FormId" = IR."FormId" - AND A."IncidentReportId" = IR."Id" - ), - '[]'::JSONB - ) AS "Attachments", - COALESCE( - ( - SELECT - JSONB_AGG( - JSONB_BUILD_OBJECT('QuestionId', "QuestionId", 'Text', "Text") - ) - FROM - "IncidentReportNotes" N - WHERE - N."ElectionRoundId" = @electionRoundId - AND N."FormId" = IR."FormId" - AND N."IncidentReportId" = IR."Id" - ), - '[]'::JSONB - ) AS "Notes", - COALESCE(IR."LastModifiedOn", IR."CreatedOn") AS "TimeSubmitted", - IR."FollowUpStatus" - FROM - "IncidentReports" IR - INNER JOIN "Forms" F ON F."Id" = IR."FormId" - INNER JOIN "MonitoringObservers" MO ON IR."MonitoringObserverId" = MO."Id" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND (@monitoringObserverStatus IS NULL OR MO."Status" = @monitoringObserverStatus) - AND (@formId IS NULL OR IR."FormId" = @formId) - AND (@locationType IS NULL OR IR."LocationType" = @locationType) - AND (@questionsAnswered IS NULL - OR (@questionsAnswered = 'All' AND F."NumberOfQuestions" = IR."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'Some' AND F."NumberOfQuestions" <> IR."NumberOfQuestionsAnswered") - OR (@questionsAnswered = 'None' AND IR."NumberOfQuestionsAnswered" = 0) - ) - AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted) - ) - SELECT - IR."IncidentReportId", - IR."TimeSubmitted", - IR."FormId", - IR."FormCode", - IR."FormName", - IR."FormDefaultLanguage", - IR."LocationType", - IR."LocationDescription", - PS."Id" AS "PollingStationId", - PS."Level1", - PS."Level2", - PS."Level3", - PS."Level4", - PS."Level5", - PS."Number", - IR."MonitoringObserverId", - U."FirstName", - U."LastName", - U."Email", - U."PhoneNumber", - IR."Answers", - IR."Attachments", - IR."Notes", - IR."FollowUpStatus", - IR."NumberOfFlaggedAnswers", - IR."IsCompleted" - FROM - INCIDENT_REPORTS IR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = IR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" - LEFT JOIN "PollingStations" PS ON PS."Id" = IR."PollingStationId" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND ( - @searchText IS NULL - OR @searchText = '' - OR U."FirstName" ILIKE @searchText - OR U."LastName" ILIKE @searchText - OR U."Email" ILIKE @searchText - OR U."PhoneNumber" ILIKE @searchText - ) - AND (@level1 IS NULL OR PS."Level1" = @level1) - AND (@level2 IS NULL OR PS."Level2" = @level2) - AND (@level3 IS NULL OR PS."Level3" = @level3) - AND (@level4 IS NULL OR PS."Level4" = @level4) - AND (@level5 IS NULL OR PS."Level5" = @level5) - AND (@pollingStationNumber IS NULL OR PS."Number" = @pollingStationNumber) - AND (@hasFlaggedAnswers IS NULL - OR (IR."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = FALSE) - OR (IR."NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = TRUE) - ) - AND (@followUpStatus IS NULL OR IR."FollowUpStatus" = @followUpStatus) - AND (@tagsFilter IS NULL OR CARDINALITY(@tagsFilter) = 0 OR MO."Tags" && @tagsFilter) - AND (@hasNotes IS NULL - OR (jsonb_array_length(IR."Notes") = 0 AND @hasNotes = FALSE) - OR (jsonb_array_length(IR."Notes") > 0 AND @hasNotes = TRUE) - ) - AND (@hasAttachments IS NULL - OR (jsonb_array_length(IR."Attachments") = 0 AND @hasAttachments = FALSE) - OR (jsonb_array_length(IR."Attachments") > 0 AND @hasAttachments = TRUE) - ) - ORDER BY IR."TimeSubmitted" DESC; - """; + var sql = + """ + WITH + INCIDENT_REPORTS AS ( + SELECT + IR."Id" AS "IncidentReportId", + F."Id" as "FormId", + F."Code" AS "FormCode", + F."Name" AS "FormName", + F."DefaultLanguage" "FormDefaultLanguage", + IR."LocationType", + IR."LocationDescription", + IR."PollingStationId", + IR."MonitoringObserverId", + IR."NumberOfQuestionsAnswered", + IR."NumberOfFlaggedAnswers", + IR."Answers", + IR."IsCompleted", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuestionId', + "QuestionId", + 'FilePath', + "FilePath", + 'UploadedFileName', + "UploadedFileName" + ) + ) + FROM + "IncidentReportAttachments" A + WHERE + A."ElectionRoundId" = @electionRoundId + AND A."FormId" = IR."FormId" + AND A."IncidentReportId" = IR."Id" + ), + '[]'::JSONB + ) AS "Attachments", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT('QuestionId', "QuestionId", 'Text', "Text") + ) + FROM + "IncidentReportNotes" N + WHERE + N."ElectionRoundId" = @electionRoundId + AND N."FormId" = IR."FormId" + AND N."IncidentReportId" = IR."Id" + ), + '[]'::JSONB + ) AS "Notes", + COALESCE(IR."LastModifiedOn", IR."CreatedOn") AS "TimeSubmitted", + IR."FollowUpStatus", + mo."DisplayName", + mo."NgoName", + mo."PhoneNumber", + mo."Email", + mo."Tags" + + FROM + "IncidentReports" IR + INNER JOIN "Forms" F ON F."Id" = IR."FormId" + INNER JOIN "GetAvailableMonitoringObservers"(@electionRoundId, @ngoId, @dataSource) MO ON MO."MonitoringObserverId" = FS."MonitoringObserverId" + inner join "GetAvailableForms"(@electionRoundId, @ngoId, @dataSource) af on IR."FormId" = af."FormId" + WHERE + (@COALITIONMEMBERID IS NULL OR mo."NgoId" = @COALITIONMEMBERID) + AND (@monitoringObserverStatus IS NULL OR MO."Status" = @monitoringObserverStatus) + AND (@formId IS NULL OR IR."FormId" = @formId) + AND (@locationType IS NULL OR IR."LocationType" = @locationType) + AND (@questionsAnswered IS NULL + OR (@questionsAnswered = 'All' AND F."NumberOfQuestions" = IR."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'Some' AND F."NumberOfQuestions" <> IR."NumberOfQuestionsAnswered") + OR (@questionsAnswered = 'None' AND IR."NumberOfQuestionsAnswered" = 0) + ) + AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) + AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) + AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted) + ) + SELECT + IR."IncidentReportId", + IR."TimeSubmitted", + IR."FormId", + IR."FormCode", + IR."FormName", + IR."FormDefaultLanguage", + IR."LocationType", + IR."LocationDescription", + PS."Id" AS "PollingStationId", + PS."Level1", + PS."Level2", + PS."Level3", + PS."Level4", + PS."Level5", + PS."Number", + IR."NgoName", + IR."MonitoringObserverId", + IR."DisplayName", + IR."Email", + IR."PhoneNumber", + IR."Answers", + IR."Attachments", + IR."Notes", + IR."FollowUpStatus", + IR."NumberOfFlaggedAnswers", + IR."IsCompleted" + FROM + INCIDENT_REPORTS IR + LEFT JOIN "PollingStations" PS ON PS."Id" = IR."PollingStationId" + WHERE + ( + @searchText IS NULL + OR @searchText = '' + OR IR."DisplayName" ILIKE @searchText + OR IR."MonitoringObserverId"::TEXT ILIKE @searchText + OR IR."Email" ILIKE @searchText + OR IR."PhoneNumber" ILIKE @searchText + ) + AND (@level1 IS NULL OR PS."Level1" = @level1) + AND (@level2 IS NULL OR PS."Level2" = @level2) + AND (@level3 IS NULL OR PS."Level3" = @level3) + AND (@level4 IS NULL OR PS."Level4" = @level4) + AND (@level5 IS NULL OR PS."Level5" = @level5) + AND (@pollingStationNumber IS NULL OR PS."Number" = @pollingStationNumber) + AND (@hasFlaggedAnswers IS NULL + OR (IR."NumberOfFlaggedAnswers" = 0 AND @hasFlaggedAnswers = FALSE) + OR (IR."NumberOfFlaggedAnswers" > 0 AND @hasFlaggedAnswers = TRUE) + ) + AND (@followUpStatus IS NULL OR IR."FollowUpStatus" = @followUpStatus) + AND (@tagsFilter IS NULL OR CARDINALITY(@tagsFilter) = 0 OR IR."Tags" && @tagsFilter) + AND (@hasNotes IS NULL + OR (jsonb_array_length(IR."Notes") = 0 AND @hasNotes = FALSE) + OR (jsonb_array_length(IR."Notes") > 0 AND @hasNotes = TRUE) + ) + AND (@hasAttachments IS NULL + OR (jsonb_array_length(IR."Attachments") = 0 AND @hasAttachments = FALSE) + OR (jsonb_array_length(IR."Attachments") > 0 AND @hasAttachments = TRUE) + ) + ORDER BY IR."TimeSubmitted" DESC; + """; var queryParams = new { electionRoundId, ngoId, - searchText = $"%{filters?.SearchText?.Trim() ?? string.Empty}%", - LocationType = filters?.LocationType?.ToString(), - level1 = filters?.Level1Filter, - level2 = filters?.Level2Filter, - level3 = filters?.Level3Filter, - level4 = filters?.Level4Filter, - level5 = filters?.Level5Filter, - pollingStationNumber = filters?.PollingStationNumberFilter, - hasFlaggedAnswers = filters?.HasFlaggedAnswers, - followUpStatus = filters?.FollowUpStatus?.ToString(), - tagsFilter = filters?.TagsFilter ?? [], - monitoringObserverStatus = filters?.MonitoringObserverStatus?.ToString(), - formId = filters?.FormId, - hasNotes = filters?.HasNotes, - hasAttachments = filters?.HasAttachments, - questionsAnswered = filters?.QuestionsAnswered?.ToString(), - fromDate = filters?.FromDateFilter?.ToString("O"), - toDate = filters?.ToDateFilter?.ToString("O"), - isCompleted = filters?.IsCompletedFilter + dataSource = filters.DataSource, + coalitionMemberId = filters.CoalitionMemberId, + searchText = $"%{filters.SearchText?.Trim() ?? string.Empty}%", + LocationType = filters.LocationType?.ToString(), + level1 = filters.Level1Filter, + level2 = filters.Level2Filter, + level3 = filters.Level3Filter, + level4 = filters.Level4Filter, + level5 = filters.Level5Filter, + pollingStationNumber = filters.PollingStationNumberFilter, + hasFlaggedAnswers = filters.HasFlaggedAnswers, + followUpStatus = filters.FollowUpStatus?.ToString(), + tagsFilter = filters.TagsFilter ?? [], + monitoringObserverStatus = filters.MonitoringObserverStatus?.ToString(), + formId = filters.FormId, + hasNotes = filters.HasNotes, + hasAttachments = filters.HasAttachments, + questionsAnswered = filters.QuestionsAnswered?.ToString(), + fromDate = filters.FromDateFilter?.ToString("O"), + toDate = filters.ToDateFilter?.ToString("O"), + isCompleted = filters.IsCompletedFilter }; IEnumerable incidentReports; @@ -282,4 +284,4 @@ OR U."PhoneNumber" ILIKE @searchText var incidentReportsData = incidentReports.ToArray(); return incidentReportsData; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTable.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTable.cs index bffccdd4f..bd00cb3c9 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTable.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTable.cs @@ -30,19 +30,14 @@ private IncidentReportsDataTable(Guid formId, string defaultLanguage, IReadOnlyL "Level4", "Level5", "Number", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber" ]); } - public static IncidentReportsDataTable FromForm(PollingStationInformationForm psiForm) - { - return new IncidentReportsDataTable(psiForm.Id, psiForm.DefaultLanguage, psiForm.Questions); - } - public static IncidentReportsDataTable FromForm(Form form) { return new IncidentReportsDataTable(form.Id, form.DefaultLanguage, form.Questions); @@ -52,4 +47,4 @@ public IncidentReportsDataTableGenerator WithData() { return IncidentReportsDataTableGenerator.For(_header, _dataTable, _formId, _answerWriters); } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTableGenerator.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTableGenerator.cs index 8869e3d32..aa3e0e503 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTableGenerator.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/IncidentReportsDataTableGenerator.cs @@ -57,9 +57,9 @@ public IncidentReportsDataTableGenerator For(params IncidentReportModel[] incide incidentReport.Level5 ?? "", incidentReport.Number ?? "", + incidentReport.NgoName, incidentReport.MonitoringObserverId.ToString(), - incidentReport.FirstName, - incidentReport.LastName, + incidentReport.DisplayName, incidentReport.Email, incidentReport.PhoneNumber }; @@ -112,4 +112,4 @@ private void MapToAnswerData(IncidentReportModel incidentReport) return (_header, _dataTable); } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ReadModels/IncidentReportModel.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ReadModels/IncidentReportModel.cs index 9cba03071..af5a78290 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ReadModels/IncidentReportModel.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/IncidentReports/ReadModels/IncidentReportModel.cs @@ -26,13 +26,13 @@ public class IncidentReportModel public string? Level4 { get; set; } = string.Empty; public string? Level5 { get; set; } = string.Empty; public string? Number { get; set; } = string.Empty; + public string NgoName { get; set; } = default!; public Guid MonitoringObserverId { get; set; } - public string FirstName { get; set; } = default!; - public string LastName { get; set; } = default!; + public string DisplayName { get; set; } = default!; public string Email { get; set; } = default!; public string PhoneNumber { get; set; } = default!; public bool IsCompleted { get; set; } = default!; public BaseAnswer[] Answers { get; set; } = []; public SubmissionNoteModel[] Notes { get; set; } = []; public SubmissionAttachmentModel[] Attachments { get; set; } = []; -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ExportQuickReportsJob.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ExportQuickReportsJob.cs index 71cf21792..9a88117ce 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ExportQuickReportsJob.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ExportQuickReportsJob.cs @@ -2,6 +2,7 @@ using Job.Contracts.Jobs; using Microsoft.EntityFrameworkCore; using Vote.Monitor.Core.FileGenerators; +using Vote.Monitor.Core.Models; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Core.Services.Time; using Vote.Monitor.Domain; @@ -88,85 +89,128 @@ public async Task Run(Guid electionRoundId, Guid ngoId, Guid exportedDataId, Can } private async Task> GetQuickReports(Guid electionRoundId, Guid ngoId, - ExportQuickReportsFilters? filters, CancellationToken ct) + ExportQuickReportsFilters filters, CancellationToken ct) { - var sql = """ - SELECT - QR."Id", - QR."QuickReportLocationType", - QR."IncidentCategory", - QR."MonitoringObserverId", - COALESCE(QR."LastModifiedOn", QR."CreatedOn") AS "Timestamp", - QR."Title", - QR."Description", - QR."FollowUpStatus", - COALESCE((select jsonb_agg(jsonb_build_object('QuickReportId', "QuickReportId", 'FileName', "FileName", 'MimeType', "MimeType", 'FilePath', "FilePath", 'UploadedFileName', "UploadedFileName", 'TimeSubmitted', COALESCE("LastModifiedOn", "CreatedOn"))) - FROM "QuickReportAttachments" qra - WHERE qra."ElectionRoundId" = @electionRoundId AND qra."QuickReportId" = qr."Id"),'[]'::JSONB) AS "Attachments", - O."FirstName", - O."LastName", - O."Email", - O."PhoneNumber", - QR."PollingStationDetails", - PS."Id" AS "PollingStationId", - PS."Level1", - PS."Level2", - PS."Level3", - PS."Level4", - PS."Level5", - PS."Number", - PS."Address" - FROM - "QuickReports" QR - INNER JOIN "MonitoringObservers" MO ON MO."Id" = QR."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "AspNetUsers" O ON MO."ObserverId" = O."Id" - LEFT JOIN "PollingStations" PS ON PS."Id" = QR."PollingStationId" - WHERE - QR."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - AND (@followUpStatus IS NULL or QR."FollowUpStatus" = @followUpStatus) - AND (@quickReportLocationType IS NULL or QR."QuickReportLocationType" = @quickReportLocationType) - AND (@incidentCategory IS NULL or QR."IncidentCategory" = @incidentCategory) - AND ( - @level1 IS NULL - OR PS."Level1" = @level1 - ) - AND ( - @level2 IS NULL - OR PS."Level2" = @level2 - ) - AND ( - @level3 IS NULL - OR PS."Level3" = @level3 - ) - AND ( - @level4 IS NULL - OR PS."Level4" = @level4 - ) - AND ( - @level5 IS NULL - OR PS."Level5" = @level5 - ) - AND (@fromDate is NULL OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") <= @toDate::timestamp) - ORDER BY COALESCE(QR."LastModifiedOn", QR."CreatedOn") DESC - """; + var sql = + """ + SELECT + QR."Id", + QR."QuickReportLocationType", + QR."IncidentCategory", + QR."MonitoringObserverId", + COALESCE(QR."LastModifiedOn", QR."CreatedOn") AS "Timestamp", + QR."Title", + QR."Description", + QR."FollowUpStatus", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT( + 'QuickReportId', + "QuickReportId", + 'FileName', + "FileName", + 'MimeType', + "MimeType", + 'FilePath', + "FilePath", + 'UploadedFileName', + "UploadedFileName", + 'TimeSubmitted', + COALESCE("LastModifiedOn", "CreatedOn") + ) + ) + FROM + "QuickReportAttachments" QRA + WHERE + QRA."ElectionRoundId" = @ELECTIONROUNDID + AND QRA."QuickReportId" = QR."Id" + ), + '[]'::JSONB + ) AS "Attachments", + MO."NgoName", + MO."DisplayName", + MO."Email", + MO."PhoneNumber", + QR."PollingStationDetails", + PS."Id" AS "PollingStationId", + PS."Level1", + PS."Level2", + PS."Level3", + PS."Level4", + PS."Level5", + PS."Number", + PS."Address" + FROM + "QuickReports" QR + INNER JOIN "GetAvailableMonitoringObservers" (@ELECTIONROUNDID, @NGOID, @DATASOURCE) MO ON MO."MonitoringObserverId" = QR."MonitoringObserverId" + LEFT JOIN "PollingStations" PS ON PS."Id" = QR."PollingStationId" + WHERE + ( + @COALITIONMEMBERID IS NULL + OR MO."NgoId" = @COALITIONMEMBERID + ) + AND ( + @FOLLOWUPSTATUS IS NULL + OR QR."FollowUpStatus" = @FOLLOWUPSTATUS + ) + AND ( + @QUICKREPORTLOCATIONTYPE IS NULL + OR QR."QuickReportLocationType" = @QUICKREPORTLOCATIONTYPE + ) + AND ( + @INCIDENTCATEGORY IS NULL + OR QR."IncidentCategory" = @INCIDENTCATEGORY + ) + AND ( + @LEVEL1 IS NULL + OR PS."Level1" = @LEVEL1 + ) + AND ( + @LEVEL2 IS NULL + OR PS."Level2" = @LEVEL2 + ) + AND ( + @LEVEL3 IS NULL + OR PS."Level3" = @LEVEL3 + ) + AND ( + @LEVEL4 IS NULL + OR PS."Level4" = @LEVEL4 + ) + AND ( + @LEVEL5 IS NULL + OR PS."Level5" = @LEVEL5 + ) + AND ( + @FROMDATE IS NULL + OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") >= @FROMDATE::TIMESTAMP + ) + AND ( + @TODATE IS NULL + OR COALESCE(QR."LastModifiedOn", QR."CreatedOn") <= @TODATE::TIMESTAMP + ) + ORDER BY + COALESCE(QR."LastModifiedOn", QR."CreatedOn") DESC + """; var queryArgs = new { electionRoundId, ngoId, - level1 = filters?.Level1Filter, - level2 = filters?.Level2Filter, - level3 = filters?.Level3Filter, - level4 = filters?.Level4Filter, - level5 = filters?.Level5Filter, - followUpStatus = filters?.QuickReportFollowUpStatus?.ToString(), - quickReportLocationType = filters?.QuickReportLocationType?.ToString(), - incidentCategory = filters?.IncidentCategory?.ToString(), - fromDate = filters?.FromDateFilter?.ToString("O"), - toDate = filters?.ToDateFilter?.ToString("O"), + dataSource = filters.DataSource.ToString(), + coalitionMemberId = filters.CoalitionMemberId, + level1 = filters.Level1Filter, + level2 = filters.Level2Filter, + level3 = filters.Level3Filter, + level4 = filters.Level4Filter, + level5 = filters.Level5Filter, + followUpStatus = filters.QuickReportFollowUpStatus?.ToString(), + quickReportLocationType = filters.QuickReportLocationType?.ToString(), + incidentCategory = filters.IncidentCategory?.ToString(), + fromDate = filters.FromDateFilter?.ToString("O"), + toDate = filters.ToDateFilter?.ToString("O"), }; IEnumerable quickReports = []; @@ -178,4 +222,4 @@ ORDER BY COALESCE(QR."LastModifiedOn", QR."CreatedOn") DESC var quickReportsData = quickReports.ToList(); return quickReportsData; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTable.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTable.cs index f8d4a9348..709a92715 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTable.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTable.cs @@ -15,9 +15,9 @@ private QuickReportsDataTable() "TimeSubmitted", "FollowUpStatus", "IncidentCategory", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber", "LocationType", diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTableGenerator.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTableGenerator.cs index 3b009f27e..31bea9143 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTableGenerator.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/QuickReportsDataTableGenerator.cs @@ -25,25 +25,25 @@ public QuickReportsDataTableGenerator For(QuickReportModel quickReport) var row = new List { - quickReport.Id.ToString(), - quickReport.Timestamp.ToString("s"), - quickReport.FollowUpStatus.Value, - quickReport.IncidentCategory.Value, - quickReport.MonitoringObserverId.ToString(), - quickReport.FirstName, - quickReport.LastName, - quickReport.Email, - quickReport.PhoneNumber, - quickReport.QuickReportLocationType.Value, - quickReport.Level1 ?? "", - quickReport.Level2 ?? "", - quickReport.Level3 ?? "", - quickReport.Level4 ?? "", - quickReport.Level5 ?? "", - quickReport.Number ?? "", - quickReport.PollingStationDetails ?? "", - quickReport.Title, - quickReport.Description + quickReport.Id.ToString(), + quickReport.Timestamp.ToString("s"), + quickReport.FollowUpStatus.Value, + quickReport.IncidentCategory.Value, + quickReport.NgoName, + quickReport.MonitoringObserverId.ToString(), + quickReport.DisplayName, + quickReport.Email, + quickReport.PhoneNumber, + quickReport.QuickReportLocationType.Value, + quickReport.Level1 ?? "", + quickReport.Level2 ?? "", + quickReport.Level3 ?? "", + quickReport.Level4 ?? "", + quickReport.Level5 ?? "", + quickReport.Number ?? "", + quickReport.PollingStationDetails ?? "", + quickReport.Title, + quickReport.Description }; _dataTable.Add(row); @@ -81,7 +81,8 @@ private void MapQuickReport(QuickReportModel quickReport) var quickReport = _quickReports[i]; var row = _dataTable[i]; - var attachmentsUrls = PadListToTheRight(quickReport.AttachmentUrls, longestAttachmentUrlsColumnHeader.Count, string.Empty); + var attachmentsUrls = PadListToTheRight(quickReport.AttachmentUrls, longestAttachmentUrlsColumnHeader.Count, + string.Empty); row.AddRange(attachmentsUrls); } diff --git a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ReadModels/QuickReportModel.cs b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ReadModels/QuickReportModel.cs index 493733a82..3567a16ae 100644 --- a/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ReadModels/QuickReportModel.cs +++ b/api/src/Vote.Monitor.Hangfire/Jobs/Export/QuickReports/ReadModels/QuickReportModel.cs @@ -15,8 +15,8 @@ public class QuickReportModel public string Title { get; set; } public string Description { get; set; } public Guid MonitoringObserverId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } + public string NgoName { get; set; } + public string DisplayName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public string? PollingStationDetails { get; set; } @@ -35,4 +35,4 @@ public class QuickReportModel public IncidentCategory IncidentCategory { get; set; } public QuickReportAttachmentModel[] Attachments { get; init; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj b/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj index eb6d086e9..7018ae2e4 100644 --- a/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj +++ b/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj @@ -8,6 +8,8 @@ + + diff --git a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs index 84fbf8199..6ca09ce69 100644 --- a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs +++ b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs @@ -13,7 +13,7 @@ internal MonitoringNgoView ArchivedElectionRound() NgoId = Guid.NewGuid(), NgoStatus = NgoStatus.Activated, MonitoringNgoId = Guid.NewGuid(), - MonitoringNgoStatus = MonitoringNgoStatus.Active, + MonitoringNgoStatus = MonitoringNgoStatus.Active }; } internal MonitoringNgoView DeactivatedNgo() @@ -25,7 +25,7 @@ internal MonitoringNgoView DeactivatedNgo() NgoId = Guid.NewGuid(), NgoStatus = NgoStatus.Deactivated, MonitoringNgoId = Guid.NewGuid(), - MonitoringNgoStatus = MonitoringNgoStatus.Active, + MonitoringNgoStatus = MonitoringNgoStatus.Active }; } internal MonitoringNgoView SuspendedMonitoringNgo() diff --git a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/MonitoringNgoAdminOrObserverAuthorizationHandlerTests.cs b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/MonitoringNgoAdminOrObserverAuthorizationHandlerTests.cs index e0ba11fb6..8ee90fda9 100644 --- a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/MonitoringNgoAdminOrObserverAuthorizationHandlerTests.cs +++ b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/MonitoringNgoAdminOrObserverAuthorizationHandlerTests.cs @@ -62,7 +62,7 @@ public async Task HandleRequirementAsync_MonitoringNgoNotFound_Failure() } [Fact] - public async Task HandleRequirementAsync_ElectionRoundArchived_Failure() + public async Task HandleRequirementAsync_ElectionRoundArchived_Success() { // Arrange _currentUserRoleProvider.IsNgoAdmin().Returns(true); @@ -78,7 +78,7 @@ public async Task HandleRequirementAsync_ElectionRoundArchived_Failure() await _handler.HandleAsync(_context); // Assert - _context.HasSucceeded.Should().BeFalse(); + _context.HasSucceeded.Should().BeTrue(); } [Fact] diff --git a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs index 53a833a00..583895e3e 100644 --- a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs +++ b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs @@ -74,7 +74,7 @@ public async Task HandleRequirementAsync_NgoIsDeactivated_Failure() NgoId = _ngoId, NgoStatus = NgoStatus.Deactivated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Active, + UserStatus = UserStatus.Active }); // Act @@ -99,7 +99,7 @@ public async Task HandleRequirementAsync_UserIsDeactivated_Failure() NgoId = _ngoId, NgoStatus = NgoStatus.Activated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Deactivated, + UserStatus = UserStatus.Deactivated }); // Act @@ -124,7 +124,7 @@ public async Task HandleRequirementAsync_ValidNgoAdmin_Success() NgoId = _ngoId, NgoStatus = NgoStatus.Activated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Active, + UserStatus = UserStatus.Active }); // Act diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs index 8c7e7c0f4..4cbc87d3e 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs @@ -173,7 +173,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() PollingStationId = pollingStationId, FormId = formId, QuestionId = questionId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -208,7 +208,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() PollingStationId = pollingStationId, FormId = formId, QuestionId = questionId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs index b7c15ada7..d19829ec9 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -75,7 +75,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -109,7 +109,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() var request = new Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index a264c547e..6a2bc135a 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -82,7 +82,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -117,7 +117,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 63e50a8f8..69d9a0e38 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -59,7 +59,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -88,7 +88,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -123,7 +123,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs index d2f1be59b..6e44fdffd 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs @@ -26,7 +26,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new Delete.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = _validator.TestValidate(model); diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs index 9cd5f773c..d82cc83a9 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs @@ -38,7 +38,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new GetById.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index 07f40015b..c7abee55e 100644 --- a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -58,7 +58,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 36db172df..a32706e1c 100644 --- a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -36,7 +36,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = citizenReportAttachment.ElectionRoundId, CitizenReportId = citizenReportAttachment.CitizenReportId, - FormId = citizenReportAttachment.FormId, + FormId = citizenReportAttachment.FormId }; var result = await _endpoint.ExecuteAsync(request, default); @@ -64,7 +64,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs index ac0154ad3..3460fc3ac 100644 --- a/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -33,7 +33,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() var request = new List.Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 29c4c454b..54daa5710 100644 --- a/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -37,7 +37,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new List.Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs index 281d0a9b9..00da9e4f0 100644 --- a/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -35,7 +35,7 @@ public async Task ShouldReturnNotFound_WhenLocationNotFound() var request = new Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -63,7 +63,7 @@ public async Task ShouldReturnNotFound_WhenFormNotFound() var request = new Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 986927a3f..4c8833fa3 100644 --- a/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -95,7 +95,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormId = Guid.NewGuid(), ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - LocationId = Guid.NewGuid(), + LocationId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs index 68481192b..6c6d91493 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs @@ -34,7 +34,7 @@ public async Task ShouldReturnNotFound_WhenUserIsNotAuthorized() var request = new ListMy.Request { ElectionRoundId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs index a5c1cb4dc..4085ee50c 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; @@ -11,6 +12,7 @@ public class UpsertEndpointTests private readonly IReadRepository _pollingStationRepository; private readonly IReadRepository _monitoringObserverRepository; private readonly IReadRepository _formRepository; + private readonly IReadRepository _coalitionRepository; private readonly IAuthorizationService _authorizationService; private readonly Upsert.Endpoint _endpoint; @@ -20,12 +22,14 @@ public UpsertEndpointTests() _repository = Substitute.For>(); _pollingStationRepository = Substitute.For>(); _monitoringObserverRepository = Substitute.For>(); + _coalitionRepository = Substitute.For>(); _formRepository = Substitute.For>(); _authorizationService = Substitute.For(); - + _endpoint = Factory.Create(_repository, _pollingStationRepository, _monitoringObserverRepository, + _coalitionRepository, _formRepository, _authorizationService); @@ -65,7 +69,7 @@ public async Task ShouldReturnNotFound_WhenFormSubmissionFormNotFound() { // Arrange _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .ReturnsNull(); // Act @@ -91,7 +95,7 @@ public async Task ShouldUpdateFormSubmission_WhenFormSubmissionExists() // Arrange var form = new FormAggregateFaker(status: FormStatus.Published).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); var formSubmission = new FormSubmissionFaker().Generate(); @@ -202,7 +206,7 @@ public async Task ShouldThrow_WhenFormIsDrafted() // Arrange var form = new FormAggregateFaker().Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); var request = new Upsert.Request(); @@ -227,7 +231,7 @@ public async Task ShouldThrow_WhenInvalidAnswersReceived() var formSubmission = new FormAggregateFaker(electionRound: electionRound).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(formSubmission); _repository.FirstOrDefaultAsync(Arg.Any()) @@ -277,7 +281,7 @@ public async Task ShouldCreateFormSubmission_WhenNotExists() var form = new FormAggregateFaker(electionRound, status: FormStatus.Published).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); _repository.FirstOrDefaultAsync(Arg.Any()) @@ -324,4 +328,4 @@ await _repository .Which .Result.Should().BeOfType>(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index 8d5c6dffd..fabe39607 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -66,7 +66,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs index f3ca85322..544f15ea5 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs @@ -1,5 +1,6 @@ using Feature.Form.Submissions.Requests; using Feature.Form.Submissions.Validators; +using Vote.Monitor.Core.Models; namespace Feature.Form.Submissions.UnitTests.ValidatorTests; @@ -29,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new FormSubmissionsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -39,6 +40,22 @@ public void Should_Have_Error_When_NgoId_Is_Empty() result.ShouldHaveValidationErrorFor(x => x.NgoId); } + [Fact] + public void Should_Have_Error_When_DataSource_Is_Empty() + { + // Arrange + var request = new FormSubmissionsAggregateFilter + { + DataSource = null! + }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DataSource); + } + [Fact] public void Should_Not_Have_Error_When_All_Fields_Are_Valid() { @@ -47,7 +64,8 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - FormId = Guid.NewGuid() + FormId = Guid.NewGuid(), + DataSource = DataSource.Coalition }; // Act @@ -56,4 +74,4 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() // Assert result.ShouldNotHaveAnyValidationErrors(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs index 7185bc2bd..5ec28c461 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs @@ -26,7 +26,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new GetById.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs index d05ef9730..ea41c181b 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs @@ -1,3 +1,5 @@ +using Vote.Monitor.Core.Models; + namespace Feature.Form.Submissions.UnitTests.ValidatorTests; public class ListByObserverValidatorTests @@ -26,7 +28,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new ListByObserver.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -36,6 +38,22 @@ public void Should_Have_Error_When_NgoId_Is_Empty() result.ShouldHaveValidationErrorFor(x => x.NgoId); } + [Fact] + public void Should_Have_Error_When_DataSource_Is_Empty() + { + // Arrange + var request = new ListByObserver.Request + { + DataSource = null! + }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DataSource); + } + [Fact] public void Should_Not_Have_Error_When_All_Fields_Are_Valid() { @@ -44,6 +62,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), + DataSource = DataSource.Coalition }; // Act @@ -52,4 +71,4 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() // Assert result.ShouldNotHaveAnyValidationErrors(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 351e21af6..346024ca3 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -1,4 +1,5 @@ using Feature.Form.Submissions.ListEntries; +using Vote.Monitor.Core.Models; namespace Feature.Form.Submissions.UnitTests.ValidatorTests; @@ -33,6 +34,22 @@ public void Validation_ShouldFail_When_ElectionRoundId_Empty() result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); } + [Fact] + public void Should_Have_Error_When_DataSource_Is_Empty() + { + // Arrange + var request = new Request() + { + DataSource = null! + }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DataSource); + } + [Fact] public void Validation_ShouldPass_When_ValidRequest() { @@ -40,7 +57,8 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid() + NgoId = Guid.NewGuid(), + DataSource = DataSource.Coalition }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs index c50978735..b406bb820 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -92,7 +92,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormId = Guid.NewGuid(), ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs b/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs index 3b9647457..a0f7c9375 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs @@ -5,7 +5,7 @@ global using NSubstitute; global using Xunit; -global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; +global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate; global using Vote.Monitor.Domain.Entities.FormTemplateAggregate; global using Vote.Monitor.Domain.Repository; global using Vote.Monitor.TestUtils; diff --git a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs index c80999970..3831c5233 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs @@ -219,7 +219,7 @@ public void Validation_ShouldPass_When_ValidRequest() Code = "A code", FormType = FormType.ClosingAndCounting, Name = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First(), - Description = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First(), + Description = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First() }; // Act diff --git a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs index 3e71d8f26..3aebc59d0 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs @@ -255,7 +255,7 @@ public void Validation_ShouldPass_When_ValidRequest() Code = "c!", Questions = [], Name = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate(), - Description = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate(), + Description = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate() }; // Act diff --git a/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs b/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs index a35e7e916..50864d6ee 100644 --- a/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs +++ b/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Constants; using Vote.Monitor.Domain.Entities.FormAggregate; diff --git a/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs index 012eb338b..0bcd6ec5a 100644 --- a/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs @@ -1,6 +1,8 @@ using System.Security.Claims; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using NSubstitute.ReturnsExtensions; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.FormAggregate; namespace Feature.Forms.UnitTests.Endpoints; @@ -9,11 +11,12 @@ public class GetEndpointTests { private readonly IAuthorizationService _authorizationService = Substitute.For(); private readonly IReadRepository _repository = Substitute.For>(); + private readonly IReadRepository _coalitionRepository = Substitute.For>(); private readonly Get.Endpoint _endpoint; public GetEndpointTests() { - _endpoint = Factory.Create(_authorizationService, _repository); + _endpoint = Factory.Create(_authorizationService, _repository, _coalitionRepository); _authorizationService .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()).Returns(AuthorizationResult.Success()); @@ -24,7 +27,8 @@ public async Task ShouldReturnNotFound_WhenUserIsNotAuthorized() { // Arrange _authorizationService - .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) + .AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Any>()) .Returns(AuthorizationResult.Failed()); // Act diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs index 6ce5ce628..942739719 100644 --- a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs @@ -143,7 +143,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormElectionRoundId = Guid.NewGuid(), FormId = Guid.NewGuid(), Languages = [LanguagesList.EN.Iso1, LanguagesList.RO.Iso1], - DefaultLanguage = LanguagesList.EN.Iso1, + DefaultLanguage = LanguagesList.EN.Iso1 }; // Act diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs index 0ebce59db..350ab41f1 100644 --- a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs @@ -129,7 +129,7 @@ public void Validation_ShouldPass_When_ValidRequest() NgoId = Guid.NewGuid(), TemplateId = Guid.NewGuid(), Languages = [LanguagesList.EN.Iso1, LanguagesList.RO.Iso1], - DefaultLanguage = LanguagesList.EN.Iso1, + DefaultLanguage = LanguagesList.EN.Iso1 }; // Act diff --git a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index cafac1f3b..c9ca7e1cf 100644 --- a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -53,7 +53,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = Guid.NewGuid(), IncidentReportId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 79f823b8b..e3668dfa3 100644 --- a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -31,7 +31,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = incidentReportAttachment.ElectionRoundId, IncidentReportId = incidentReportAttachment.IncidentReportId, - FormId = incidentReportAttachment.FormId, + FormId = incidentReportAttachment.FormId }; var result = await _endpoint.ExecuteAsync(request, default); @@ -59,7 +59,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = Guid.NewGuid(), IncidentReportId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs index 9cf798001..60f260782 100644 --- a/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -30,7 +30,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() var request = new Request { ElectionRoundId = Guid.NewGuid(), - IncidentReportId = Guid.NewGuid(), + IncidentReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index c0540d519..0b67b0369 100644 --- a/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -39,7 +39,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new Request { ElectionRoundId = Guid.NewGuid(), - IncidentReportId = Guid.NewGuid(), + IncidentReportId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs index cc2e780fe..b240c0144 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs index e34b517a2..5133a2e3f 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs @@ -31,7 +31,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new IncidentReportsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs index 3e36a68d9..05c923615 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs @@ -29,7 +29,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new ListByObserver.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -46,7 +46,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new ListByObserver.Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs index 8a76c0e98..9c06cfcd4 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -47,7 +47,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs index 2b4a8e04b..271f5a6da 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs @@ -31,7 +31,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new IncidentReportsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -48,7 +48,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new IncidentReportsAggregateFilter { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs index 9209d6018..9b1dbf734 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -46,7 +46,7 @@ public void Should_Have_Error_When_Id_Is_Empty() // Arrange var request = new Request { - Id = Guid.Empty, + Id = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 68fc3f225..67bf1aebf 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -158,7 +158,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs b/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs index 2136a66f0..19335d2e4 100644 --- a/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs +++ b/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldFail_When_Id_Empty() // Arrange var request = new Get.Request { - Id = Guid.Empty, + Id = Guid.Empty }; // Act diff --git a/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 3d7d26356..6c5ea8706 100644 --- a/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -15,7 +15,7 @@ public void Validation_ShouldPass_When_PageSize_ValidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -36,7 +36,7 @@ public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -57,7 +57,7 @@ public void Validation_ShouldPass_When_PageNumber_ValidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act @@ -77,7 +77,7 @@ public void Validation_ShouldFail_When_PageNumber_InvalidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs index f5b2a8e6e..4ca1aab0f 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs index 895c18c68..3ad052ae2 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs @@ -49,9 +49,9 @@ public void Validation_ShouldPass_When_ValidRequest() Tags = ["tag1", "tag2"], FirstName = "firstName", LastName = "lastName", - PhoneNumber = "phoneNumber", + PhoneNumber = "phoneNumber" } - ], + ] }; // Act @@ -60,4 +60,4 @@ public void Validation_ShouldPass_When_ValidRequest() // Assert result.ShouldNotHaveAnyValidationErrors(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs index 9b339a294..e4e0aec78 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs index 19b0fa3d4..786f66011 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs @@ -51,7 +51,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -71,7 +71,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs index 35ac6175d..ab70433d1 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs index 7cad66dcf..64fbf41cb 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs @@ -42,7 +42,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs index c7f03498f..0535410a9 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs index 9851f86c2..d7ac00a21 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs @@ -54,7 +54,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs index 77daf8f8b..c95b51b96 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs index 434c2f480..87406217e 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -86,7 +86,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; @@ -106,7 +106,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ], Tags = ["a tag"] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs index 84abbd2af..e6322cb12 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -106,7 +106,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ], Tags = ["a Untag"] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs index 13ee314dc..612a6b9ab 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs @@ -70,7 +70,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs index a13588a2f..085de06f8 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -46,7 +46,7 @@ public async Task ShouldReturnNoContent_WhenAllIdsExist() { ElectionRoundId = fakeNote.ElectionRoundId, ObserverId = fakeMonitoringObserver.ObserverId, - Id = fakeNote.Id, + Id = fakeNote.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -106,7 +106,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, ObserverId = fakeMonitoringObserver.Id, - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs index f3f78fa70..50631b10c 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -50,7 +50,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() ElectionRoundId = fakeElectionRound.Id, PollingStationId = Guid.NewGuid(), FormId = Guid.NewGuid(), - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -78,7 +78,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = Guid.NewGuid(), - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs index 170c3d0df..630bc07e3 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -79,7 +79,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, ObserverId = fakeMonitoringObserver.Id, - Text = noteText, + Text = noteText }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index e56facdf9..6306b8cd1 100644 --- a/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -67,7 +67,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs index 9c64abd95..263562ec3 100644 --- a/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs +++ b/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs @@ -28,7 +28,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new Delete.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = _validator.TestValidate(model); diff --git a/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs b/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs index bd0f1be6d..b04b357bd 100644 --- a/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs +++ b/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs @@ -40,7 +40,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new GetById.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs index 4537a6eec..e1b33b128 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -35,7 +35,7 @@ public async Task Should_DeletePollingStationInformationForm_And_ReturnNoContent // Act var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -59,7 +59,7 @@ public async Task ShouldReturnNotFound_WhenPollingStationInformationFormNotFound // Act var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs index ddd79aece..1409f64b6 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs @@ -34,7 +34,7 @@ public async Task Should_GetPollingStationInformationForm_And_ReturnNoContent_Wh // Act var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -57,7 +57,7 @@ public async Task ShouldReturnNotFound_WhenPollingStationInformationFormNotFound // Act var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs index 08e929317..3a3419905 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -79,7 +79,7 @@ public async Task ShouldReturnNotFound_WhenElectionRoundNotExists() var request = new Upsert.Request { - ElectionRoundId = electionRoundId, + ElectionRoundId = electionRoundId }; _electionRoundRepository diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index d5925c4ae..1a54d1996 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldPass_When_ValidRequest() // Arrange var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs index f94d159d3..4a54d391a 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldPass_When_ValidRequest() // Arrange var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 29acc2af7..867e79862 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -130,7 +130,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), DefaultLanguage = LanguagesList.EN.Iso1, - Languages = [LanguagesList.RO.Iso1, LanguagesList.EN.Iso1], + Languages = [LanguagesList.RO.Iso1, LanguagesList.EN.Iso1] }; // Act diff --git a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index 6fab90ca6..921e25e77 100644 --- a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -54,7 +54,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 91075584b..47d88edd5 100644 --- a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -80,12 +80,12 @@ public void Validation_ShouldFail_When_ObservationBreaks_ContainsInvalid() new Request.BreakRequest { Start = DateTime.UtcNow.AddDays(-1), - End = DateTime.UtcNow, + End = DateTime.UtcNow }, new Request.BreakRequest { Start = DateTime.UtcNow, - End = DateTime.UtcNow.AddDays(-1), + End = DateTime.UtcNow.AddDays(-1) } ]}; @@ -108,7 +108,7 @@ public void Validation_ShouldPass_When_BreakEndIsNull() new Request.BreakRequest { Start = DateTime.UtcNow.AddDays(-1), - End = DateTime.UtcNow, + End = DateTime.UtcNow }, new Request.BreakRequest { diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs index 53d57a7ee..855ea9197 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs @@ -152,7 +152,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() ElectionRoundId = fakeElectionRound.Id, QuickReportId = _quickReportId, Id = _attachmentId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -183,7 +183,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() ElectionRoundId = fakeElectionRound.Id, QuickReportId = _quickReportId, Id = _attachmentId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs index 7ca4b34fd..188dd0a3f 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs @@ -28,7 +28,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new DeleteAttachment.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs index 460b04137..27daa7adc 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -32,7 +32,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = electionRoundId, ObserverId = monitoringObserverId, - Id = attachmentId, + Id = attachmentId }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/GetEndpointTests.cs index 748386090..e33f2ae12 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/GetEndpointTests.cs @@ -1,117 +1,30 @@ using System.Security.Claims; using Feature.QuickReports.Get; -using Feature.QuickReports.Specifications; using Microsoft.AspNetCore.Authorization; -using NSubstitute.ReturnsExtensions; using Vote.Monitor.Core.Services.FileStorage.Contracts; -using Vote.Monitor.Domain.Entities.QuickReportAggregate; -using Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate; +using Vote.Monitor.Core.Services.Security; +using Vote.Monitor.Domain.ConnectionFactory; namespace Feature.QuickReports.UnitTests.Endpoints; public class GetEndpointTests { private readonly IAuthorizationService _authorizationService; - private readonly IReadRepository _repository; - private readonly IReadRepository _quickReportAttachmentRepository; - private readonly IFileStorageService _fileStorageService; private readonly Get.Endpoint _endpoint; public GetEndpointTests() { _authorizationService = Substitute.For(); - _repository = Substitute.For>(); - _quickReportAttachmentRepository = Substitute.For>(); - _fileStorageService = Substitute.For(); + INpgsqlConnectionFactory dbConnectionFactory = Substitute.For(); + IFileStorageService fileStorageService = Substitute.For(); + ICurrentUserRoleProvider userRoleProvider = Substitute.For(); + ICurrentUserProvider userProvider = Substitute.For(); + _endpoint = Factory.Create(_authorizationService, - _repository, - _quickReportAttachmentRepository, - _fileStorageService); - } - - [Fact] - public async Task ShouldReturnOkWithQuickReportDetailedModel_WhenAllIdsExist() - { - // Arrange - var quickReportId = Guid.NewGuid(); - - var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); - var quickReport = new QuickReportFaker(quickReportId).Generate(); - - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) - .Returns(AuthorizationResult.Success()); - - _repository.FirstOrDefaultAsync(Arg.Any()) - .Returns(quickReport); - - List quickReportAttachments = [new QuickReportAttachmentFaker(), new QuickReportAttachmentFaker()]; - - _quickReportAttachmentRepository - .ListAsync(Arg.Any()) - .Returns(quickReportAttachments); - - // Act - var request = new Get.Request - { - ElectionRoundId = fakeElectionRound.Id, - Id = quickReportId - }; - var result = await _endpoint.ExecuteAsync(request, default); - - // Assert - var model = result.Result.As>().Value!; - - model.Id.Should().Be(quickReport.Id); - model.ElectionRoundId.Should().Be(quickReport.ElectionRoundId); - model.QuickReportLocationType.Should().Be(quickReport.QuickReportLocationType); - model.Title.Should().Be(quickReport.Title); - model.Description.Should().Be(quickReport.Description); - model.PollingStationId.Should().Be(quickReport.PollingStationId); - model.PollingStationDetails.Should().Be(quickReport.PollingStationDetails); - - model.Attachments.Should().BeEquivalentTo(quickReportAttachments, cmp => cmp.ExcludingMissingMembers()); - } - - [Fact] - public async Task ShouldGetPresignedUrlsForAttachments_WhenAllIdsExist() - { - // Arrange - var quickReportId = Guid.NewGuid(); - - var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); - var fakeQuickReport = new QuickReportFaker(quickReportId).Generate(); - - _authorizationService - .AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) - .Returns(AuthorizationResult.Success()); - - _repository - .FirstOrDefaultAsync(Arg.Any()) - .Returns(fakeQuickReport); - - var quickReportAttachment1 = new QuickReportAttachmentFaker().Generate(); - var quickReportAttachment2 = new QuickReportAttachmentFaker().Generate(); - List quickReportAttachments = [quickReportAttachment1, quickReportAttachment2]; - - _quickReportAttachmentRepository - .ListAsync(Arg.Any()) - .Returns(quickReportAttachments); - // Act - var request = new Get.Request - { - ElectionRoundId = fakeElectionRound.Id, - Id = quickReportId - }; - _ = await _endpoint.ExecuteAsync(request, default); - - // Assert - await _fileStorageService - .Received(1) - .GetPresignedUrlAsync(quickReportAttachment1.FilePath, quickReportAttachment1.UploadedFileName); - - await _fileStorageService - .Received(1) - .GetPresignedUrlAsync(quickReportAttachment2.FilePath, quickReportAttachment2.UploadedFileName); + dbConnectionFactory, + fileStorageService, + userRoleProvider, + userProvider); } [Fact] @@ -138,33 +51,4 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() .Which .Result.Should().BeOfType(); } - - [Fact] - public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() - { - // Arrange - var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); - var quickReportId = Guid.NewGuid(); - - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) - .Returns(AuthorizationResult.Success()); - - _repository - .GetByIdAsync(Arg.Any()) - .ReturnsNull(); - - // Act - var request = new Get.Request - { - ElectionRoundId = fakeElectionRound.Id, - Id = quickReportId - }; - var result = await _endpoint.ExecuteAsync(request, default); - - // Assert - result - .Should().BeOfType, NotFound>>() - .Which - .Result.Should().BeOfType(); - } } diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs index 5937a9d98..5ac4bc1d7 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs @@ -58,7 +58,7 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() ]; List quickReport2Attachments = [ - new QuickReportAttachmentFaker(quickReportId: quickReport2.Id).Generate(), + new QuickReportAttachmentFaker(quickReportId: quickReport2.Id).Generate() ]; List attachments = [.. quickReport1Attachments, .. quickReport2Attachments]; @@ -70,7 +70,7 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -86,7 +86,10 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() model[0].PollingStationId.Should().Be(quickReport1.PollingStationId); model[0].PollingStationDetails.Should().Be(quickReport1.PollingStationDetails); - model[0].Attachments.Should().BeEquivalentTo(quickReport1Attachments, cmp => cmp.ExcludingMissingMembers()); + model[0].Attachments.Should().BeEquivalentTo(quickReport1Attachments, cmp => cmp + .ExcludingMissingMembers() + .Excluding(x=>x.UploadedFileName) + .Excluding(x=>x.FilePath)); model[1].Id.Should().Be(quickReport2.Id); model[1].ElectionRoundId.Should().Be(quickReport2.ElectionRoundId); @@ -96,7 +99,11 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() model[1].PollingStationId.Should().Be(quickReport2.PollingStationId); model[1].PollingStationDetails.Should().Be(quickReport2.PollingStationDetails); - model[1].Attachments.Should().BeEquivalentTo(quickReport2Attachments, cmp => cmp.ExcludingMissingMembers()); + model[1].Attachments.Should().BeEquivalentTo(quickReport2Attachments, cmp => cmp + .ExcludingMissingMembers() + .Excluding(x=>x.UploadedFileName) + .Excluding(x=>x.FilePath) + ); } [Fact] @@ -113,7 +120,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -155,7 +162,7 @@ public async Task ShouldGetPresignedUrlsForAttachments_WhenAllIdsExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; _ = await _endpoint.ExecuteAsync(request, default); @@ -195,7 +202,7 @@ public async Task ShouldReturnEmptyList_WhenQuickReportsDoNotExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs index 467da0651..9115dd88d 100644 --- a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs @@ -37,7 +37,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new ListMy.Request { ElectionRoundId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 54534d7f9..8d07f898d 100644 --- a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -1,3 +1,5 @@ +using Vote.Monitor.Core.Models; + namespace Feature.QuickReports.UnitTests.ValidatorTests; public class ListRequestValidatorTests @@ -30,6 +32,19 @@ public void Validation_ShouldFail_When_ElectionRoundId_Empty() result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); } + [Fact] + public void Validation_ShouldFail_When_DataSource_Empty() + { + // Arrange + var request = new List.Request { DataSource = null! }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DataSource); + } + [Theory] [InlineData(0)] [InlineData(101)] @@ -37,11 +52,7 @@ public void Validation_ShouldFail_When_ElectionRoundId_Empty() public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) { // Arrange - var request = new List.Request - { - PageSize = pageSize, - PageNumber = 1 - }; + var request = new List.Request { PageSize = pageSize, PageNumber = 1 }; // Act var result = _validator.TestValidate(request); @@ -56,11 +67,7 @@ public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) public void Validation_ShouldFail_When_PageNumber_InvalidValues(int pageNumber) { // Arrange - var request = new List.Request - { - PageSize = 10, - PageNumber = pageNumber - }; + var request = new List.Request { PageSize = 10, PageNumber = pageNumber }; // Act var result = _validator.TestValidate(request); @@ -75,8 +82,7 @@ public void Validation_ShouldPass_When_ValidRequest() // Arrange var request = new List.Request { - ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), DataSource = DataSource.Coalition }; // Act diff --git a/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs b/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs index f093e626f..641aca41c 100644 --- a/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs +++ b/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs @@ -1,4 +1,6 @@ -namespace Feature.Statistics.UnitTests.Validators; +using Vote.Monitor.Core.Models; + +namespace Feature.Statistics.UnitTests.Validators; public class GetNgoAdminStatisticsValidatorTests { @@ -30,14 +32,26 @@ public void Validation_ShouldFail_When_NgoId_Empty() result.ShouldHaveValidationErrorFor(x => x.NgoId); } + [Fact] + public void Validation_ShouldFail_When_DataSource_Empty() + { + // Arrange + var request = new GetNgoAdminStatistics.Request { DataSource = null! }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DataSource); + } + [Fact] public void Validation_ShouldPass_When_ValidRequest() { // Arrange var request = new GetNgoAdminStatistics.Request { - ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), DataSource = DataSource.Ngo }; // Act diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs index 4225df53f..897c601e3 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs @@ -29,6 +29,6 @@ public void Map_Should_Return_CorrectAggregator(BaseQuestion question, Type expe new object[] { new RatingQuestionFaker().Generate(), typeof(RatingAnswerAggregate) }, new object[] { new NumberQuestionFaker().Generate(), typeof(NumberAnswerAggregate) }, new object[] { new SingleSelectQuestionFaker().Generate(), typeof(SingleSelectAnswerAggregate) }, - new object[] { new MultiSelectQuestionFaker().Generate(), typeof(MultiSelectAnswerAggregate) }, + new object[] { new MultiSelectQuestionFaker().Generate(), typeof(MultiSelectAnswerAggregate) } }; } diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/BaseAnswerAggregateTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/BaseAnswerAggregateTests.cs index 2fa779fa3..e51b03e58 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/BaseAnswerAggregateTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/BaseAnswerAggregateTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Vote.Monitor.Answer.Module.Aggregators; +using Vote.Monitor.Answer.Module.Models; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; using Vote.Monitor.TestUtils.Fakes.Aggregates.Questions; @@ -50,4 +51,9 @@ protected override void QuestionSpecificAggregate(Guid submissionId, Guid monito { QuestionSpecificAggregateInvoked = true; } + + protected override void QuestionSpecificAggregate(Guid submissionId, Guid monitoringObserverId, BaseAnswerModel answer) + { + throw new NotImplementedException(); + } } diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs index 5f216b630..5ed6668c2 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs @@ -15,7 +15,7 @@ namespace Vote.Monitor.Answer.Module.UnitTests.Aggregators; public class FormSubmissionsAggregateTests { - private readonly Form _form; + private readonly Domain.Entities.FormAggregate.Form _form; private readonly TextQuestion _textQuestion = new TextQuestionFaker().Generate(); private readonly NumberQuestion _numberQuestion = new NumberQuestionFaker().Generate(); private readonly DateQuestion _dateQuestion = new DateQuestionFaker().Generate(); @@ -43,7 +43,7 @@ public FormSubmissionsAggregateTests() _multiSelectQuestion ]; - _form = Form.Create(_electionRound, monitoringNgo, FormType.Opening, "F1", new TranslatedStringFaker(), + _form = Domain.Entities.FormAggregate.Form.Create(_electionRound, monitoringNgo, FormType.Opening, "F1", new TranslatedStringFaker(), new TranslatedStringFaker(), "EN", [], null, questions); } @@ -93,8 +93,6 @@ public void Constructor_ShouldCreateEmptyAggregates() .Which.QuestionId .Should().Be(_multiSelectQuestion.Id); - aggregate.ElectionRoundId.Should().Be(_form.ElectionRoundId); - aggregate.MonitoringNgoId.Should().Be(_form.MonitoringNgoId); aggregate.FormId.Should().Be(_form.Id); } @@ -146,9 +144,9 @@ public void AggregateAnswers_ShouldAddResponderIdToResponders() aggregate.Responders.Should().HaveCount(2); var observer1 = monitoringObserver1.Observer.ApplicationUser; var observer2 = monitoringObserver2.Observer.ApplicationUser; - var responder1 = new Responder(monitoringObserver1.Id, observer1.FirstName, observer1.LastName, observer1.Email, + var responder1 = new Responder(monitoringObserver1.Id, observer1.DisplayName, observer1.Email, observer1.PhoneNumber); - var responder2 = new Responder(monitoringObserver2.Id, observer2.FirstName, observer2.LastName, observer2.Email, + var responder2 = new Responder(monitoringObserver2.Id, observer2.DisplayName, observer2.Email, observer2.PhoneNumber); aggregate.Responders.Should().Contain([responder1, responder2]); } @@ -235,7 +233,7 @@ public void AggregateAnswers_ShouldAggregateAnswersCorrectly() [ new DateAnswerFaker(_dateQuestion.Id), new TextAnswerFaker(_textQuestion.Id), - new NumberAnswerFaker(_numberQuestion.Id), + new NumberAnswerFaker(_numberQuestion.Id) ]; var formSubmission1 = FormSubmission.Create(_electionRound, pollingStation, monitoringObserver, _form, @@ -269,12 +267,12 @@ public void AggregateAnswers_ShouldIgnoreInvalidAnswersInSubmissions() List submission1Answers = [ - new SingleSelectAnswerFaker(_singleSelectQuestion.Id, _singleSelectQuestion.Options[1].Select()), + new SingleSelectAnswerFaker(_singleSelectQuestion.Id, _singleSelectQuestion.Options[1].Select()) ]; List submission2Answers = [ - new SingleSelectAnswerFaker(Guid.NewGuid(), _singleSelectQuestion.Options[2].Select()), + new SingleSelectAnswerFaker(Guid.NewGuid(), _singleSelectQuestion.Options[2].Select()) ]; List submission3Answers = []; @@ -294,4 +292,4 @@ public void AggregateAnswers_ShouldIgnoreInvalidAnswersInSubmissions() // Assert aggregate.Aggregates[_singleSelectQuestion.Id].AnswersAggregated.Should().Be(1); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs index c8deb4c01..144435e6f 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs @@ -93,8 +93,6 @@ public void Constructor_ShouldCreateEmptyAggregates() .Which.QuestionId .Should().Be(_multiSelectQuestion.Id); - aggregate.ElectionRoundId.Should().Be(_form.ElectionRoundId); - aggregate.MonitoringNgoId.Should().Be(Guid.Empty); aggregate.FormId.Should().Be(_form.Id); } @@ -156,9 +154,9 @@ public void AggregateAnswers_ShouldAddResponderIdToResponders() aggregate.Responders.Should().HaveCount(2); var observer1 = monitoringObserver1.Observer.ApplicationUser; var observer2 = monitoringObserver2.Observer.ApplicationUser; - var responder1 = new Responder(monitoringObserver1.Id, observer1.FirstName, observer1.LastName, observer1.Email, + var responder1 = new Responder(monitoringObserver1.Id, observer1.DisplayName, observer1.Email, observer1.PhoneNumber); - var responder2 = new Responder(monitoringObserver2.Id, observer2.FirstName, observer2.LastName, observer2.Email, + var responder2 = new Responder(monitoringObserver2.Id, observer2.DisplayName, observer2.Email, observer2.PhoneNumber); aggregate.Responders.Should().Contain([responder1, responder2]); } @@ -257,7 +255,7 @@ public void AggregateAnswers_ShouldAggregateAnswersCorrectly() [ new DateAnswerFaker(_dateQuestion.Id), new TextAnswerFaker(_textQuestion.Id), - new NumberAnswerFaker(_numberQuestion.Id), + new NumberAnswerFaker(_numberQuestion.Id) ]; var formSubmission1 = PollingStationInformation.Create(_userId, _electionRound, pollingStation, @@ -286,4 +284,4 @@ public void AggregateAnswers_ShouldAggregateAnswersCorrectly() aggregate.Aggregates[_singleSelectQuestion.Id].AnswersAggregated.Should().Be(2); aggregate.Aggregates[_multiSelectQuestion.Id].AnswersAggregated.Should().Be(2); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs index 716b27854..0de2af4a3 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs @@ -42,7 +42,7 @@ public void Validation_ShouldFail_When_InvalidSelection() { Text = "", OptionId = Guid.Empty - }, + } ] }; diff --git a/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Endpoints/GetEndpointTests.cs deleted file mode 100644 index a4842aa27..000000000 --- a/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Endpoints/GetEndpointTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.UnitTests.Endpoints; - -public class GetEndpointTests -{ - [Fact] - public async Task Should_ReturnElectionRound_WhenExists() - { - // Arrange - var electionRoundModel = new ElectionRoundModel - { - Id = Guid.NewGuid(), - Title = "A title", - StartDate = new DateOnly(2024, 01, 02), - EnglishTitle = "An english title", - Status = ElectionRoundStatus.NotStarted, - CountryId = CountriesList.MD.Id, - CountryIso2 = CountriesList.MD.Iso2, - CountryIso3 = CountriesList.MD.Iso3, - CountryName = CountriesList.MD.Name, - CountryFullName = CountriesList.MD.FullName, - CountryNumericCode = CountriesList.MD.NumericCode, - CreatedOn = DateTime.UtcNow.AddHours(-30), - LastModifiedOn = DateTime.UtcNow.AddHours(-15) - }; - - var repository = Substitute.For>(); - repository - .SingleOrDefaultAsync(Arg.Any()) - .Returns(electionRoundModel); - - var endpoint = Factory.Create(repository); - - // Act - var request = new Get.Request { Id = electionRoundModel.Id }; - var result = await endpoint.ExecuteAsync(request, default); - - // Assert - result - .Should().BeOfType, NotFound>>() - .Which - .Result.Should().BeOfType>() - .Which.Value.Should().BeEquivalentTo(electionRoundModel); - } - - [Fact] - public async Task ShouldReturnNotFound_WhenElectionRoundNotFound() - { - // Arrange - var repository = Substitute.For>(); - var endpoint = Factory.Create(repository); - - // Act - var request = new Get.Request { Id = Guid.NewGuid() }; - var result = await endpoint.ExecuteAsync(request, default); - - // Assert - result - .Should().BeOfType, NotFound>>() - .Which - .Result.Should().BeOfType(); - } -} diff --git a/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Specifications/GetElectionRoundByIdSpecificationTests.cs b/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Specifications/GetElectionRoundByIdSpecificationTests.cs deleted file mode 100644 index ee5c5df11..000000000 --- a/api/tests/Vote.Monitor.Api.Feature.ElectionRound.UnitTests/Specifications/GetElectionRoundByIdSpecificationTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Vote.Monitor.Api.Feature.ElectionRound.UnitTests.Specifications; - -public class GetElectionRoundByIdSpecificationTests -{ - [Fact] - public void ShouldMatch_NonArchivedElectionRounds() - { - // Arrange - var electionRoundId = Guid.NewGuid(); - var electionRound = new ElectionRoundAggregateFaker(id: electionRoundId).Generate(); - - List testCollection = - [ - electionRound, - .. new ElectionRoundAggregateFaker().Generate(100) - ]; - - // Act - var spec = new GetElectionRoundByIdSpecification(electionRoundId); - var result = spec.Evaluate(testCollection).ToList(); - - // Assert - result.Should().HaveCount(1); - result.Should().Contain(x => x.Id == electionRoundId); - } -} diff --git a/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs b/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs index bf55de186..c8964c217 100644 --- a/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs @@ -107,7 +107,7 @@ public void Parsing_ShouldSucceed_When_ValidCSVs(string fileContent, List{ - new() {FirstName = "Obs1",LastName="test",Password="pa$$word", Email = "obs1@mail.com", PhoneNumber = "2000000000", }, + new() {FirstName = "Obs1",LastName="test",Password="pa$$word", Email = "obs1@mail.com", PhoneNumber = "2000000000" }, new() {FirstName = "Obs2",LastName="test",Password="pa$$word", Email = "obs2@mail.com", PhoneNumber = "3000000000" } } }, diff --git a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs index 735a40ab4..9c9be647d 100644 --- a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs @@ -17,8 +17,14 @@ public void GetPollingStationSpecification_AppliesCorrectFilters() .Union(new PollingStationAggregateFaker(electionRound: electionRound).Generate(500)) .ToList(); - var spec = new GetPollingStationSpecification(electionRound.Id, pollingStation.Address, new Dictionary()); - + var spec = new GetPollingStationSpecification(electionRound.Id, + pollingStation.Level1, + pollingStation.Level2, + pollingStation.Level3, + pollingStation.Level4, + pollingStation.Level5, + pollingStation.Number, + pollingStation.Address); // Act var result = spec.Evaluate(testCollection).ToList(); @@ -40,7 +46,14 @@ public void GetPollingStationSpecification_AppliesCorrectFilters_WhenPartialAddr .Union(new PollingStationAggregateFaker(electionRound).Generate(500)) .ToList(); - var spec = new GetPollingStationSpecification(electionRound.Id, pollingStation.Address[..(pollingStation.Address.Length / 2)], new Dictionary()); + var spec = new GetPollingStationSpecification(electionRound.Id, + pollingStation.Level1, + pollingStation.Level2, + pollingStation.Level3, + pollingStation.Level4, + pollingStation.Level5, + pollingStation.Number, + pollingStation.Address); // Act var result = spec.Evaluate(testCollection).ToList(); diff --git a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 9a3db366d..b3d458875 100644 --- a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -15,7 +15,7 @@ public void Validation_ShouldPass_When_PageSize_ValidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -36,7 +36,7 @@ public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -57,7 +57,7 @@ public void Validation_ShouldPass_When_PageNumber_ValidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act @@ -77,7 +77,7 @@ public void Validation_ShouldFail_When_PageNumber_InvalidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs new file mode 100644 index 000000000..188ff5bfc --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs @@ -0,0 +1,83 @@ +using Job.Contracts; +using NSubstitute; +using NSubstitute.ClearExtensions; +using Vote.Monitor.Api.IntegrationTests.Db; +using Vote.Monitor.Core.Services.EmailTemplating; +using Vote.Monitor.Core.Services.EmailTemplating.Props; +using Vote.Monitor.Core.Services.Time; + +namespace Vote.Monitor.Api.IntegrationTests; + +[SetUpFixture] +public class ApiTesting +{ + private static ITestDatabase _database = null!; + private static CustomWebApplicationFactory _factory = null!; + private static ITimeProvider _apiTimeProvider = null!; + public static ITimeProvider ApiTimeProvider => _apiTimeProvider; + + private static IEmailTemplateFactory _emailFactory = null!; + public static IEmailTemplateFactory EmailFactory => _emailFactory; + private static IJobService _jobService = null!; + public static IJobService JobService => _jobService; + + [OneTimeSetUp] + public async Task RunBeforeAnyTests() + { + _database = await TestDatabaseFactory.CreateAsync(); + + _apiTimeProvider = Substitute.For(); + _apiTimeProvider.UtcNow.Returns(_ => DateTime.UtcNow); + _apiTimeProvider.UtcNowDate.Returns(_ => DateOnly.FromDateTime(DateTime.UtcNow)); + + _emailFactory = Substitute.For(); + _emailFactory.GenerateConfirmAccountEmail(Arg.Any()) + .Returns(new EmailModel("fake", "fake")); + + _emailFactory.GenerateResetPasswordEmail(Arg.Any()) + .Returns(new EmailModel("fake", "fake")); + + _emailFactory.GenerateInvitationExistingUserEmail(Arg.Any()) + .Returns(new EmailModel("fake", "fake")); + + _emailFactory.GenerateNewUserInvitationEmail(Arg.Any()) + .Returns(new EmailModel("fake", "fake")); + + _emailFactory.GenerateCitizenReportEmail(Arg.Any()) + .Returns(new EmailModel("fake", "fake")); + + _jobService = Substitute.For(); + + await _database.InitialiseAsync(); + _factory = new CustomWebApplicationFactory(_database.GetConnectionString(), _database.GetConnection(), + _apiTimeProvider, _emailFactory, _jobService); + } + + public static string DbConnectionString => _database.GetConnectionString(); + + public static async Task ResetState() + { + try + { + await _database.ResetAsync(); + _emailFactory.ClearReceivedCalls(); + _jobService.ClearReceivedCalls(); + } + catch (Exception e) + { + TestContext.Out.WriteLine(e.Message); + } + } + + public static HttpClient CreateClient() + { + return _factory.CreateClient(); + } + + [OneTimeTearDown] + public async Task RunAfterAnyTests() + { + await _database.DisposeAsync(); + await _factory.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs new file mode 100644 index 000000000..a459603b5 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests; + +using static ApiTesting; + +[TestFixture] +public abstract class BaseApiTestFixture +{ + [SetUp] + public async Task BaseTestSetUp() + { + await ResetState(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs new file mode 100644 index 000000000..8e542be31 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests; + +using static ApiTesting; + +[TestFixture] +public abstract class BaseDbTestFixture +{ + [SetUp] + public async Task BaseTestSetUp() + { + await ResetState(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs new file mode 100644 index 000000000..c19a7d858 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs @@ -0,0 +1,7 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioCoalition +{ + Youth, + Old, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs new file mode 100644 index 000000000..6f38a8741 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioElectionRound +{ + A, + B, + C, + D, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs new file mode 100644 index 000000000..278439f37 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs @@ -0,0 +1,39 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioNgo +{ + Alfa, + Beta, + Delta +} + +public class ScenarioNgos +{ + public static AlfaDetails Alfa => new(); + public static BetaDetails Beta => new BetaDetails(); + public static DeltaDetails Delta => new DeltaDetails(); + + public class AlfaDetails + { + public static implicit operator ScenarioNgo(AlfaDetails _) => ScenarioNgo.Alfa; + public readonly string Anya = "anya@alfa.com"; + public readonly string Ben = "ben@alfa.com"; + public readonly string Cody = "cody@alfa.com"; + } + + public class BetaDetails + { + public static implicit operator ScenarioNgo(BetaDetails _) => ScenarioNgo.Beta; + public readonly string Dana = "dana@beta.com"; + public readonly string Ivy = "ivy@beta.com"; + public readonly string Finn = "finn@beta.com"; + } + + public class DeltaDetails + { + public static implicit operator ScenarioNgo(DeltaDetails _) => ScenarioNgo.Delta; + public readonly string Gia = "gia@delta.com"; + public readonly string Mason = "mason@delta.com"; + public readonly string Omar = "omar@delta.com"; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs new file mode 100644 index 000000000..e39301205 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioObserver +{ + Alice, + Bob, + Charlie, + Dave, + Eve, + Frank, + Grace, + Mallory, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs new file mode 100644 index 000000000..c352b69bf --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioPollingStation +{ + Iasi, + Bacau, + Cluj, + Dolj, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs new file mode 100644 index 000000000..9d1cc8a2e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs @@ -0,0 +1,101 @@ +using System.Data.Common; +using Job.Contracts; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Npgsql; +using Serilog; +using Vote.Monitor.Core.Services.EmailTemplating; +using Vote.Monitor.Core.Services.Security; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests; + +public class CustomWebApplicationFactory : WebApplicationFactory +{ + private readonly DbConnection _connection; + private readonly ITimeProvider _timeProvider; + private readonly NpgsqlConnectionStringBuilder _connectionDetails; + private readonly IEmailTemplateFactory _emailFactory; + private readonly IJobService _jobService; + + public const string AdminEmail = "integration@testing.com"; + public const string AdminPassword = "toTallyNotTestPassw0rd"; + + public CustomWebApplicationFactory(string connectionString, DbConnection connection, ITimeProvider timeProvider, + IEmailTemplateFactory emailFactory, IJobService jobService) + { + _connection = connection; + _connectionDetails = new NpgsqlConnectionStringBuilder { ConnectionString = connectionString }; + _timeProvider = timeProvider; + _emailFactory = emailFactory; + _jobService = jobService; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseSetting("AuthFeatureConfig:JWTConfig:TokenSigningKey", + "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes"); + builder.UseSetting("AuthFeatureConfig:JWTConfig:TokenExpirationInMinutes", "10080"); + builder.UseSetting("AuthFeatureConfig:JWTConfig:RefreshTokenExpirationInDays", "30"); + builder.UseSetting("Domain:DbConnectionConfig:Server", _connectionDetails.Host); + builder.UseSetting("Domain:DbConnectionConfig:Port", _connectionDetails.Port.ToString()); + builder.UseSetting("Domain:DbConnectionConfig:Database", _connectionDetails.Database); + builder.UseSetting("Domain:DbConnectionConfig:UserId", _connectionDetails.Username); + builder.UseSetting("Domain:DbConnectionConfig:Password", _connectionDetails.Password); + builder.UseSetting("Core:EnableHangfire", "false"); + builder.UseSetting("Core:HangfireConnectionConfig:Server", _connectionDetails.Host); + builder.UseSetting("Core:HangfireConnectionConfig:Port", _connectionDetails.Port.ToString()); + builder.UseSetting("Core:HangfireConnectionConfig:Database", _connectionDetails.Database); + builder.UseSetting("Core:HangfireConnectionConfig:UserId", _connectionDetails.Username); + builder.UseSetting("Core:HangfireConnectionConfig:Password", _connectionDetails.Password); + builder.UseSetting("Sentry:Enabled", "false"); + builder.UseSetting("Sentry:Dsn", ""); + builder.UseSetting("Sentry:TracesSampleRate", "0.2"); + builder.UseSetting("Seeders:PlatformAdminSeeder:FirstName", "John"); + builder.UseSetting("Seeders:PlatformAdminSeeder:LastName", "Doe"); + builder.UseSetting("Seeders:PlatformAdminSeeder:Email", AdminEmail); + builder.UseSetting("Seeders:PlatformAdminSeeder:PhoneNumber", "1234567890"); + builder.UseSetting("Seeders:PlatformAdminSeeder:Password", AdminPassword); + + builder.ConfigureTestServices((services) => + { + services.RemoveAll(); + services.RemoveAll(); + services.RemoveAll(); + + services.AddSingleton(_ => _timeProvider); + services.AddTransient(_ => _emailFactory); + services.AddTransient(_ => _jobService); + + services + .RemoveAll>() + .AddDbContext((sp, options) => + { + options.UseNpgsql(_connection) + .AddInterceptors(new AuditingInterceptor(sp.GetRequiredService(), + _timeProvider)); + }); + + services.AddLogging(logging => + { + Serilog.Debugging.SelfLog.Enable(Console.WriteLine); + + var loggerConfiguration = new LoggerConfiguration() + .Enrich.FromLogContext() + .Enrich.WithMachineName() + .Enrich.WithEnvironmentUserName() + .Destructure.ToMaximumDepth(3) + .WriteTo.NUnitOutput(); + + var logger = Log.Logger = loggerConfiguration.CreateLogger(); + + logging.AddSerilog(logger); + }); + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs new file mode 100644 index 000000000..1f6f86b63 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs @@ -0,0 +1,14 @@ +using System.Data.Common; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public interface ITestDatabase +{ + Task InitialiseAsync(); + + DbConnection GetConnection(); + string GetConnectionString(); + Task ResetAsync(); + + Task DisposeAsync(); +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs new file mode 100644 index 000000000..544b71c7a --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs @@ -0,0 +1,75 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Npgsql; +using Respawn; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public class PostgresTestDatabase : ITestDatabase +{ + private readonly string _connectionString = null!; + private NpgsqlConnection _connection = null!; + private Respawner _respawner = null!; + + public PostgresTestDatabase() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + var connectionString = configuration.GetConnectionString("DefaultConnection"); + + _connectionString = connectionString; + } + + public async Task InitialiseAsync() + { + _connection = new NpgsqlConnection(_connectionString); + _connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(_connectionString) + .Options; + + var context = new VoteMonitorContext(options); + + context.Database.Migrate(); + + _respawner = await Respawner.CreateAsync(_connection, + new RespawnerOptions + { + TablesToIgnore = + [ + "__EFMigrationsHistory", + "AspNetRoles", + "AspNetUsers", + "Language", + "Countries" + ], + DbAdapter = DbAdapter.Postgres + }); + } + + public DbConnection GetConnection() + { + return _connection; + } + + public string GetConnectionString() + { + return _connectionString; + } + + public async Task ResetAsync() + { + await _respawner.ResetAsync(_connection); + } + + public async Task DisposeAsync() + { + await _connection.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs new file mode 100644 index 000000000..296aafa9e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs @@ -0,0 +1,14 @@ +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public static class TestDatabaseFactory +{ + public static async Task CreateAsync() + { + var database = new TestcontainersTestDatabase(); + // var database = new PostgresTestDatabase(); + + await database.InitialiseAsync(); + + return database; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs new file mode 100644 index 000000000..a2d3878d9 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs @@ -0,0 +1,77 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using Respawn; +using Testcontainers.PostgreSql; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public class TestcontainersTestDatabase : ITestDatabase +{ + private readonly PostgreSqlContainer _container; + private DbConnection _connection = null!; + private string _connectionString = null!; + private Respawner _respawner = null!; + + public TestcontainersTestDatabase() + { + _container = new PostgreSqlBuilder() + .WithAutoRemove(true) + // .WithExposedPort(33747) + // .WithPortBinding(33747, 5432) + .Build(); + } + + public async Task InitialiseAsync() + { + await _container.StartAsync(); + + _connectionString = _container.GetConnectionString(); + _connection = new NpgsqlConnection(_connectionString); + _connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(_connectionString) + .Options; + + var context = new VoteMonitorContext(options); + + context.Database.Migrate(); + + _respawner = await Respawner.CreateAsync(_connection, + new RespawnerOptions + { + TablesToIgnore = + [ + "__EFMigrationsHistory", + "AspNetRoles", + "AspNetUsers", + "Language", + "Countries" + ], + DbAdapter = DbAdapter.Postgres + }); + } + + public DbConnection GetConnection() + { + return _connection; + } + + public string GetConnectionString() + { + return _connectionString; + } + + public async Task ResetAsync() + { + await _respawner.ResetAsync(_connection); + } + + public async Task DisposeAsync() + { + await _connection.DisposeAsync(); + await _container.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs deleted file mode 100644 index 51c239d88..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Vote.Monitor.Api.IntegrationTests; - -/// -/// A helper class to deserialize FastEndpoints.ProblemDetails -/// -public sealed class FEProblemDetails -{ - public string Type { get; set; } = string.Empty; - public string Title { get; set; } = string.Empty; - public int Status { get; set; } - public string Instance { get; set; } = string.Empty; - public string TraceId { get; set; } = string.Empty; - public IEnumerable Errors { get; set; } = Array.Empty(); - - public sealed class Error - { - /// - /// the name of the error or property of the dto that caused the error - /// - public string Name { get; init; } = string.Empty; - - /// - /// the reason for the error - /// - public string Reason { get; init; } = string.Empty; - - /// - /// the code of the error - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Code { get; init; } - - /// - /// the severity of the error - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Severity { get; init; } - - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs new file mode 100644 index 000000000..852627b73 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs @@ -0,0 +1,33 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public static class Answers +{ + public static BaseAnswerRequest GetFakeAnswer(BaseQuestionRequest question) + { + switch (question) + { + case TextQuestionRequest _: + return new TextAnswerFaker(question.Id).Generate(); + + case NumberQuestionRequest _: + return new NumberAnswerFaker(question.Id).Generate(); + + case DateQuestionRequest _: + return new DateAnswerFaker(question.Id).Generate(); + + case SingleSelectQuestionRequest singleSelectQuestionRequest: + return new SingleSelectAnswerFaker(question.Id, singleSelectQuestionRequest.Options).Generate(); + + case MultiSelectQuestionRequest multiSelectQuestionRequest: + return new MultiSelectAnswerFaker(question.Id, multiSelectQuestionRequest.Options).Generate(); + + case RatingQuestionRequest _: + return new RatingAnswerFaker(question.Id).Generate(); + + default: throw new ApplicationException("Unknown question type received"); + } + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs new file mode 100644 index 000000000..3814bb8c7 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class DateAnswerFaker : Faker +{ + public DateAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Date, f => f.Date.Recent()); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs new file mode 100644 index 000000000..7aa377484 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs @@ -0,0 +1,15 @@ +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class ElectionRoundFaker : Faker +{ + public ElectionRoundFaker() + { + // Republic of Azerbaijan + RuleFor(x => x.CountryId, new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1")); + RuleFor(x => x.Title, f => f.Lorem.Sentence()); + RuleFor(x => x.EnglishTitle, f => f.Lorem.Sentence()); + RuleFor(x => x.StartDate, f => f.Date.FutureDateOnly()); + } +} \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs new file mode 100644 index 000000000..1edc4311e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs @@ -0,0 +1,32 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class MultiSelectAnswerFaker : Faker +{ + public MultiSelectAnswerFaker(Guid questionId, List options) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Selection, f => + { + var optionsToPick = f.Random.Number(1, options.Count); + + var selectedOptions = f.PickRandom(options, optionsToPick).Distinct().ToList(); + var selection = new List(); + + foreach (var selectedOption in selectedOptions) + { + string? text = null; + if (selectedOption.IsFreeText) + { + text = FakerHub.Lorem.Sentence(100); + } + + selection.Add(new SelectedOptionRequest { OptionId = selectedOption.Id, Text = text }); + } + + return selection; + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs new file mode 100644 index 000000000..1bc0cf7f1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class NumberAnswerFaker : Faker +{ + public NumberAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Value, f => f.Random.Number(0, 1_000_000)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/QuickReportRequestFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/QuickReportRequestFaker.cs new file mode 100644 index 000000000..6ec824635 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/QuickReportRequestFaker.cs @@ -0,0 +1,15 @@ +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.TestUtils.Utils; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class QuickReportRequestFaker: Faker +{ + public QuickReportRequestFaker(Guid pollingStationId) + { + RuleFor(x => x.PollingStationId, pollingStationId); + RuleFor(x => x.Id, f => f.Random.Guid()); + RuleFor(x => x.Title, f => f.Lorem.Sentence(20).OfLength(1000)); + RuleFor(x => x.Description, f => f.Lorem.Sentence(100).OfLength(10000)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs new file mode 100644 index 000000000..5049366b3 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class RatingAnswerFaker : Faker +{ + public RatingAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Value, f => f.Random.Number(1, 10)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs new file mode 100644 index 000000000..8180d6941 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs @@ -0,0 +1,25 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class SingleSelectAnswerFaker : Faker +{ + public SingleSelectAnswerFaker(Guid questionId, List options) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Selection, f => + { + var selectedOption = f.PickRandom(options); + + string? text = null; + if (selectedOption.IsFreeText) + { + text = f.Lorem.Sentence(100); + } + + var selection = new SelectedOptionRequest { OptionId = selectedOption.Id, Text = text }; + return selection; + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SubmissionFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SubmissionFaker.cs new file mode 100644 index 000000000..09662733e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SubmissionFaker.cs @@ -0,0 +1,18 @@ +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class SubmissionFaker : Faker +{ + public SubmissionFaker(Guid formId, Guid pollingStationId, List questions) + { + Rules((f, x) => + { + x.PollingStationId = pollingStationId; + x.FormId = formId; + x.Answers = f.PickRandom(questions, f.Random.Int(0, questions.Count)) + .Select(Answers.GetFakeAnswer).ToList(); + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs new file mode 100644 index 000000000..bf16d68d1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class TextAnswerFaker : Faker +{ + public TextAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Text, f => f.Lorem.Sentence(100)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs new file mode 100644 index 000000000..67c471a93 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs @@ -0,0 +1,210 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Forms.Models; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; +using ListMonitoringNgosResponse = Feature.Monitoring.List.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class CreateTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A) + .Please(); + + var coalitionName = Guid.NewGuid().ToString(); + var electionRoundId = scenarioData.ElectionRoundId; + + var coalition = await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = coalitionName, + LeaderId = scenarioData.NgoIdByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } + }); + + coalition.Should().NotBeNull(); + coalition.Name.Should().Be(coalitionName); + coalition.Members.Should().HaveCount(2); + + coalition.LeaderId.Should().Be(scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId); + coalition.Members.Select(x => x.Id).Should() + .Contain([scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId]); + } + + [Test] + public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa).WithMonitoringNgo(ScenarioNgos.Beta)) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Delta).NgoId } + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, + scenarioData.NgoByName(ScenarioNgos.Delta).NgoId + ]); + } + + [Test] + public async Task ShouldAddLeaderAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa).WithMonitoringNgo(ScenarioNgos.Beta)) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Delta).NgoId, + NgoMembersIds = Array.Empty() + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, + scenarioData.NgoByName(ScenarioNgos.Delta).NgoId + ]); + } + + [Test] + public async Task ShouldNotGiveAccessToNgoFormsForCoalitionMembers() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A"))) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, + NgoMembersIds = Array.Empty() + }); + + var formResult = await scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().BeEmpty(); + } + + [Test] + public async Task NgoAdmin_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithElectionRound(ScenarioElectionRound.A) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await scenarioData.Observer.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await CreateClient().PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs new file mode 100644 index 000000000..65c05cb3c --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs @@ -0,0 +1,238 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Form.Submissions.ListEntries; +using Feature.Forms.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class DeleteTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldDeleteCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalition = scenarioData.ElectionRound.Coalition; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalition.CoalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + + var getCoalitionByIdResponse = await scenarioData.PlatformAdmin.GetAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalition.CoalitionId}"); + + getCoalitionByIdResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + + [Test] + public async Task ShouldRemoveDataForMonitoringNgos() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], + cfg => cfg + .WithForm("Common", [ScenarioNgos.Beta], + form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var alfaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + var submission1 = + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + var submission2 = + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, ScenarioPollingStation.Bacau); + + alfaNgoSubmissions.Items.Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And + .BeEquivalentTo([submission1, submission2]); + + betaNgoSubmissions.Items.Should().HaveCount(0); + } + + [Test] + public async Task ShouldKeepFormForLeader() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], + cfg => cfg.WithForm("Common", [ScenarioNgos.Beta]) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var formResult = await scenarioData + .NgoByName(ScenarioNgos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().HaveCount(1); + formResult.Items.First().Id.Should().Be(scenarioData.ElectionRound.Coalition.FormId); + } + + [Test] + public async Task ShouldRemoveFormAccessFromExCoalitionMembers() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], + cfg => cfg + .WithForm("Common", [ScenarioNgos.Beta]) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var formResult = await scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().HaveCount(0); + } + + [Test] + public async Task NgoAdmin_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin()) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Observer.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = Array.Empty() + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await CreateClient().PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = Array.Empty() + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs new file mode 100644 index 000000000..c6f1a16d7 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -0,0 +1,282 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Forms.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class FormAccessTests : BaseApiTestFixture +{ + [Test] + public void ShouldNotGrantFormAccessForMonitoringObservers_WhenCreatingNewForm() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, + er => er + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + // Act + var formRequest = Dummy.Form("A"); + var electionRoundId = scenarioData.ElectionRoundId; + var ngoForm = + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/forms", + formRequest); + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + // Assert + var aliceForms = scenarioData + .ObserverByName(ScenarioObserver.Alice) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + var bobForms = scenarioData + .ObserverByName(ScenarioObserver.Bob) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + aliceForms.Forms.Should().BeEmpty(); + bobForms.Forms.Should().BeEmpty(); + } + + [Test] + public void ShouldNotGrantFormAccessForMonitoringNgos_WhenCreatingNewForm() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, + er => er + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + // Act + var formRequest = Dummy.Form("A"); + var electionRoundId = scenarioData.ElectionRoundId; + var ngoForm = + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/forms", + formRequest); + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + // Assert + var betaForms = scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + betaForms.Items.Should().BeEmpty(); + } + + [Test] + public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, + er => er + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); + + // Assert + var aliceForms = scenarioData + .ObserverByName(ScenarioObserver.Alice) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + var bobForms = scenarioData + .ObserverByName(ScenarioObserver.Bob) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + var betaForms = scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + var form = scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponse($"/api/election-rounds/{electionRoundId}/forms/{formId}"); + + aliceForms.Forms.Should().BeEmpty(); + bobForms.Forms.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); + betaForms.Items.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); + form.Should().NotBeNull(); + } + + [Test] + public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, + er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); + + var pollingStationId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var questions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + var submission = new SubmissionFaker(formId, pollingStationId, questions).Generate(); + + var observer = scenarioData.ObserverByName(ScenarioObserver.Bob); + + var submissionId = await observer.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/form-submissions", + submission); + + // Assert + submissionId.Should().HaveStatusCode(HttpStatusCode.OK); + } + + [Test] + public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, + er => er + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta, ScenarioNgos.Delta], c=>c.WithForm(sharedWithMembers:[ScenarioNgos.Beta]))) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Delta) } }); + + // Assert + var deltaForms = scenarioData + .NgoByName(ScenarioNgos.Delta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + deltaForms.Items.Should().HaveCount(1); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg.WithForm("A", []))) + .Please(); + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", []) + .WithMonitoringObserver(ScenarioNgos.Alfa, ScenarioObserver.Alice) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await scenarioData.ObserverByName(ScenarioObserver.Alice) + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], c => c.WithForm())) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await CreateClient() + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/GetMyTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/GetMyTests.cs new file mode 100644 index 000000000..e3ca1db17 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/GetMyTests.cs @@ -0,0 +1,67 @@ +using System.Net; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class GetMyTests : BaseApiTestFixture +{ + [Test] + public void ShouldReturnCoalitionDetails_WhenIsPartOfCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta]) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaNgo = scenarioData.NgoByName(ScenarioNgos.Alfa); + var betaNgo = scenarioData.NgoByName(ScenarioNgos.Beta); + + var alfaNgoAdmin = alfaNgo.Admin; + var betaNgoAdmin = betaNgo.Admin; + + // Act + var alfaNgoCoalition = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/coalitions:my"); + + var betaNgoCoalition = betaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/coalitions:my"); + + // Assert + alfaNgoCoalition.Should().BeEquivalentTo(betaNgoCoalition); + alfaNgoCoalition.Members.Should().HaveCount(2); + alfaNgoCoalition.Members.Select(x => x.Id).Should().BeEquivalentTo([alfaNgo.NgoId, betaNgo.NgoId]); + } + + [Test] + public async Task TaskShouldReturnNotFound_WhenNotInACoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + // Act + var alfaNgoCoalitionResponse = await alfaNgoAdmin + .GetAsync( + $"/api/election-rounds/{electionRoundId}/coalitions:my"); + + // Assert + alfaNgoCoalitionResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs new file mode 100644 index 000000000..f09633c94 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs @@ -0,0 +1,327 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Form.Submissions.ListEntries; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; +using ListMonitoringNgosResponse = Feature.Monitoring.List.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class UpdateTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var newCoalitionName = Guid.NewGuid().ToString(); + var updatedCoalition = await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = newCoalitionName, + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } + }); + + updatedCoalition.Should().NotBeNull(); + + updatedCoalition.Name.Should().Be(newCoalitionName); + updatedCoalition.Members.Should().HaveCount(2); + + updatedCoalition.LeaderId.Should().Be(scenarioData.NgoIdByName(ScenarioNgos.Alfa)); + updatedCoalition.Members.Select(x => x.Id).Should() + .Contain([scenarioData.NgoIdByName(ScenarioNgos.Alfa), scenarioData.NgoIdByName(ScenarioNgos.Beta)]); + } + + [Test] + public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] + { + scenarioData.NgoIdByName(ScenarioNgos.Beta), scenarioData.NgoIdByName(ScenarioNgos.Delta) + } + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoIdByName(ScenarioNgos.Alfa), + scenarioData.NgoIdByName(ScenarioNgos.Beta), + scenarioData.NgoIdByName(ScenarioNgos.Delta) + ]); + } + + [Test] + public async Task NgosStayAsMonitoringNgos_WhenTheyAreKicked() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] + { + scenarioData.NgoIdByName(ScenarioNgos.Beta), scenarioData.NgoIdByName(ScenarioNgos.Delta) + } + }); + + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoIdByName(ScenarioNgos.Alfa), + scenarioData.NgoIdByName(ScenarioNgos.Beta), + scenarioData.NgoIdByName(ScenarioNgos.Delta) + ]); + } + + + [Test] + public async Task ShouldKeepObserverDataForNgoForms_WhenNgoIsKicked() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithObserver(ScenarioObserver.Charlie) + .WithObserver(ScenarioObserver.Dave) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo + .WithMonitoringObserver(ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioObserver.Bob) + ) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo + .WithMonitoringObserver(ScenarioObserver.Charlie) + .WithMonitoringObserver(ScenarioObserver.Dave) + .WithForm("B", form => form + + .WithSubmission(ScenarioObserver.Charlie, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Charlie, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Cluj)) + ) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgo.Beta], cfg=> + cfg + .WithForm("A", [ScenarioNgo.Alfa], form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)))) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = Array.Empty(), + }); + + var alfaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + alfaNgoSubmissions.Items.Should().HaveCount(4); + betaNgoSubmissions.Items.Should().HaveCount(4); + } + + [Test] + public async Task ShouldRemoveDataForSharedFormsOfKickedNgos() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], + cfg => cfg + .WithForm("Common", [ScenarioNgos.Beta], + form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Alfa) } + }); + + var alfaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(ScenarioNgos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + var submission1 = + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, + ScenarioPollingStation.Iasi); + var submission2 = + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, + ScenarioPollingStation.Bacau); + + alfaNgoSubmissions.Items + .Should() + .HaveCount(2) + .And.Subject + .Select(x => x.SubmissionId) + .Should() + .BeEquivalentTo([submission1, submission2]); + + betaNgoSubmissions.Items.Should().HaveCount(0); + } + + + [Test] + public async Task NgoAdmin_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Ngo.Admin.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithObserver(ScenarioObserver.Alice) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Observer.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await CreateClient().PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/ElectionRounds/GetMonitoringTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/ElectionRounds/GetMonitoringTests.cs new file mode 100644 index 000000000..71b28d341 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/ElectionRounds/GetMonitoringTests.cs @@ -0,0 +1,86 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using ElectionRoundsMonitoringResult = Vote.Monitor.Api.Feature.ElectionRound.Monitoring.Result; + +namespace Vote.Monitor.Api.IntegrationTests.Features.ElectionRounds; + +using static ApiTesting; + +public class GetMonitoringTests : BaseApiTestFixture +{ + [Test] + public void ShouldReturnCorrectElectionRoundDetails() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta]) + ) + .WithElectionRound(ScenarioElectionRound.B, er => er + .WithMonitoringNgo(ScenarioNgo.Beta) + ) + .WithElectionRound(ScenarioElectionRound.C, er => er + .WithMonitoringNgo(ScenarioNgo.Alfa) + .WithMonitoringNgo(ScenarioNgo.Beta) + ) + .Please(); + + var electionRoundAId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.A); + var electionRoundBId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.B); + var electionRoundCId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.C); + // Act + var alfaNgoElectionRounds = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds:monitoring"); + + var betaNgoElectionRounds = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds:monitoring"); + + // Assert + alfaNgoElectionRounds.ElectionRounds + .Should() + .HaveCount(2); + + alfaNgoElectionRounds + .ElectionRounds + .First(x => x.Id == electionRoundAId) + .IsCoalitionLeader + .Should() + .BeTrue(); + + alfaNgoElectionRounds + .ElectionRounds + .First(x => x.Id == electionRoundCId) + .IsCoalitionLeader + .Should() + .BeFalse(); + + betaNgoElectionRounds.ElectionRounds + .Should() + .HaveCount(3); + + betaNgoElectionRounds + .ElectionRounds + .First(x => x.Id == electionRoundAId) + .IsCoalitionLeader + .Should() + .BeFalse(); + + betaNgoElectionRounds + .ElectionRounds + .First(x => x.Id == electionRoundBId) + .IsCoalitionLeader + .Should() + .BeFalse(); + + betaNgoElectionRounds + .ElectionRounds + .First(x => x.Id == electionRoundCId) + .IsCoalitionLeader + .Should() + .BeFalse(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetAggregatedTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetAggregatedTests.cs new file mode 100644 index 000000000..3cffd6eca --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetAggregatedTests.cs @@ -0,0 +1,238 @@ +using Vote.Monitor.Answer.Module.Aggregators; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +class AggregatedFormData +{ + public Guid FormId { get; set; } + public IReadOnlyList Responders { get; set; } + public int SubmissionCount { get; set; } +} + +class GetAggregatedResponse +{ + public AggregatedFormData SubmissionsAggregate { get; set; } +} + +public class GetAggregatedTests : BaseApiTestFixture +{ + [Test] + public void ShouldExcludeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var commonFormId = scenarioData.ElectionRound.Coalition.Form.Id; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + // Act + var aggregatedFormResponses = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{commonFormId}:aggregated?dataSource=Ngo"); + + // Assert + aggregatedFormResponses.SubmissionsAggregate.FormId + .Should() + .Be(commonFormId); + + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().HaveCount(1); + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().BeEquivalentTo([ + new Responder(alice.MonitoringObserverId, alice.DisplayName, alice.Email, alice.PhoneNumber) + ]); + aggregatedFormResponses.SubmissionsAggregate.SubmissionCount.Should().Be(2); + } + + [Test] + public void ShouldAnonymizedCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var commonFormId = scenarioData.ElectionRound.Coalition.Form.Id; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var aggregatedFormResponses = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{commonFormId}:aggregated?dataSource=Coalition"); + + // Assert + aggregatedFormResponses.SubmissionsAggregate.FormId + .Should() + .Be(commonFormId); + + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().HaveCount(2); + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().BeEquivalentTo([ + new Responder(alice.MonitoringObserverId, alice.DisplayName, alice.Email, alice.PhoneNumber), + new Responder(bob.MonitoringObserverId, bob.MonitoringObserverId.ToString(), + bob.MonitoringObserverId.ToString(), bob.MonitoringObserverId.ToString()) + ]); + aggregatedFormResponses.SubmissionsAggregate.SubmissionCount.Should().Be(4); + } + + [Test] + public void ShouldAllowFilteringResponsesByNgoId_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var commonFormId = scenarioData.ElectionRound.Coalition.Form.Id; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var betaNgoId = scenarioData.NgoIdByName(ScenarioNgos.Beta); + + // Act + var aggregatedFormResponses = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{commonFormId}:aggregated?dataSource=Coalition&coalitionMemberId={betaNgoId}"); + + // Assert + aggregatedFormResponses.SubmissionsAggregate.FormId + .Should() + .Be(commonFormId); + + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().HaveCount(1); + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().BeEquivalentTo([ + new Responder(bob.MonitoringObserverId, bob.MonitoringObserverId.ToString(), + bob.MonitoringObserverId.ToString(), bob.MonitoringObserverId.ToString()) + ]); + aggregatedFormResponses.SubmissionsAggregate.SubmissionCount.Should().Be(3); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var commonFormId = scenarioData.ElectionRound.Coalition.Form.Id; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var aggregatedFormResponses = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{commonFormId}:aggregated?dataSource={dataSource}"); + + // Assert + aggregatedFormResponses.SubmissionsAggregate.FormId + .Should() + .Be(commonFormId); + + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().HaveCount(1); + aggregatedFormResponses.SubmissionsAggregate.Responders.Should().BeEquivalentTo([ + new Responder(bob.MonitoringObserverId, bob.DisplayName, bob.Email, bob.PhoneNumber) + ]); + aggregatedFormResponses.SubmissionsAggregate.SubmissionCount.Should().Be(2); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetByIdTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetByIdTests.cs new file mode 100644 index 000000000..c5270e15e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetByIdTests.cs @@ -0,0 +1,154 @@ +using System.Net; +using Vote.Monitor.Answer.Module.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class GetByIdTests : BaseApiTestFixture +{ + [Test] + public void ShouldAnonymizedCoalitionMembersResponses_WhenCoalitionLeader() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var aliceSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Alice, ScenarioPollingStation.Cluj); + var bobSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var aliceSubmission = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}"); + + var bobSubmission = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}"); + + // Assert + aliceSubmission.Should().NotBeNull(); + bobSubmission.Should().NotBeNull(); + + aliceSubmission.ObserverName.Should().Be(alice.DisplayName); + aliceSubmission.Email.Should().Be(alice.Email); + aliceSubmission.PhoneNumber.Should().Be(alice.PhoneNumber); + aliceSubmission.MonitoringObserverId.Should().Be(alice.MonitoringObserverId); + + bobSubmission.ObserverName.Should().Be(bob.MonitoringObserverId.ToString()); + bobSubmission.Email.Should().Be(bob.MonitoringObserverId.ToString()); + bobSubmission.PhoneNumber.Should().Be(bob.MonitoringObserverId.ToString()); + bobSubmission.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + } + + [Test] + public void ShouldReturnNgoResponses_WhenCoalitionMember() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var bobSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var bobSubmission = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}"); + + // Assert + bobSubmission.Should().NotBeNull(); + + bobSubmission.ObserverName.Should().Be(bob.DisplayName); + bobSubmission.Email.Should().Be(bob.Email); + bobSubmission.PhoneNumber.Should().Be(bob.PhoneNumber); + bobSubmission.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + } + + [Test] + public async Task ShouldNotReturnResponses_WhenCoalitionMemberAccessesOtherMembersData() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var aliceSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Alice, ScenarioPollingStation.Cluj); + + // Act + var aliceSubmissionResponse = await scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetAsync($"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}"); + + // Assert + aliceSubmissionResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs new file mode 100644 index 000000000..328dbc8c7 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs @@ -0,0 +1,408 @@ +using NSubstitute; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; +using GetFiltersResponse = Feature.Form.Submissions.GetFilters.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class GetFiltersTests : BaseApiTestFixture +{ + private static readonly DateTime _now = DateTime.UtcNow.AddDays(1000); + private readonly DateTime _firstSubmissionAt = _now.AddDays(-5); + private readonly DateTime _secondSubmissionAt = _now.AddDays(-3); + private readonly DateTime _thirdSubmissionAt = _now.AddDays(-1); + + [Test] + public void ShouldIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithForm("A", [ScenarioNgos.Alfa]) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.Coalition.FormByCode("A").Id; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormByCode("Shared").Id; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.Coalition.FormByCode("A").Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.FormByCode("Shared").Questions; + + var iasiSubmission = + new SubmissionFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:filters?dataSource=Coalition"); + + // Assert + alfaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(2) + .And + .BeEquivalentTo([alfaFormId, coalitionFormId]); + + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); + } + + [Test] + public void + ShouldIncludeCoalitionMembersResponses_AndIgnoreMembersOwnFormsSubmissions_WhenGettingFiltersAsCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithForm("A")) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).FormId; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var betaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).Form.Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + + var iasiSubmission = new SubmissionFaker(betaFormId, psIasiId, betaFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(betaFormId, psClujId, betaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:filters?dataSource=Coalition"); + + // Assert + alfaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([coalitionFormId]); + + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); + + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); + } + + [Test] + public void ShouldNotIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithForm("A", [ScenarioNgos.Alfa]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + + var alfaFormId = scenarioData.ElectionRound.Coalition.FormByCode("A").Id; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormByCode("Shared").Id; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.Coalition.FormByCode("A").Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.FormByCode("Shared").Questions; + + var iasiSubmission = + new SubmissionFaker(alfaFormId, psIasiId, alfaFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:filters?dataSource=Ngo"); + + // Assert + alfaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([alfaFormId]); + + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A")) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + + var iasiSubmission = + new SubmissionFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var betaNgoFilters = betaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:filters?dataSource={dataSource}"); + + // Assert + betaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([coalitionFormId]); + + betaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + betaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); + } + + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsIndependentNgo(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, + ngo => ngo.WithForm("A").WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo.WithForm("A").WithMonitoringObserver(ScenarioObserver.Bob))) + .Please(); + + ApiTimeProvider.UtcNow + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + var betaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + var betaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).Form.Questions; + + var iasiSubmission = + new SubmissionFaker(alfaFormId, psIasiId, alfaFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(betaFormId, psBacauId, betaFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + alice.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + // Act + var aflaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:filters?dataSource={dataSource}"); + + // Assert + aflaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgo.Alfa).FormId]); + + aflaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + aflaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByFormTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByFormTests.cs new file mode 100644 index 000000000..d62cdc4d1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByFormTests.cs @@ -0,0 +1,235 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; +using ListByFormResponse = Feature.Form.Submissions.ListByForm.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class ListByFormTests : BaseApiTestFixture +{ + [Test] + public void ShouldExcludeCoalitionMembersForms_WhenCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + // Act + var forms = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:byForm?dataSource=Ngo"); + + // Assert + forms + .AggregatedForms + .Should() + .HaveCount(3); + var psiFormData = forms.AggregatedForms.First(x => x.FormCode == "PSI"); + psiFormData.NumberOfSubmissions.Should().Be(0); + + var aFormData = forms.AggregatedForms.First(x => x.FormCode == "A"); + aFormData.NumberOfSubmissions.Should().Be(1); + + var commonFormData = forms.AggregatedForms.First(x => x.FormCode == "Common"); + commonFormData.NumberOfSubmissions.Should().Be(1); + } + + [Test] + public void ShouldIncludeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + // Act + var forms = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:byForm?dataSource=Coalition"); + + // Assert + forms + .AggregatedForms + .Should() + .HaveCount(4); + + var psiFormData = forms.AggregatedForms.First(x => x.FormCode == "PSI"); + psiFormData.NumberOfSubmissions.Should().Be(0); + + var aFormData = forms.AggregatedForms.First(x => x.FormCode == "A"); + aFormData.NumberOfSubmissions.Should().Be(1); + + var commonFormData = forms.AggregatedForms.First(x => x.FormCode == "Common"); + commonFormData.NumberOfSubmissions.Should().Be(2); + + var betaFormData = forms.AggregatedForms.First(x => x.FormCode == "Beta only"); + betaFormData.NumberOfSubmissions.Should().Be(1); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + // Act + var forms = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:byForm?dataSource={dataSource}"); + + // Assert + forms + .AggregatedForms + .Should() + .HaveCount(4); + + // A form is not visible since it was not shared with Beta NGO + forms.AggregatedForms.Should().HaveCount(4); + var psiFormData = forms.AggregatedForms.First(x => x.FormCode == "PSI"); + psiFormData.NumberOfSubmissions.Should().Be(0); + + var commonFormData = forms.AggregatedForms.First(x => x.FormCode == "Common"); + commonFormData.NumberOfSubmissions.Should().Be(1); + + var betaFormData = forms.AggregatedForms.First(x => x.FormCode == "Beta only"); + betaFormData.NumberOfSubmissions.Should().Be(1); + + var bFormData = forms.AggregatedForms.First(x => x.FormCode == "B"); + bFormData.NumberOfSubmissions.Should().Be(1); + } + + [Test] + public void ShouldAllowFilteringResponsesByNgoId_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoId = scenarioData.NgoIdByName(ScenarioNgos.Beta); + + // Act + var response = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions:byForm?dataSource=Coalition&coalitionMemberId={betaNgoId}"); + // Assert + + // A form is not visible since it was not shared with anyone + response.AggregatedForms.Should().HaveCount(4); + + var psiFormData = response.AggregatedForms.First(x => x.FormCode == "PSI"); + psiFormData.NumberOfSubmissions.Should().Be(0); + + var aFormData = response.AggregatedForms.First(x => x.FormCode == "A"); + aFormData.NumberOfSubmissions.Should().Be(0); + + var commonFormData = response.AggregatedForms.First(x => x.FormCode == "Common"); + commonFormData.NumberOfSubmissions.Should().Be(3); + + var betaFormData = response.AggregatedForms.First(x => x.FormCode == "Beta only"); + betaFormData.NumberOfSubmissions.Should().Be(1); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByObserverTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByObserverTests.cs new file mode 100644 index 000000000..59390bd68 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListByObserverTests.cs @@ -0,0 +1,308 @@ +using Feature.Form.Submissions.ListByObserver; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class ListByObserverTests : BaseApiTestFixture +{ + [Test] + public void ShouldExcludeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta], form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj) + ) + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau)) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + // Act + var alfaNgoObservers = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource=Ngo"); + + // Assert + alfaNgoObservers.Items + .Select(x => x.MonitoringObserverId) + .Should() + .HaveCount(1) + .And.BeEquivalentTo([alice.MonitoringObserverId]); + + alfaNgoObservers.Items.First().NumberOfFormsSubmitted.Should().Be(2); + alfaNgoObservers.Items.First().IsOwnObserver.Should().BeTrue(); + } + + [Test] + public void ShouldAnonymizeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", form => form + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta], form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + ) + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau)) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var coalitionObservers = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource=Coalition"); + + // Assert + coalitionObservers.Items + .Should() + .HaveCount(2); + + var aliceData = coalitionObservers.Items + .First(x => x.MonitoringObserverId == alice.MonitoringObserverId); + + var bobData = coalitionObservers.Items + .First(x => x.MonitoringObserverId == bob.MonitoringObserverId); + + aliceData.NumberOfFormsSubmitted.Should().Be(2); + bobData.NumberOfFormsSubmitted.Should().Be(1); + + aliceData.ObserverName.Should().Be(alice.DisplayName); + bobData.ObserverName.Should().Be(bob.MonitoringObserverId.ToString()); + + aliceData.Email.Should().Be(alice.Email); + bobData.Email.Should().Be(bob.MonitoringObserverId.ToString()); + + aliceData.PhoneNumber.Should().Be(alice.PhoneNumber); + bobData.PhoneNumber.Should().Be(bob.MonitoringObserverId.ToString()); + aliceData.IsOwnObserver.Should().BeTrue(); + bobData.IsOwnObserver.Should().BeFalse(); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A")) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("A", form => form + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta], form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau)) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var betaNgoObservers = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource={dataSource}"); + + // Assert + betaNgoObservers.Items + .Should() + .HaveCount(1); + + var bobData = betaNgoObservers.Items.First(); + + bobData.NumberOfFormsSubmitted.Should().Be(2); + bobData.ObserverName.Should().Be(bob.DisplayName); + bobData.Email.Should().Be(bob.Email); + bobData.PhoneNumber.Should().Be(bob.PhoneNumber); + bobData.IsOwnObserver.Should().BeTrue(); + + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenIndependentNgo(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, + ngo => ngo + .WithMonitoringObserver(ScenarioObserver.Alice) + .WithForm("A", + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau))) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo + .WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("A", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)))) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var alfaNgoObservers = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource={dataSource}"); + + var betaObservers = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource={dataSource}"); + + // Assert + var aliceData = alfaNgoObservers.Items + .Should() + .ContainSingle() + .Subject; + + aliceData.NumberOfFormsSubmitted.Should().Be(2); + aliceData.MonitoringObserverId.Should().Be(alice.MonitoringObserverId); + aliceData.PhoneNumber.Should().Be(alice.PhoneNumber); + aliceData.ObserverName.Should().Be(alice.DisplayName); + aliceData.Email.Should().Be(alice.Email); + aliceData.IsOwnObserver.Should().BeTrue(); + + var bobData = betaObservers.Items + .Should() + .ContainSingle() + .Subject; + + bobData.NumberOfFormsSubmitted.Should().Be(1); + bobData.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobData.PhoneNumber.Should().Be(bob.PhoneNumber); + bobData.ObserverName.Should().Be(bob.DisplayName); + bobData.Email.Should().Be(bob.Email); + bobData.IsOwnObserver.Should().BeTrue(); + } + + [Test] + public void ShouldAllowFilteringObserversByNgoId_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoId = scenarioData.NgoIdByName(ScenarioNgos.Beta); + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var coalitionObservers = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byObserver?dataSource=Coalition&coalitionMemberId={betaNgoId}"); + + // Assert + coalitionObservers.TotalCount.Should().Be(1); + var bobData = coalitionObservers.Items + .Should() + .ContainSingle() + .Subject; + + bobData.NumberOfFormsSubmitted.Should().Be(4); + bobData.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobData.PhoneNumber.Should().Be(bob.MonitoringObserverId.ToString()); + bobData.ObserverName.Should().Be(bob.MonitoringObserverId.ToString()); + bobData.Email.Should().Be(bob.MonitoringObserverId.ToString()); + bobData.IsOwnObserver.Should().BeFalse(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs new file mode 100644 index 000000000..f297df67f --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs @@ -0,0 +1,345 @@ +using Feature.Form.Submissions.ListEntries; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class ListEntriesTests : BaseApiTestFixture +{ + [Test] + public void ShouldExcludeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo + .WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + // Act + var alfaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Ngo"); + + // Assert + alfaNgoFormSubmissions.Items + .Should() + .HaveCount(2); + + alfaNgoFormSubmissions.Items.Select(x => x.MonitoringObserverId) + .Distinct() + .Should() + .BeEquivalentTo([alice.MonitoringObserverId]); + + var iasiPollingStationId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var clujPollingStationId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + alfaNgoFormSubmissions.Items.Select(x => x.PollingStationId) + .Should() + .BeEquivalentTo([iasiPollingStationId, clujPollingStationId]); + } + + [Test] + public void ShouldAnonymizedCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("A", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithForm("A", [ScenarioNgos.Alfa]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.Coalition.FormByCode("A").Id; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.Coalition.FormByCode("A").Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + + var iasiSubmission = + new SubmissionFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.Client.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + // Assert + alfaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(3) + .And.BeEquivalentTo([firstSubmission.Id, secondSubmission.Id, thirdSubmission.Id]); + + alfaNgoFormSubmissions.Items.Select(x => x.ObserverName).Should() + .BeEquivalentTo(alice.DisplayName, alice.DisplayName, bob.MonitoringObserverId.ToString()); + alfaNgoFormSubmissions.Items.Select(x => x.Email).Should() + .BeEquivalentTo(alice.Email, alice.Email, bob.MonitoringObserverId.ToString()); + alfaNgoFormSubmissions.Items.Select(x => x.PhoneNumber).Should() + .BeEquivalentTo(alice.PhoneNumber, alice.PhoneNumber, bob.MonitoringObserverId.ToString()); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A")) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + var iasiSubmission = + new SubmissionFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = bob.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var betaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource={dataSource}"); + + // Assert + betaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And.BeEquivalentTo([secondSubmission.Id, thirdSubmission.Id]); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenGettingSubmissions_AsIndependentNgo(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, + ngo => ngo.WithForm("A").WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, + ngo => ngo.WithForm("B").WithMonitoringObserver(ScenarioObserver.Bob))) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + var betaFormId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + var betaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Beta).Form.Questions; + + var iasiSubmission = + new SubmissionFaker(alfaFormId, psIasiId, alfaFormQuestions).Generate(); + var clujSubmission = new SubmissionFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new SubmissionFaker(betaFormId, psBacauId, betaFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = alice.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource={dataSource}"); + + var betaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource={dataSource}"); + + // Assert + alfaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And.BeEquivalentTo([firstSubmission.Id, secondSubmission.Id]); + + betaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(1) + .And.BeEquivalentTo([thirdSubmission.Id]); + } + + [Test] + public void ShouldAllowFilteringResponsesByNgoId_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob) + .WithForm("B", + form => form.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi)) + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)) + .WithForm("Beta only", [ScenarioNgos.Beta], + betaForm => betaForm.WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi)) + ) + ) + .Please(); + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoId = scenarioData.NgoIdByName(ScenarioNgos.Beta); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + // Act + var alfaNgoFormSubmissions = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry?dataSource=Coalition&coalitionMemberId={betaNgoId}"); + + // Assert + alfaNgoFormSubmissions.TotalCount.Should().Be(4); + alfaNgoFormSubmissions.Items.Should().HaveCount(4); + alfaNgoFormSubmissions.Items + .Should() + .AllSatisfy(submission => submission.MonitoringObserverId.Should().Be(bob.MonitoringObserverId)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/UpdateStatusTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/UpdateStatusTests.cs new file mode 100644 index 000000000..f4577e646 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/UpdateStatusTests.cs @@ -0,0 +1,127 @@ +using Vote.Monitor.Answer.Module.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class UpdateStatusTests : BaseApiTestFixture +{ + [Test] + public void CoalitionAdminsCanUpdateStatusForFormSubmissionButNotForMembersObservers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var aliceSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Alice, ScenarioPollingStation.Cluj); + var bobSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + + // Act + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PutWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}:status", new + { + FollowUpStatus = SubmissionFollowUpStatus.NeedsFollowUp.ToString() + }); + + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .PutWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}:status", new + { + FollowUpStatus = SubmissionFollowUpStatus.NeedsFollowUp.ToString() + }); + + // Assert + var aliceSubmission = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}"); + + var bobSubmission = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}"); + + aliceSubmission.FollowUpStatus.Should().Be(SubmissionFollowUpStatus.NeedsFollowUp); + bobSubmission.FollowUpStatus.Should().Be(SubmissionFollowUpStatus.NotApplicable); + } + + [Test] + public void CoalitionMemberAdminsCanUpdateStatusForFormSubmissionButNotForOhterMembersObservers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Common", [ScenarioNgos.Alfa, ScenarioNgos.Beta], + commonForm => commonForm + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + ) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var aliceSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Alice, ScenarioPollingStation.Cluj); + var bobSubmissionId = scenarioData.ElectionRound.Coalition.GetSubmissionId("Common", ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + + // Act + scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .PutWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}:status", new + { + FollowUpStatus = SubmissionFollowUpStatus.NeedsFollowUp.ToString() + }); + + scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .PutWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}:status", new + { + FollowUpStatus = SubmissionFollowUpStatus.NeedsFollowUp.ToString() + }); + + // Assert + var aliceSubmission = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{aliceSubmissionId}"); + + var bobSubmission = scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions/{bobSubmissionId}"); + + aliceSubmission.FollowUpStatus.Should().Be(SubmissionFollowUpStatus.NotApplicable); + bobSubmission.FollowUpStatus.Should().Be(SubmissionFollowUpStatus.NeedsFollowUp); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/MonitoringObservers/ImportObserversTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/MonitoringObservers/ImportObserversTests.cs new file mode 100644 index 000000000..83f3aeccf --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/MonitoringObservers/ImportObserversTests.cs @@ -0,0 +1,238 @@ +using System.Web; +using Feature.MonitoringObservers; +using NSubstitute; +using NSubstitute.Extensions; +using Vote.Monitor.Api.Feature.Observer; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.Services.EmailTemplating; +using Vote.Monitor.Core.Services.EmailTemplating.Props; +using Vote.Monitor.Domain.Entities.ApplicationUserAggregate; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; + +namespace Vote.Monitor.Api.IntegrationTests.Features.MonitoringObservers; + +using static ApiTesting; + +public class ImportObserversTests : BaseApiTestFixture +{ + [Test] + public void ReImportedObserversShouldBeInPendingUntilTheyAcceptInvite() + { + // Arrange + EmailFactory.GenerateNewUserInvitationEmail(Arg.Any()) + .Returns(new EmailModel(string.Empty, string.Empty)); + EmailFactory.GenerateInvitationExistingUserEmail(Arg.Any()) + .Returns(new EmailModel(string.Empty, string.Empty)); + + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .WithElectionRound(ScenarioElectionRound.B, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .Please(); + + var electionRoundAId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.A); + var electionRoundBId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.B); + var admin = scenarioData.AdminOfNgo(ScenarioNgo.Alfa); + var platformAdmin = scenarioData.PlatformAdmin; + + string importFile = + $""" + "Email","FirstName","LastName","PhoneNumber" + "{Guid.NewGuid()}@example.com","Alice","Smith","5551111" + """; + // Act + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundAId}/monitoring-observers:import", + importFile); + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundBId}/monitoring-observers:import", + importFile); + + // Assert + var observersElectionA = + admin.GetResponse>( + $"/api/election-rounds/{electionRoundAId}/monitoring-observers"); + var observersElectionB = + admin.GetResponse>( + $"/api/election-rounds/{electionRoundBId}/monitoring-observers"); + + observersElectionA.Items.Should().ContainSingle(); + observersElectionB.Items.Should().ContainSingle(); + + observersElectionA.Items.First().Status.Should().Be(MonitoringObserverStatus.Pending); + observersElectionB.Items.First().Status.Should().Be(MonitoringObserverStatus.Pending); + + var observers = platformAdmin.GetResponse>("/api/observers"); + observers.Items.Should().ContainSingle(); + observers.Items.First().Status.Should().Be(UserStatus.Pending); + } + + [Test] + public void ReImportedObserversShouldBeActiveIfTheyAcceptInvite() + { + // Arrange + EmailFactory.GenerateNewUserInvitationEmail(Arg.Any()) + .Returns(new EmailModel(string.Empty, string.Empty)); + EmailFactory.GenerateInvitationExistingUserEmail(Arg.Any()) + .Returns(new EmailModel(string.Empty, string.Empty)); + + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .WithElectionRound(ScenarioElectionRound.B, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .Please(); + + var electionRoundAId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.A); + var electionRoundBId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.B); + var admin = scenarioData.AdminOfNgo(ScenarioNgo.Alfa); + var platformAdmin = scenarioData.PlatformAdmin; + var invitationToken = string.Empty; + + string importFile = + $""" + "Email","FirstName","LastName","PhoneNumber" + "{Guid.NewGuid()}@example.com","Alice","Smith","5551111" + """; + + EmailFactory + .GenerateNewUserInvitationEmail(Arg.Do(x => + { + invitationToken = GetInvitationTokenFromInviteUrl(x.AcceptUrl); + })) + .Returns(new EmailModel(string.Empty, string.Empty)); + + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundAId}/monitoring-observers:import", + importFile); + + CreateClient().PostWithoutResponse("/api/auth/accept-invite", + new { InvitationToken = invitationToken, Password = "parola123", ConfirmPassword = "parola123" }); + + // Act + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundBId}/monitoring-observers:import", + importFile); + + // Assert + var observersElectionA = + admin.GetResponse>( + $"/api/election-rounds/{electionRoundAId}/monitoring-observers"); + var observersElectionB = + admin.GetResponse>( + $"/api/election-rounds/{electionRoundBId}/monitoring-observers"); + + observersElectionA.Items.Should().ContainSingle(); + observersElectionB.Items.Should().ContainSingle(); + + observersElectionA.Items.First().Status.Should().Be(MonitoringObserverStatus.Active); + observersElectionB.Items.First().Status.Should().Be(MonitoringObserverStatus.Active); + + var observers = platformAdmin.GetResponse>("/api/observers"); + observers.Items.Should().ContainSingle(); + observers.Items.First().Status.Should().Be(UserStatus.Active); + } + + [Test] + public void ReImportedObserversThatHavePendingAccountShouldReceiveAcceptInviteEmail() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .WithElectionRound(ScenarioElectionRound.B, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .Please(); + + var aliceEmail = $"{Guid.NewGuid()}@example.com"; + string importFile = + $""" + "Email","FirstName","LastName","PhoneNumber" + "{aliceEmail}","Alice","Smith","5551111" + """; + + var electionRoundAId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.A); + var electionRoundBId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.B); + var admin = scenarioData.AdminOfNgo(ScenarioNgo.Alfa); + var acceptUrl = Guid.NewGuid().ToString(); + + EmailFactory + .GenerateNewUserInvitationEmail(Arg.Any()) + .Returns(new EmailModel("This is a confirm account email", acceptUrl)); + + // Act + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundAId}/monitoring-observers:import", + importFile); + + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundBId}/monitoring-observers:import", + importFile); + + // Assert + JobService + .Received(2) + .EnqueueSendEmail(aliceEmail, "This is a confirm account email", acceptUrl); + } + + [Test] + public void ReImportedObserversThatHaveActiveAccountShouldReceiveNotificationEmail() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .WithElectionRound(ScenarioElectionRound.B, er => er.WithMonitoringNgo(ScenarioNgo.Alfa)) + .Please(); + + var electionRoundAId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.A); + var electionRoundBId = scenarioData.ElectionRoundIdByName(ScenarioElectionRound.B); + var admin = scenarioData.AdminOfNgo(ScenarioNgo.Alfa); + var invitationToken = string.Empty; + + var aliceEmail = $"{Guid.NewGuid()}@example.com"; + string importFile = + $""" + "Email","FirstName","LastName","PhoneNumber" + "{aliceEmail}","Alice","Smith","5551111" + """; + + EmailFactory + .Configure() + .GenerateNewUserInvitationEmail(Arg.Do(x => + { + invitationToken = GetInvitationTokenFromInviteUrl(x.AcceptUrl); + })) + .Returns(x => new EmailModel("This is a confirm account email", + GetInvitationTokenFromInviteUrl(x.Arg().AcceptUrl))); + + EmailFactory + .GenerateInvitationExistingUserEmail(Arg.Any()) + .Returns(new EmailModel("This is a notification email", "Notification email body")); + + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundAId}/monitoring-observers:import", + importFile); + + CreateClient().PostWithoutResponse("/api/auth/accept-invite", + new { InvitationToken = invitationToken, Password = "parola123", ConfirmPassword = "parola123" }); + + // Act + admin.PostFileWithoutResponse($"/api/election-rounds/{electionRoundBId}/monitoring-observers:import", + importFile); + + // Assert + JobService + .Received(1) + .EnqueueSendEmail(aliceEmail, + "This is a confirm account email", Arg.Is(invitationToken)); + + JobService + .Received(1) + .EnqueueSendEmail(aliceEmail, "This is a notification email", "Notification email body"); + } + + private string GetInvitationTokenFromInviteUrl(string inviteUrl) + { + // Parse the string as a Uri + Uri uri = new Uri(inviteUrl); + + // Extract query parameters + var queryParams = HttpUtility.ParseQueryString(uri.Query); + + return queryParams["invitationToken"]!; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetFiltersTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetFiltersTests.cs new file mode 100644 index 000000000..8ade1c5c1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetFiltersTests.cs @@ -0,0 +1,205 @@ +using NSubstitute; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; +using GetFiltersResponse = Feature.QuickReports.GetFilters.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.QuickReports; + +using static ApiTesting; + +public class GetFiltersTests : BaseApiTestFixture +{ + private static readonly DateTime _now = DateTime.UtcNow.AddDays(1000); + private readonly DateTime _firstQuickReportAt = _now.AddDays(-5); + private readonly DateTime _secondQuickReportAt = _now.AddDays(-3); + private readonly DateTime _thirdQuickReportAt = _now.AddDays(-1); + + [Test] + public void ShouldIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstQuickReportAt, _secondQuickReportAt, _thirdQuickReportAt); + + var electionRoundId = scenarioData.ElectionRoundId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var iasiQuickReport = new QuickReportRequestFaker(psIasiId).Generate(); + var clujQuickReport = new QuickReportRequestFaker(psClujId).Generate(); + var bacauQuickReport = new QuickReportRequestFaker(psBacauId).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + iasiQuickReport); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + clujQuickReport); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + bacauQuickReport); + + // Act + var alfaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports:filters?dataSource=Coalition"); + + // Assert + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstQuickReportAt, TimeSpan.FromMicroseconds(100)); + + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdQuickReportAt, TimeSpan.FromMicroseconds(100)); + } + + [Test] + public void ShouldNotIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstQuickReportAt, _secondQuickReportAt, _thirdQuickReportAt); + + var electionRoundId = scenarioData.ElectionRoundId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var iasiQuickReport = new QuickReportRequestFaker(psIasiId).Generate(); + var clujQuickReport = new QuickReportRequestFaker(psClujId).Generate(); + var bacauQuickReport = new QuickReportRequestFaker(psBacauId).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + iasiQuickReport); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + clujQuickReport); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + bacauQuickReport); + + // Act + var alfaNgoFilters = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports:filters?dataSource=Ngo"); + + // Assert + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstQuickReportAt, TimeSpan.FromMicroseconds(100)); + + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_secondQuickReportAt, TimeSpan.FromMicroseconds(100)); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + ApiTimeProvider.UtcNow + .Returns(_firstQuickReportAt, _secondQuickReportAt, _thirdQuickReportAt); + + var electionRoundId = scenarioData.ElectionRoundId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var iasiQuickReport = new QuickReportRequestFaker(psIasiId).Generate(); + var clujQuickReport = new QuickReportRequestFaker(psClujId).Generate(); + var bacauQuickReport = new QuickReportRequestFaker(psBacauId).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + iasiQuickReport); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + clujQuickReport); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports", + bacauQuickReport); + + // Act + var betaNgoFilters = betaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports:filters?dataSource={dataSource}"); + + // Assert + betaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_thirdQuickReportAt, TimeSpan.FromMicroseconds(100)); + + betaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdQuickReportAt, TimeSpan.FromMicroseconds(100)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetTests.cs new file mode 100644 index 000000000..26f4b8026 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/GetTests.cs @@ -0,0 +1,225 @@ +using System.Net; +using Feature.QuickReports.Get; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; + +namespace Vote.Monitor.Api.IntegrationTests.Features.QuickReports; + +using static ApiTesting; + +public class GetTests : BaseApiTestFixture +{ + [Test] + public void ShouldAnonymizedCoalitionMembersQuickReports_WhenCoalitionLeader() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var aliceQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + var bobQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + // Act + var aliceQuickReport = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports/{aliceQuickReportId}"); + + var bobQuickReport = alfaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports/{bobQuickReportId}"); + + // Assert + aliceQuickReport.Should().NotBeNull(); + aliceQuickReport.MonitoringObserverId.Should().Be(alice.MonitoringObserverId); + aliceQuickReport.ObserverName.Should().Be(alice.DisplayName); + aliceQuickReport.Email.Should().Be(alice.Email); + aliceQuickReport.PhoneNumber.Should().Be(alice.PhoneNumber); + + bobQuickReport.Should().NotBeNull(); + bobQuickReport.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobQuickReport.ObserverName.Should().Be(bob.MonitoringObserverId.ToString()); + bobQuickReport.Email.Should().Be(bob.MonitoringObserverId.ToString()); + bobQuickReport.PhoneNumber.Should().Be(bob.MonitoringObserverId.ToString()); + } + + [Test] + public void ShouldReturnNgoQuickReports_WhenCoalitionMember() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var bobQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + // Act + var bobQuickReport = betaNgoAdmin + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports/{bobQuickReportId}"); + + // Assert + bobQuickReport.Should().NotBeNull(); + bobQuickReport.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobQuickReport.ObserverName.Should().Be(bob.DisplayName); + } + + [Test] + public async Task ShouldNotReturnResponses_WhenCoalitionMemberAccessesOtherMembersQuickReports() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + var aliceQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + + // Act + var aliceQuickReportResponse = await betaNgoAdmin + .GetAsync($"/api/election-rounds/{electionRoundId}/quick-reports/{aliceQuickReportId}"); + + // Assert + aliceQuickReportResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + + [Test] + public void ShouldReturnQuickReport_WhenOwnerAccessesIt() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var quickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + // Act + var bobQuickReport = bob.Client + .GetResponse( + $"/api/election-rounds/{electionRoundId}/quick-reports/{quickReportId}"); + + // Assert + bobQuickReport.Should().NotBeNull(); + bobQuickReport.MonitoringObserverId.Should().BeEmpty(); + bobQuickReport.ObserverName.Should().BeNull(); + bobQuickReport.Tags.Should().BeEmpty(); + bobQuickReport.Email.Should().BeNull(); + bobQuickReport.PhoneNumber.Should().BeNull(); + } + + [Test] + public async Task ShouldNotReturnResponses_WhenObserverAccessesOtherObserversQuickReport() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var aliceQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + + // Act + var aliceQuickReportResponse = await bob.Client + .GetAsync($"/api/election-rounds/{electionRoundId}/quick-reports/{aliceQuickReportId}"); + + // Assert + aliceQuickReportResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/ListTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/ListTests.cs new file mode 100644 index 000000000..c2095446d --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/QuickReports/ListTests.cs @@ -0,0 +1,209 @@ +using Feature.QuickReports.List; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.QuickReports; + +using static ApiTesting; + +public class ListTests : BaseApiTestFixture +{ + [Test] + public void ShouldExcludeCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + var electionRoundId = scenarioData.ElectionRoundId; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var aliceQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + + // Act + var alfaNgoQuickReports = alfaNgoAdmin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/quick-reports?dataSource=Ngo"); + + // Assert + alfaNgoQuickReports.Should().NotBeNull(); + alfaNgoQuickReports.Items.Should().HaveCount(1); + + var aliceQuickReport = alfaNgoQuickReports.Items.First(); + aliceQuickReport.Id.Should().Be(aliceQuickReportId); + aliceQuickReport.MonitoringObserverId.Should().Be(alice.MonitoringObserverId); + aliceQuickReport.ObserverName.Should().Be(alice.DisplayName); + aliceQuickReport.PhoneNumber.Should().Be(alice.PhoneNumber); + aliceQuickReport.Email.Should().Be(alice.Email); + } + + [Test] + public void ShouldAnonymizedCoalitionMembersResponses_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + var electionRoundId = scenarioData.ElectionRoundId; + + var alice = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var aliceQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); + var bobQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + // Act + var coalitionQuickReports = alfaNgoAdmin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/quick-reports?dataSource=Coalition"); + + // Assert + coalitionQuickReports.Should().NotBeNull(); + coalitionQuickReports.Items.Should().HaveCount(2); + + var aliceQuickReport = coalitionQuickReports.Items.First(x => x.Id == aliceQuickReportId); + aliceQuickReport.MonitoringObserverId.Should().Be(alice.MonitoringObserverId); + aliceQuickReport.ObserverName.Should().Be(alice.DisplayName); + aliceQuickReport.PhoneNumber.Should().Be(alice.PhoneNumber); + aliceQuickReport.Email.Should().Be(alice.Email); + + var bobQuickReport = coalitionQuickReports.Items.First(x => x.Id == bobQuickReportId); + bobQuickReport.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobQuickReport.ObserverName.Should().Be(bob.MonitoringObserverId.ToString()); + bobQuickReport.PhoneNumber.Should().Be(bob.MonitoringObserverId.ToString()); + bobQuickReport.Email.Should().Be(bob.MonitoringObserverId.ToString()); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldReturnNgoResponses_WhenCoalitionMember(DataSource dataSource) + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var betaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Beta).Admin; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var bobQuickReportId = + scenarioData.ElectionRound.GetQuickReportId(ScenarioObserver.Bob, ScenarioPollingStation.Iasi); + + // Act + var coalitionQuickReports = betaNgoAdmin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/quick-reports?dataSource={dataSource}"); + + // Assert + coalitionQuickReports.Should().NotBeNull(); + coalitionQuickReports.Items.Should().ContainSingle(); + + var bobQuickReport = coalitionQuickReports.Items.First(); + bobQuickReport.Id.Should().Be(bobQuickReportId); + bobQuickReport.MonitoringObserverId.Should().Be(bob.MonitoringObserverId); + bobQuickReport.ObserverName.Should().Be(bob.DisplayName); + bobQuickReport.PhoneNumber.Should().Be(bob.PhoneNumber); + bobQuickReport.Email.Should().Be(bob.Email); + } + + [Test] + public void ShouldAllowFilteringResponsesByNgoId_WhenCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Cluj) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + )) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaNgoAdmin = scenarioData.NgoByName(ScenarioNgos.Alfa).Admin; + + var bob = scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + var betaNgoId = scenarioData.NgoIdByName(ScenarioNgos.Beta); + + // Act + var coalitionQuickReports = alfaNgoAdmin + .GetResponse>( + $"/api/election-rounds/{electionRoundId}/quick-reports?dataSource=Coalition&coalitionMemberId={betaNgoId}"); + + // Assert + coalitionQuickReports.Should().NotBeNull(); + coalitionQuickReports.TotalCount.Should().Be(3); + coalitionQuickReports.Items + .Should() + .HaveCount(3) + .And + .AllSatisfy(qr => qr.MonitoringObserverId.Should().Be(bob.MonitoringObserverId)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs index 3329ca202..03c5616fb 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs @@ -1,13 +1,3 @@ -// Global using directives - -global using Bogus; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.TestHost; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.Extensions.DependencyInjection; -global using Testcontainers.PostgreSql; -global using Vote.Monitor.Domain; -global using Xunit.Abstractions; -global using FastEndpoints; +global using Bogus; global using FluentAssertions; -global using Xunit; +global using NUnit.Framework; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs new file mode 100644 index 000000000..7317fe9b4 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs @@ -0,0 +1,208 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using Vote.Monitor.Api.Feature.Auth.Services; + +namespace Vote.Monitor.Api.IntegrationTests; + +public static class HttpClientExtensions +{ + private static readonly JsonSerializerOptions _jsonSerializerOptions = + new JsonSerializerOptions(JsonSerializerDefaults.Web); + + public static HttpClient NewForAuthenticatedUser(this Func clientFactory, string email, + string password) + { + var authenticatedUser = clientFactory(); + + var loginResponse = authenticatedUser + .PostWithResponse("/api/auth/login", new { email, password }); + + authenticatedUser.DefaultRequestHeaders.Authorization = new("Bearer", loginResponse.Token); + + return authenticatedUser; + } + + public static void PostWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static void PostWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri) + { + var response = client.PostAsync(requestUri, null).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static void PostFileWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string requestUri, + string file, + string? fieldName="File", + string? fileName="data.csv") + { + // Create the request content + using var formData = new MultipartFormDataContent(); + + // Add the file content + using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(file)); + + var fileContent = new StreamContent(fileStream); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + formData.Add(fileContent, fieldName, fileName); + + var response = client.PostAsync(requestUri, formData).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static void PutWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static TResponse PostWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + public static TResponse PutWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + public static TResponse GetResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri) + { + var response = client.GetAsync(requestUri).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + + public static async Task PostWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PostAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } + + public static async Task PutWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PutAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } + + public static async Task GetResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + CancellationToken cancellationToken = default) + { + var response = await client.GetAsync(requestUri, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs deleted file mode 100644 index 39b2db720..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Security.Claims; -using MartinCostello.Logging.XUnit; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.Logging; -using Vote.Monitor.Api.Feature.Auth.Login; -using Vote.Monitor.Api.Feature.Auth.Services; -using Vote.Monitor.Api.Feature.ElectionRound; -using Vote.Monitor.Core.Security; -using Vote.Monitor.Core.Services.Security; -using Vote.Monitor.Domain.Constants; - -using ElectionRoundCreateEndpoint = Vote.Monitor.Api.Feature.ElectionRound.Create.Endpoint; -using ElectionRoundCreateRequest = Vote.Monitor.Api.Feature.ElectionRound.Create.Request; - -namespace Vote.Monitor.Api.IntegrationTests; - -public class HttpServerFixture : WebApplicationFactory, IAsyncLifetime, ITestOutputHelperAccessor where TDataSeeder : class, IDataSeeder -{ - private static readonly Faker _faker = new(); - private PostgreSqlContainer _postgresContainer = default!; - - public ITestOutputHelper? OutputHelper { get; set; } - - /// - /// bogus data generator - /// - public Faker Fake => _faker; - - /// - /// the default http client - /// - public HttpClient Client { get; private set; } - - /// - /// The platform admin http client - /// - public HttpClient PlatformAdmin { get; private set; } - public ElectionRoundModel ElectionRound { get; private set; } - - private readonly ClaimsPrincipal _integrationTestingUser = new([new ClaimsIdentity([new Claim(ApplicationClaimTypes.UserId, "007e57ed-7e57-7e57-7e57-007e57ed0000")], "fake")]); - private const string AdminEmail = "admin@example.com"; - private const string AdminPassword = "toTallyNotTestPassw0rd"; - - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.UseEnvironment("Testing"); - builder.ConfigureTestServices(services => - { - var descriptorType = typeof(DbContextOptions); - RemoveService(services, descriptorType); - - services.AddDbContext(options => options.UseNpgsql(_postgresContainer.GetConnectionString())); - services.AddScoped(); - }); - - builder.ConfigureLogging(l => l.ClearProviders().AddXUnit(this).SetMinimumLevel(LogLevel.Debug)); - } - - public async Task InitializeAsync() - { - _postgresContainer = new PostgreSqlBuilder() - .WithDatabase(Guid.NewGuid().ToString()) - .Build(); - - await _postgresContainer.StartAsync(); - - var currentUserInitializer = Services.GetRequiredService(); - currentUserInitializer.SetCurrentUser(_integrationTestingUser); - - Client = CreateClient(); - - var (tokenResult, tokenResponse) = await Client.POSTAsync(new() - { - Email = AdminEmail, - Password = AdminPassword - }); - - tokenResult.IsSuccessStatusCode.Should().BeTrue(); - - PlatformAdmin = CreateClient(); - PlatformAdmin.DefaultRequestHeaders.Authorization = new("Bearer", tokenResponse.Token); - - (var electionRoundResult, ElectionRound) = await PlatformAdmin.POSTAsync(new() - { - CountryId = CountriesList.RO.Id, - Title = Guid.NewGuid().ToString(), - EnglishTitle = Guid.NewGuid().ToString(), - StartDate = Fake.Date.FutureDateOnly() - }); - - electionRoundResult.IsSuccessStatusCode.Should().BeTrue(); - - var seeder = Services.GetRequiredService(); - await seeder.SeedDataAsync(); - } - - public new async Task DisposeAsync() => await _postgresContainer.DisposeAsync().AsTask(); - - private static void RemoveService(IServiceCollection services, Type descriptorType) - { - var descriptor = services.SingleOrDefault(s => s.ServiceType == descriptorType); - if (descriptor is not null) - { - services.Remove(descriptor); - } - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs deleted file mode 100644 index 62639c23d..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests; - -/// -/// Data seeder contract used in -/// -public interface IDataSeeder -{ - Task SeedDataAsync(); -} diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs similarity index 79% rename from api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs rename to api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs index 01c52fe8f..501c64f64 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs @@ -3,6 +3,8 @@ using NSubstitute; using Vote.Monitor.Core.Services.Security; using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; using Vote.Monitor.Domain.Constants; using Vote.Monitor.Domain.Entities.ApplicationUserAggregate; using Vote.Monitor.Domain.Entities.Auditing; @@ -11,34 +13,47 @@ using Vote.Monitor.Domain.Entities.ObserverAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -namespace Vote.Monitor.Domain.UnitTests.Interceptors; +namespace Vote.Monitor.Api.IntegrationTests.Interceptors; -public class AuditTrailInterceptorTests +using static ApiTesting; + +public class AuditTrailInterceptorTests : BaseDbTestFixture { - private readonly ITimeProvider _fakeTimeProvider; - private readonly ICurrentUserProvider _fakeCurrentUserProvider; - private readonly TestContext _context; + private ITimeProvider _fakeTimeProvider; + private ICurrentUserProvider _fakeCurrentUserProvider; + private VoteMonitorContext _context; - public AuditTrailInterceptorTests() + [SetUp] + public void Init() { - _fakeTimeProvider = Substitute.For(); _fakeCurrentUserProvider = Substitute.For(); + _fakeTimeProvider = Substitute.For(); - var dbContextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()); + var options = new DbContextOptionsBuilder() + .UseNpgsql(DbConnectionString) + .AddInterceptors(new AuditTrailInterceptor(new SerializerService(NullLogger.Instance), + _fakeCurrentUserProvider, + _fakeTimeProvider)) + .Options; - _context = new TestContext(dbContextOptions.Options, - new SerializerService(NullLogger.Instance), - _fakeTimeProvider, - _fakeCurrentUserProvider); + _context = new VoteMonitorContext(options); } - [Fact] + [TearDown] + public void Cleanup() + { + _fakeTimeProvider = null!; + _fakeCurrentUserProvider = null!; + _context.Dispose(); + } + + + [Test] public async Task Interceptor_OnEntityAdd_SaveChangesAsync_AddsCreateAuditTrail() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -62,12 +77,12 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_AddsCreateAuditTrail( auditTrail.NewValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityAdd_SaveChanges_AddsCreateAuditTrail() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -91,14 +106,14 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_AddsCreateAuditTrail() auditTrail.NewValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_AddsUpdateAudit() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -127,14 +142,14 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_AddsUpdateAudit() auditTrail.NewValues.Should().Contain(newNgoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChanges_AddsUpdateAudit() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -163,14 +178,14 @@ public async Task Interceptor_OnEntityUpdate_SaveChanges_AddsUpdateAudit() auditTrail.NewValues.Should().Contain(newNgoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityDelete_SaveChangesAsync_AddsDeleteAudit() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -197,14 +212,14 @@ public async Task Interceptor_OnEntityDelete_SaveChangesAsync_AddsDeleteAudit() auditTrail.OldValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityDelete_SaveChanges_AddsDeleteAudit() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -231,14 +246,14 @@ public async Task Interceptor_OnEntityDelete_SaveChanges_AddsDeleteAudit() auditTrail.OldValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForForm() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -252,11 +267,11 @@ public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForF var applicationUser = ApplicationUser.CreateObserver("test", "test", "email", "222", "password"); var observer = Observer.Create(applicationUser); - var monitoringObserver = new MonitoringObserverFaker(electionRound: electionRound, monitoringNgo: monitoringNgo, - observer: observer) - .Generate(); + var monitoringObserver = + new MonitoringObserverFaker(electionRound: electionRound, monitoringNgo: monitoringNgo, observer: observer) + .Generate(); - await _context.Countries.AddAsync(CountriesList.AD.ToEntity()); + // await _context.Countries.AddAsync(CountriesList.AD.ToEntity()); await _context.ElectionRounds.AddAsync(electionRound); await _context.PollingStations.AddAsync(pollingStation); await _context.Ngos.AddAsync(ngo); @@ -285,4 +300,4 @@ public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForF newTrails.First().Type.Should().Be(TrailType.Create); newTrails.First().TableName.Should().Be("FormSubmission"); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs similarity index 62% rename from api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs rename to api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs index 55f0a81b2..2839c4acd 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs @@ -1,41 +1,50 @@ using Microsoft.EntityFrameworkCore; using NSubstitute; using Vote.Monitor.Core.Services.Security; -using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.NgoAggregate; -namespace Vote.Monitor.Domain.UnitTests.Interceptors; +namespace Vote.Monitor.Api.IntegrationTests.Interceptors; -public class AuditingInterceptorTests +using static ApiTesting; + +public class AuditingInterceptorTests : BaseDbTestFixture { - private readonly ITimeProvider _fakeTimeProvider; - private readonly ICurrentUserProvider _fakeCurrentUserProvider; - private readonly TestContext _context; + private ICurrentUserProvider _currentUserProvider; + private ITimeProvider _fakeTimeProvider; + private VoteMonitorContext _context; - public AuditingInterceptorTests() + [SetUp] + public void Init() { _fakeTimeProvider = Substitute.For(); - var fakeSerializationService = Substitute.For(); - _fakeCurrentUserProvider = Substitute.For(); + _currentUserProvider = Substitute.For(); + var options = new DbContextOptionsBuilder() + .UseNpgsql(DbConnectionString) + .AddInterceptors(new AuditingInterceptor(_currentUserProvider, _fakeTimeProvider)) + .Options; - var dbContextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()); + _context = new VoteMonitorContext(options); + } - _context = new TestContext(dbContextOptions.Options, - fakeSerializationService, - _fakeTimeProvider, - _fakeCurrentUserProvider); + [TearDown] + public void Cleanup() + { + _fakeTimeProvider = null!; + _currentUserProvider = null!; + _context.Dispose(); } - [Fact] + [Test] public async Task Interceptor_OnEntityAdd_SaveChangesAsync_SetsCreatedFields() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); - _fakeCurrentUserProvider.GetUserId().Returns(userId); + _currentUserProvider.GetUserId().Returns(userId); //Act var testEntity = new Ngo(string.Empty); @@ -51,15 +60,15 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_SetsCreatedFields() testEntity.LastModifiedBy.Should().Be(userId); } - [Fact] + [Test] public async Task Interceptor_OnEntityAdd_SaveChanges_SetsCreatedFields() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); - _fakeCurrentUserProvider.GetUserId().Returns(userId); + _currentUserProvider.GetUserId().Returns(userId); //Act var testEntity = new Ngo(string.Empty); @@ -76,18 +85,18 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_SetsCreatedFields() testEntity.LastModifiedBy.Should().Be(userId); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFields() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); - _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); - + _currentUserProvider.GetUserId().Returns(userId, anotherUserId); + var testEntity = new Ngo(string.Empty); await _context.Ngos.AddAsync(testEntity); @@ -103,18 +112,18 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFi testEntity.LastModifiedBy.Should().Be(anotherUserId); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChanges_SetsLastModifiedFields() { //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); - _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); - + _currentUserProvider.GetUserId().Returns(userId, anotherUserId); + var testEntity = new Ngo(string.Empty); await _context.Ngos.AddAsync(testEntity); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs new file mode 100644 index 000000000..45be27e4e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs @@ -0,0 +1,20 @@ +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Form.Module.Requests; + + +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class CreateFormRequest +{ + public Guid Id { get; set; } + public string Code { get; set; } + public TranslatedString Name { get; set; } = new(); + public TranslatedString Description { get; set; } = new(); + public FormType FormType { get; set; } + public List Languages { get; set; } = []; + public string DefaultLanguage { get; set; } + public string Icon { get; set; } + + public List Questions { get; set; } = new(); +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs new file mode 100644 index 000000000..2254fa956 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class FormSubmissionRequest +{ + public Guid PollingStationId { get; set; } + public Guid FormId { get; set; } + + public List? Answers { get; set; } + public bool? IsCompleted { get; set; } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/QuickReportRequest.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/QuickReportRequest.cs new file mode 100644 index 000000000..1677e0268 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/QuickReportRequest.cs @@ -0,0 +1,14 @@ +using Vote.Monitor.Domain.Entities.QuickReportAggregate; + +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class QuickReportRequest +{ + public Guid Id { get; set; } + public QuickReportLocationType QuickReportLocationType =>QuickReportLocationType.VisitedPollingStation; + public IncidentCategory IncidentCategory => IncidentCategory.Other; + + public string Title { get; set; } + public string Description { get; set; } + public Guid PollingStationId { set; get; } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs new file mode 100644 index 000000000..358b438cc --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs @@ -0,0 +1,6 @@ +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class ResponseWithId +{ + public Guid Id { get; set; } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs deleted file mode 100644 index 0a4368d9b..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests; -/// -/// No operation data seeder. -/// -public class NoopDataSeeder : IDataSeeder -{ - public Task SeedDataAsync() - { - return Task.CompletedTask; - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs new file mode 100644 index 000000000..ed8fd6f98 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs @@ -0,0 +1,42 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class CoalitionFormScenarioBuilder +{ + private readonly CoalitionScenarioBuilder _parentBuilder; + private readonly CreateFormRequest _form; + + private readonly Dictionary _submissions = new Dictionary(); + + internal CoalitionFormScenarioBuilder( + CoalitionScenarioBuilder parentBuilder, + CreateFormRequest form) + { + _parentBuilder = parentBuilder; + _form = form; + } + + public CoalitionFormScenarioBuilder WithSubmission(ScenarioObserver observer, ScenarioPollingStation pollingStation) + { + var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStation); + var submission = new SubmissionFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observerClient = _parentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); + + var submissionId = observerClient.PostWithResponse( + $"/api/election-rounds/{_parentBuilder.ParentBuilder.ElectionRoundId}/form-submissions", + submission).Id; + + _submissions.Add($"{observer}_{pollingStation}", submissionId); + return this; + } + + public Guid GetSubmissionId(ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _submissions[$"{observer}_{pollingStation}"]; + + public Guid FormId => _form.Id; + public CreateFormRequest Form => _form; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs new file mode 100644 index 000000000..1e735a667 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs @@ -0,0 +1,78 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class CoalitionScenarioBuilder +{ + private readonly HttpClient _coalitionLeaderAdminAdmin; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly CoalitionModel _coalition; + private readonly Dictionary _forms = new(); + + public CoalitionScenarioBuilder(HttpClient coalitionLeaderAdmin, + ElectionRoundScenarioBuilder parentBuilder, + CoalitionModel coalition) + { + _coalitionLeaderAdminAdmin = coalitionLeaderAdmin; + ParentBuilder = parentBuilder; + _coalition = coalition; + } + + public Guid CoalitionId => _coalition.Id; + + public CoalitionScenarioBuilder WithForm(string? formCode = null, + ScenarioNgo[]? sharedWithMembers = null, + Action? cfg = null) + { + sharedWithMembers ??= Array.Empty(); + formCode ??= Guid.NewGuid().ToString(); + + var formRequest = Dummy.Form(formCode); + var ngoForm = + _coalitionLeaderAdminAdmin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + _coalitionLeaderAdminAdmin + .PostWithoutResponse($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish"); + + var members = sharedWithMembers.Select(member => ParentBuilder.ParentBuilder.NgoIdByName(member)) + .ToList(); + _coalitionLeaderAdminAdmin.PutWithoutResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/coalitions/{CoalitionId}/forms/{ngoForm.Id}:access", + new { NgoMembersIds = members }); + + var coalitionFormScenarioBuilder = new CoalitionFormScenarioBuilder(this, ngoForm); + cfg?.Invoke(coalitionFormScenarioBuilder); + + _forms.Add(formCode, coalitionFormScenarioBuilder); + + return this; + } + + public CoalitionScenarioBuilder WithMonitoringObserver(ScenarioNgo ngo, ScenarioObserver observer) + { + ParentBuilder.ParentBuilder.ElectionRound.MonitoringNgoByName(ngo).WithMonitoringObserver(observer); + + return this; + } + + + public CoalitionScenarioBuilder WithQuickReport(ScenarioObserver observer, ScenarioPollingStation pollingStation) + { + var observerClient = ParentBuilder.WithQuickReport(observer, pollingStation); + return this; + } + + + + public CreateFormRequest Form => _forms.First().Value.Form; + public CoalitionFormScenarioBuilder FormData => _forms.First().Value; + public Guid FormId => _forms.First().Value.FormId; + public CreateFormRequest FormByCode(string formCode) => _forms[formCode].Form; + + public Guid GetSubmissionId(string formCode, ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _forms[formCode].GetSubmissionId(observer, pollingStation); +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs new file mode 100644 index 000000000..f18e6f8c8 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs @@ -0,0 +1,184 @@ +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.FormBase.Questions; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class Dummy +{ + public static CreateFormRequest Form(string code) => new() + { + Id = Guid.Empty, + Code = code, + DefaultLanguage = "RO", + Languages = new List { "RO", "EN" }, + Name = new TranslatedString { { "EN", code }, { "RO", code } }, + Description = new TranslatedString { { "EN", code }, { "RO", code } }, + FormType = FormType.Opening, + Questions = + [ + new NumberQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A1", + Text = new TranslatedString + { + { "EN", "How many PEC members have been appointed" }, + { "RO", "Câți membri PEC au fost numiți" } + }, + Helptext = new TranslatedString + { + { "EN", "Please enter a number" }, { "RO", "Vă rugăm să introduceți numărul dvs" } + }, + InputPlaceholder = new TranslatedString { { "EN", "number" }, { "RO", "numar" } } + }, + new TextQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A2", + Text = new TranslatedString { { "EN", "How are you today" }, { "RO", "Cum te simți azi" } }, + Helptext = new TranslatedString + { + { "EN", "Please enter how are you" }, { "RO", "Vă rugăm să introduceți cum sunteți" } + }, + InputPlaceholder = new TranslatedString { { "EN", "mood" }, { "RO", "dispozitie" } } + }, + new DateQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A3", + Text = new TranslatedString { { "EN", "Time of arrival" }, { "RO", "Timpul sosirii" } }, + Helptext = new TranslatedString + { + { "EN", "Please enter exact hour when did you arrive" }, + { "RO", "Vă rugăm să introduceți ora exactă când ați sosit" } + } + }, + new RatingQuestionRequest + { + Id = Guid.NewGuid(), + Code = "C1", + Text = new TranslatedString + { + { "EN", "Please rate this form" }, { "RO", "Vă rugăm să evaluați acest formular" } + }, + Helptext = new TranslatedString + { + { "EN", "Please give us a rating" }, { "RO", "Vă rugăm să ne dați o evaluare" } + }, + Scale = RatingScale.OneTo10 + }, + new SingleSelectQuestionRequest + { + Id = Guid.NewGuid(), + Code = "B1", + Text = + new TranslatedString + { + { "EN", "The overall conduct of the opening of this PS was:" }, + { "RO", "Conducerea generală a deschiderii acestui PS a fost:" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select a single option" }, + { "RO", "Vă rugăm să selectați o singură opțiune" } + }, + Options = new List + { + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Very good" }, { "RO", "Foarte bun" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Good" }, { "RO", "bun" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bad" }, { "RO", "Rea" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Very bad" }, { "RO", "Foarte rea" } }, + IsFreeText = false, + IsFlagged = true + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Alta" } }, + IsFreeText = true, + IsFlagged = true + } + } + }, + new MultiSelectQuestionRequest + { + Id = Guid.NewGuid(), + Code = "B2", + Text = + new TranslatedString + { + { "EN", "What party/bloc proxies were present at the opening of this PS" }, + { "RO", "Ce împuterniciri de partid/bloc au fost prezenți la deschiderea acestui PS" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select as many you want" }, { "RO", "Vă rugăm să selectați câte doriți" } + }, + Options = new List + { + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 1" }, { "RO", "Bloc 1" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 2" }, { "RO", "Bloc 2" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 3" }, { "RO", "Bloc 3" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Party 1" }, { "RO", "Party 1" } }, + IsFreeText = false, + IsFlagged = true + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Other" } }, + IsFreeText = true, + IsFlagged = true + } + } + } + ] + }; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs new file mode 100644 index 000000000..b815bde87 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs @@ -0,0 +1,129 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; +using ListMonitoringNgos = Feature.Monitoring.List.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ElectionRoundScenarioBuilder +{ + public Guid ElectionRoundId { get; } + + private readonly Dictionary _pollingStations = new(); + private readonly Dictionary _monitoringNgos = new(); + private readonly Dictionary _coalitions = new(); + private readonly Dictionary _quickReports = new(); + + private readonly HttpClient _platformAdmin; + public readonly ScenarioBuilder ParentBuilder; + + public ElectionRoundScenarioBuilder WithPollingStation(ScenarioPollingStation pollingStation) + { + var createdPollingStation = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/polling-stations", + new + { + Level1 = Guid.NewGuid().ToString(), + Number = "1", + DisplayOrder = 1, + Address = "Address", + Tags = new { } + }); + + _pollingStations[pollingStation] = createdPollingStation.Id; + return this; + } + + public ElectionRoundScenarioBuilder(ScenarioBuilder parentBuilder, + Guid electionRoundId, + HttpClient platformAdmin) + { + ParentBuilder = parentBuilder; + ElectionRoundId = electionRoundId; + _platformAdmin = platformAdmin; + } + + public ElectionRoundScenarioBuilder WithMonitoringNgo(ScenarioNgo ngo, + Action? cfg = null) + { + var monitoringNgo = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/monitoring-ngos", + new { ngoId = ParentBuilder.NgoIdByName(ngo) }); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ParentBuilder.NgoByName(ngo)); + + cfg?.Invoke(monitoringNgoScenarioBuilder); + + _monitoringNgos.Add(ngo, monitoringNgoScenarioBuilder); + + return this; + } + + public ElectionRoundScenarioBuilder WithCoalition(ScenarioCoalition name, ScenarioNgo leader, ScenarioNgo[] members, + Action? cfg = null) + { + var coalition = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/coalitions", + new + { + CoalitionName = name + Guid.NewGuid().ToString(), + LeaderId = ParentBuilder.NgoIdByName(leader), + NgoMembersIds = members.Select(member => ParentBuilder.NgoIdByName(member)).ToArray(), + }); + + var response = + _platformAdmin.GetResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos"); + + foreach (var monitoringNgo in response.MonitoringNgos) + { + var ngo = ParentBuilder.NgoById(monitoringNgo.NgoId); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ngo.builder); + + + _monitoringNgos.TryAdd(ngo.ngo, monitoringNgoScenarioBuilder); + } + + var coalitionScenarioBuilder = + new CoalitionScenarioBuilder(ParentBuilder.NgoByName(leader).Admin, this, coalition); + cfg?.Invoke(coalitionScenarioBuilder); + + _coalitions.Add(name, coalitionScenarioBuilder); + return this; + } + + public ElectionRoundScenarioBuilder WithQuickReport(ScenarioObserver observer, + ScenarioPollingStation pollingStation) + { + var observerClient = ParentBuilder.ClientFor(observer); + var pollingStationId = PollingStationByName(pollingStation); + + var quickReport = observerClient.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/quick-reports", + new QuickReportRequestFaker(pollingStationId).Generate()); + + _quickReports.Add($"{observer}_{pollingStation}", quickReport.Id); + return this; + } + + public MonitoringNgoScenarioBuilder MonitoringNgo => _monitoringNgos.First().Value; + public MonitoringNgoScenarioBuilder MonitoringNgoByName(ScenarioNgo ngo) => _monitoringNgos[ngo]; + public Guid MonitoringNgoIdByName(ScenarioNgo ngo) => _monitoringNgos[ngo].MonitoringNgoId; + public CoalitionScenarioBuilder Coalition => _coalitions.First().Value; + public Guid CoalitionId => _coalitions.First().Value.CoalitionId; + public CoalitionScenarioBuilder CoalitionByName(ScenarioCoalition coalition) => _coalitions[coalition]; + public Guid CoalitionIdByName(ScenarioCoalition coalition) => _coalitions[coalition].CoalitionId; + + public Guid PollingStation => _pollingStations.First().Value; + + public Guid PollingStationByName(ScenarioPollingStation pollingStation) => _pollingStations[pollingStation]; + + public Guid GetQuickReportId(ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _quickReports[$"{observer}_{pollingStation}"]; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs new file mode 100644 index 000000000..3d953f561 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -0,0 +1,36 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class MonitoringNgoFormScenarioBuilder +{ + public readonly MonitoringNgoScenarioBuilder ParentBuilder; + + private readonly CreateFormRequest _form; + public Guid FormId => _form.Id; + public CreateFormRequest Form => _form; + + public MonitoringNgoFormScenarioBuilder( + MonitoringNgoScenarioBuilder parentBuilder, + CreateFormRequest form) + { + ParentBuilder = parentBuilder; + _form = form; + } + + public MonitoringNgoFormScenarioBuilder WithSubmission(ScenarioObserver observer, + ScenarioPollingStation pollingStation) + { + var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStation); + var submission = new SubmissionFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observerClient = ParentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); + + observerClient.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/form-submissions", + submission); + return this; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs new file mode 100644 index 000000000..2ed81015c --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -0,0 +1,76 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class MonitoringNgoScenarioBuilder +{ + public Guid ElectionRoundId { get; } + private readonly Dictionary _forms = new(); + private readonly Dictionary _monitoringObservers = new(); + public readonly Guid MonitoringNgoId; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly HttpClient _platformAdmin; + public NgoScenarioBuilder NgoScenario { get; } + public Guid FormId => _forms.First().Value.FormId; + public CreateFormRequest Form => _forms.First().Value.Form; + public MonitoringNgoFormScenarioBuilder FormData => _forms.First().Value; + + public (Guid ObserverId, Guid MonitoringObserverId , HttpClient Client, string FullName, string Email, string PhoneNumber) Observer => _monitoringObservers.First().Value; + + public (Guid ObserverId, Guid MonitoringObserverId, HttpClient Client, string DisplayName, string Email, string PhoneNumber) ObserverByName(ScenarioObserver name) => + _monitoringObservers[name]; + + public MonitoringNgoScenarioBuilder(Guid electionRoundId, + Guid monitoringNgoId, + ElectionRoundScenarioBuilder parentBuilder, + HttpClient platformAdmin, + NgoScenarioBuilder ngoScenario) + { + ElectionRoundId = electionRoundId; + MonitoringNgoId = monitoringNgoId; + ParentBuilder = parentBuilder; + _platformAdmin = platformAdmin; + NgoScenario = ngoScenario; + } + + public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, + Action? cfAction = null) + { + formCode ??= Guid.NewGuid().ToString(); + var formRequest = Dummy.Form(formCode); + var admin = NgoScenario.Admin; + + var ngoForm = admin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + admin + .PostWithoutResponse($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish"); + + var monitoringNgoFormScenarioBuilder = new MonitoringNgoFormScenarioBuilder(this, ngoForm); + cfAction?.Invoke(monitoringNgoFormScenarioBuilder); + + _forms.Add(formCode, monitoringNgoFormScenarioBuilder); + return this; + } + + public MonitoringNgoScenarioBuilder WithMonitoringObserver(ScenarioObserver observer) + { + var observerClient = ParentBuilder.ParentBuilder.ClientFor(observer); + var observerId = ParentBuilder.ParentBuilder.IdFor(observer); + var fullName = ParentBuilder.ParentBuilder.FullNameFor(observer); + var email = ParentBuilder.ParentBuilder.EmailFor(observer); + var phone = ParentBuilder.ParentBuilder.PhoneNumberFor(observer); + + var monitoringObserver = _platformAdmin + .PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{MonitoringNgoId}/monitoring-observers" + , new { observerId = observerId }); + + _monitoringObservers.Add(observer, (observerId,monitoringObserver.Id, observerClient, fullName, email,phone)); + return this; + } + + +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs new file mode 100644 index 000000000..35dbbe8d6 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs @@ -0,0 +1,37 @@ +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class NgoScenarioBuilder +{ + private readonly Dictionary _admins = new(); + private readonly HttpClient _platformAdmin; + private readonly Func _clientFactory; + public Guid NgoId { get; } + + public NgoScenarioBuilder(HttpClient platformAdmin, + Func clientFactory, + Guid ngoId) + { + _platformAdmin = platformAdmin; + _clientFactory = clientFactory; + NgoId = ngoId; + } + + public NgoScenarioBuilder WithAdmin(string? adminEmail = null) + { + adminEmail ??= Guid.NewGuid().ToString("N"); + var realEmail = $"{Guid.NewGuid()}@example.org"; + _platformAdmin.PostWithResponse($"/api/ngos/{NgoId}/admins", + new { FirstName = "Observer", LastName = adminEmail, Email = realEmail, Password = "string" }); + + var adminClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + _admins.Add(adminEmail, adminClient); + return this; + } + + public HttpClient Admin => _admins.First().Value; + + public HttpClient AdminByName(string name) => _admins[name]; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs new file mode 100644 index 000000000..78eac423e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs @@ -0,0 +1,118 @@ +using Vote.Monitor.Api.Feature.Ngo; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Domain.Constants; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ScenarioBuilder +{ + public HttpClient PlatformAdmin { get; } + private readonly Func _clientFactory; + private readonly Dictionary _electionRounds = new(); + private readonly Dictionary _ngos = new(); + private readonly Dictionary _observers = new(); + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + + public string ObserverEmail => _observers.First().Value.Email; + public string ObserverFullName => _observers.First().Value.FullName; + public string ObserverPhoneNumber => _observers.First().Value.PhoneNumber; + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public string FullNameFor(ScenarioObserver observer) => _observers[observer].FullName; + public HttpClient ClientFor(ScenarioObserver observer) => _observers[observer].Client; + public Guid IdFor(ScenarioObserver observer) => _observers[observer].Id; + public string EmailFor(ScenarioObserver observer)=> _observers[observer].Email; + public string PhoneNumberFor(ScenarioObserver observer)=> _observers[observer].PhoneNumber; + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + + public (ScenarioNgo ngo, NgoScenarioBuilder builder) NgoById(Guid ngoId) + { + var ngo = _ngos.First(x => x.Value.NgoId == ngoId); + + return (ngo.Key, ngo.Value); + } + + public Guid NgoIdByName(ScenarioNgo ngo) => _ngos[ngo].NgoId; + + public static ScenarioBuilder New(Func clientFactory) + { + return new ScenarioBuilder(clientFactory); + } + + private ScenarioBuilder(Func clientFactory) + { + PlatformAdmin = clientFactory.NewForAuthenticatedUser(CustomWebApplicationFactory.AdminEmail, + CustomWebApplicationFactory.AdminPassword); + _clientFactory = clientFactory; + } + + public ScenarioBuilder WithNgo(ScenarioNgo ngo, Action? cfg = null) + { + var createdNgo = + PlatformAdmin.PostWithResponse("/api/ngos", new { name = $"{ngo}-{Guid.NewGuid()}"}); + + var ngoScenarioBuilder = new NgoScenarioBuilder(PlatformAdmin, _clientFactory, createdNgo.Id); + ngoScenarioBuilder.WithAdmin(); + + cfg?.Invoke(ngoScenarioBuilder); + _ngos.Add(ngo, ngoScenarioBuilder); + return this; + } + + public ScenarioBuilder WithElectionRound(ScenarioElectionRound electionRound, + Action? cfg = null) + { + var title = "ER" + electionRound + Guid.NewGuid(); + var createdElectionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + new + { + countryId = CountriesList.RO.Id, + title = title, + englishTitle = title, + StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddYears(100) + }); + + + var electionRoundScenarioBuilder = + new ElectionRoundScenarioBuilder(this, createdElectionRound.Id, PlatformAdmin); + _electionRounds.Add(electionRound, electionRoundScenarioBuilder); + + cfg?.Invoke(electionRoundScenarioBuilder); + + return this; + } + + public ScenarioBuilder WithObserver(ScenarioObserver observer) + { + var realEmail = $"{Guid.NewGuid()}@example.org"; + var lastName = observer + "-" + Guid.NewGuid(); + var phoneNumber = Guid.NewGuid().ToString("N"); + + var createdObserver = PlatformAdmin.PostWithResponse("/api/observers", + new { FirstName = "Observer", LastName = lastName, Email = realEmail, Password = "string" , PhoneNumber= phoneNumber}); + + var observerClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + _observers.Add(observer, (createdObserver.Id, observerClient, $"Observer {lastName}", realEmail, phoneNumber)); + return this; + } + + public ScenarioData Please() + { + return new(PlatformAdmin, _electionRounds, _ngos, _observers); + } + + +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs new file mode 100644 index 000000000..4812513df --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs @@ -0,0 +1,46 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ScenarioData +{ + public HttpClient PlatformAdmin { get; } + private readonly IReadOnlyDictionary _electionRounds; + private readonly IReadOnlyDictionary _ngos; + + private readonly IReadOnlyDictionary _observers; + + public ScenarioData(HttpClient platformAdmin, + IReadOnlyDictionary electionRounds, + IReadOnlyDictionary ngos, + IReadOnlyDictionary observers) + { + PlatformAdmin = platformAdmin; + _electionRounds = electionRounds; + _ngos = ngos; + _observers = observers; + } + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public HttpClient ObserverByName(ScenarioObserver observer) => _observers[observer].Client; + public Guid ObserverIdByName(ScenarioObserver observer) => _observers[observer].Id; + + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + public HttpClient AdminOfNgo(ScenarioNgo name) => _ngos[name].Admin; + public Guid NgoIdByName(ScenarioNgo name) => _ngos[name].NgoId; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs new file mode 100644 index 000000000..7b75da5ba --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs @@ -0,0 +1,13 @@ +using System.Collections; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.TestCases; + +public class DataSourcesTestCases : IEnumerable +{ + public IEnumerator GetEnumerator() + { + yield return new object[] { DataSource.Ngo }; + yield return new object[] { DataSource.Coalition }; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj index 100f4a6dc..b6c9eca76 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj @@ -9,16 +9,48 @@ - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + PreserveNewest + + + + + + ..\..\..\..\..\.nuget\packages\nsubstitute\5.1.0\lib\net6.0\NSubstitute.dll + diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json b/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json new file mode 100644 index 000000000..654a3c10f --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=vote-monitor;Username=postgres;Password=docker;Include Error Detail=True" + } +} diff --git a/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs b/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs index 679369ed2..7986b1cf5 100644 --- a/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs +++ b/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs @@ -7,13 +7,13 @@ public class LanguagesTranslationStatusTests private readonly LanguagesTranslationStatus _first = new LanguagesTranslationStatus { ["Ro"] = TranslationStatus.Translated, - ["En"] = TranslationStatus.MissingTranslations, + ["En"] = TranslationStatus.MissingTranslations }; private readonly LanguagesTranslationStatus _second = new LanguagesTranslationStatus { ["Ro"] = TranslationStatus.Translated, - ["En"] = TranslationStatus.MissingTranslations, + ["En"] = TranslationStatus.MissingTranslations }; diff --git a/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs b/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs index 5fa9dfe60..612a7cc05 100644 --- a/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs +++ b/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs @@ -40,7 +40,7 @@ public void ComparingToATranslatedString_WithDifferentProperties_ReturnsFalse(Tr new object[] { new TranslatedString { { "EN", "Some text" }, { "RO", "Other text" } }, new TranslatedString { { "EN", "Some text" }, { "RO", "Other different text" } } }, new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "EN", "Some text" }, { "RO", "Other different text" } } }, new object[] { new TranslatedString { { "EN", "Some text" }, { "RO", "Other text" } }, new TranslatedString { { "EN", "Some text" } } }, - new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "RO", "Other different text" } } }, + new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "RO", "Other different text" } } } }; } diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs index d0b7998df..0a74cae0a 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs @@ -99,6 +99,6 @@ public void CreatePollingStationInformation_ShouldUpdateTimeOfStays_Correctly( { ValueOrUndefined.Some(DateTime.Now), ValueOrUndefined.Some(DateTime.Now.AddDays(3)), Answers, new List() - }, + } }; } \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs index c0cc52792..233d7ea93 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs @@ -80,7 +80,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForEachQuestion() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, @@ -133,7 +133,7 @@ public void WhenAddingTranslations_ThenRecomputesLanguagesTranslationsStatus() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs index dfd65f12c..4e9b2147e 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs @@ -1,6 +1,6 @@ using FluentValidation; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormAggregate; @@ -10,7 +10,7 @@ public partial class FormTests public void Clone_ShouldReturnClonedForm_WhenStatusIsPublishedAndLanguagesAreValid() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -50,7 +50,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() { // Arrange // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -81,7 +81,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -114,7 +114,7 @@ public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupporte public void Clone_ShouldThrowValidationException_WhenOneOrMoreLanguagesAreUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs index fc069a002..7de59d886 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs @@ -13,7 +13,7 @@ public void WhenDuplicate_ThenCreatesNewDraftedFormTemplate() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs index 71065f187..f55435635 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs @@ -63,7 +63,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForEachQuestion() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, @@ -112,7 +112,7 @@ public void WhenRemovingTranslation_ThenRecomputesLanguagesTranslationsStatus() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs index 411185ff1..c9e7439aa 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs @@ -54,7 +54,7 @@ static FormTestsTestData() private static readonly SelectOption[] Options = [ new(Guid.NewGuid(), Option1Text, false, false), - new(Guid.NewGuid(), Option2Text, false, true), + new(Guid.NewGuid(), Option2Text, false, true) ]; private static readonly TextQuestion PartiallyTranslatedTextQuestion = @@ -92,6 +92,6 @@ static FormTestsTestData() PartiallyTranslatedSingleSelectQuestion, PartiallyTranslatedMultiSelectQuestion } - }, + } }; } \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs index 1c922c429..674b26dfb 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs @@ -26,7 +26,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), + SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()) ]; List multiSelectOptions = @@ -34,7 +34,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(regularOptionId, new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true), + SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true) ]; var singleSelectQuestion = @@ -44,7 +44,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() var questions = new BaseQuestion[] { singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -59,8 +59,8 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() new MultiSelectAnswer(multiSelectQuestion.Id, [ SelectedOption.Create(flaggedOptionId1, ""), SelectedOption.Create(flaggedOptionId2, ""), - SelectedOption.Create(regularOptionId, ""), - ]), + SelectedOption.Create(regularOptionId, "") + ]) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, answers, false); @@ -88,7 +88,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), + SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()) ]; List multiSelectOptions = @@ -96,7 +96,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(regularOptionId, new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true), + SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true) ]; var singleSelectQuestion = @@ -106,7 +106,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() var questions = new BaseQuestion[] { singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -127,8 +127,8 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() new MultiSelectAnswer(multiSelectQuestion.Id, [ SelectedOption.Create(flaggedOptionId1, ""), SelectedOption.Create(flaggedOptionId2, ""), - SelectedOption.Create(regularOptionId, ""), - ]), + SelectedOption.Create(regularOptionId, "") + ]) ]; form.FillIn(submission, updatedAnswers, false); @@ -161,7 +161,7 @@ public void WhenCreatingSubmission_ShouldCountQuestionsAnswered() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -175,7 +175,7 @@ public void WhenCreatingSubmission_ShouldCountQuestionsAnswered() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; // Act @@ -209,7 +209,7 @@ public void WhenFillingInSubmission_ShouldUpdateQuestionsAnswered() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -231,7 +231,7 @@ public void WhenFillingInSubmission_ShouldUpdateQuestionsAnswered() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; form.FillIn(submission, updatedAnswers, false); @@ -264,7 +264,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldClearAnswers() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -278,7 +278,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldClearAnswers() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, initialAnswers, false); @@ -316,7 +316,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldStayTheSame() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -329,7 +329,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldStayTheSame() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, initialAnswers, false); diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs index 3625aa9f1..8b5f6a5a0 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs @@ -1,7 +1,6 @@ using Vote.Monitor.Core.Helpers; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; @@ -15,7 +14,7 @@ public void WhenAddingTranslations_AndDuplicatedLanguageCodes_ThenOnlyNewLanguag var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; @@ -35,7 +34,7 @@ public void WhenAddingTranslations_AndNoNewLanguages_ThenFormStaysTheSame() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); var formBefore = formTemplate.DeepClone(); @@ -55,7 +54,7 @@ public void WhenAddingTranslations_AndEmptyNewLanguages_ThenFormStaysTheSame() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); var formBefore = formTemplate.DeepClone(); @@ -75,7 +74,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForFormTemplateDetails() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; @@ -103,10 +102,10 @@ public void WhenAddingTranslations_ThenAddsTranslationsForEachQuestion() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs index 87e302e9c..7dfc999d4 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs @@ -1,7 +1,6 @@ using FluentValidation; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests @@ -10,7 +9,7 @@ public partial class FormTests public void Clone_ShouldReturnClonedForm_WhenStatusIsPublishedAndLanguagesAreValid() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -50,7 +49,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() { // Arrange // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -80,7 +79,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -112,7 +111,7 @@ public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupporte public void Clone_ShouldThrowValidationException_WhenOneOrMoreLanguagesAreUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs index 5d115286c..42357cc2a 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs @@ -1,6 +1,5 @@ using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests @@ -19,10 +18,10 @@ public void WhenDuplicate_ThenCreatesNewDraftedFormTemplate() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); formTemplate.Publish(); // Act diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs index fbcbffe53..735872473 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs @@ -1,7 +1,6 @@ using Vote.Monitor.Core.Helpers; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; @@ -15,7 +14,7 @@ public void WhenRemovingTranslation_AndFormTemplateDoesNotHaveIt_ThenFormTemplat var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages,[]); var formBefore = formTemplate.DeepClone(); @@ -35,7 +34,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForFormTemplateDetails var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); // Act @@ -55,7 +54,7 @@ public void WhenRemovingTranslation_AndDefaultLanguageIsRemoved_ThenException() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); // Act @@ -81,10 +80,10 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForEachQuestion() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages,questions); // Act diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs index 4f1cbdece..6374683ca 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs @@ -1,8 +1,4 @@ -using Vote.Monitor.Core.Helpers; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; -using Vote.Monitor.TestUtils.Fakes.Aggregates; - -namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; +namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests { diff --git a/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs b/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs index 2cea57b81..4589667ad 100644 --- a/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs +++ b/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs @@ -79,7 +79,7 @@ public QuestionsMapperTests() new(Guid.NewGuid(), _option1Text, false, false), new(Guid.NewGuid(), _option2Text, false, true), new(Guid.NewGuid(), _option3Text, true, false), - new(Guid.NewGuid(), _option4Text, true, true), + new(Guid.NewGuid(), _option4Text, true, true) ]; _requestOptions = @@ -111,7 +111,7 @@ public QuestionsMapperTests() Text = _option4Text, IsFlagged = true, IsFreeText = true - }, + } ]; } @@ -294,7 +294,7 @@ public void ToEntity_ShouldReturnNumberQuestion_WhenGivenDateQuestionModel() Id = Guid.NewGuid(), Code = _code, Text = _text, - Helptext = _helptext, + Helptext = _helptext }; // Act var result = QuestionsMapper.ToEntity(dateQuestionRequest); diff --git a/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs b/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs index 4a54412e2..23d175026 100644 --- a/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs +++ b/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs @@ -64,7 +64,7 @@ public class ValidatorsTestData new List { new object?[] { new DisplayLogicRequest{ParentQuestionId= Guid.NewGuid(), Condition = DisplayLogicCondition.GreaterEqual, Value = "1"} }, - new object?[] { null }, + new object?[] { null } }; diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.IncidentReports.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.IncidentReports.cs index c3a03ac65..6fb8973f0 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.IncidentReports.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.IncidentReports.cs @@ -52,8 +52,8 @@ private static IncidentReportModel GenerateIncidentReport( x.LocationDescription = f.Lorem.Sentence(10); } }) - .RuleFor(x => x.FirstName, f => f.Name.FirstName()) - .RuleFor(x => x.LastName, f => f.Name.LastName()) + .RuleFor(x => x.NgoName, f => f.Company.CompanyName()) + .RuleFor(x => x.DisplayName, f => f.Name.FirstName()) .RuleFor(x => x.Email, f => f.Internet.Email()) .RuleFor(x => x.PhoneNumber, f => f.Phone.PhoneNumber()) .RuleFor(x => x.MonitoringObserverId, f => f.Random.Guid()) @@ -183,4 +183,4 @@ public static IncidentReportModel PartialIncidentReport(Guid formId, return GenerateIncidentReport(formId, locationType, answers.ToArray(), notes.ToArray(), attachments.ToArray()); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.QuickReport.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.QuickReport.cs index e7446edd9..4f29a532f 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.QuickReport.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.QuickReport.cs @@ -12,8 +12,8 @@ public static QuickReportModel QuickReport(Guid quickReportId, QuickReportLocati var fakeQuickReport = new Faker() .RuleFor(x => x.Id, quickReportId) .RuleFor(x => x.Timestamp, f => f.Date.Recent(1, DateTime.UtcNow)) - .RuleFor(x => x.FirstName, f => f.Name.FirstName()) - .RuleFor(x => x.LastName, f => f.Name.LastName()) + .RuleFor(x => x.NgoName, f => f.Company.CompanyName()) + .RuleFor(x => x.DisplayName, f => f.Name.FullName()) .RuleFor(x => x.Email, f => f.Internet.Email()) .RuleFor(x => x.PhoneNumber, f => f.Phone.PhoneNumber()) .RuleFor(x => x.MonitoringObserverId, f => f.Random.Guid()) @@ -46,4 +46,4 @@ public static QuickReportModel QuickReport(Guid quickReportId, QuickReportLocati return fakeQuickReport.Generate(); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.Submission.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.Submission.cs index f46384198..6a7919e9d 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.Submission.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/Fakes/Fake.Submission.cs @@ -28,8 +28,8 @@ private static SubmissionModel GenerateSubmission(Guid formId, BaseAnswer[]? ans .RuleFor(x => x.Level4, f => f.Lorem.Word()) .RuleFor(x => x.Level5, f => f.Lorem.Word()) .RuleFor(x => x.Number, f => f.Lorem.Word()) - .RuleFor(x => x.FirstName, f => f.Name.FirstName()) - .RuleFor(x => x.LastName, f => f.Name.LastName()) + .RuleFor(x => x.NgoName, f => f.Company.CompanyName()) + .RuleFor(x => x.DisplayName, f => f.Name.FullName()) .RuleFor(x => x.Email, f => f.Internet.Email()) .RuleFor(x => x.PhoneNumber, f => f.Phone.PhoneNumber()) .RuleFor(x => x.MonitoringObserverId, f => f.Random.Guid()) @@ -157,4 +157,4 @@ public static SubmissionModel PartialSubmission(Guid formId, return GenerateSubmission(formId, answers.ToArray(), notes.ToArray(), attachments.ToArray()); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/FormSubmissionsDataTableGeneratorTests.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/FormSubmissionsDataTableGeneratorTests.cs index 32aabd5fa..1b3cf898d 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/FormSubmissionsDataTableGeneratorTests.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/FormSubmissionsDataTableGeneratorTests.cs @@ -120,9 +120,9 @@ public class FormSubmissionsDataTableGeneratorTests "Level4", "Level5", "Number", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber", ]; @@ -863,9 +863,9 @@ private object[] GetDefaultExpectedColumns(SubmissionModel submission) submission.Level4, submission.Level5, submission.Number, + submission.NgoName, submission.MonitoringObserverId.ToString(), - submission.FirstName, - submission.LastName, + submission.DisplayName, submission.Email, submission.PhoneNumber, ]; @@ -888,4 +888,4 @@ private SubmissionAttachmentModel[] FakeAttachmentsFor(Guid questionId) new SubmissionAttachmentModel { QuestionId = questionId, PresignedUrl = Attachment2Url }, ]; } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/IncidentReportsDataTableGeneratorTests.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/IncidentReportsDataTableGeneratorTests.cs index 1e4480897..85fe605a3 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/IncidentReportsDataTableGeneratorTests.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/IncidentReportsDataTableGeneratorTests.cs @@ -123,9 +123,9 @@ public class IncidentReportsDataTableGeneratorTests "Level4", "Level5", "Number", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber" ]; @@ -887,9 +887,9 @@ private object[] GetDefaultExpectedColumns(IncidentReportModel incidentReport) incidentReport.Level4 ?? "", incidentReport.Level5 ?? "", incidentReport.Number ?? "", + incidentReport.NgoName, incidentReport.MonitoringObserverId.ToString(), - incidentReport.FirstName, - incidentReport.LastName, + incidentReport.DisplayName, incidentReport.Email, incidentReport.PhoneNumber ]; @@ -912,4 +912,4 @@ private SubmissionAttachmentModel[] FakeAttachmentsFor(Guid questionId) new SubmissionAttachmentModel { QuestionId = questionId, PresignedUrl = Attachment2Url }, ]; } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/QuickReportsDataTableGeneratorTests.cs b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/QuickReportsDataTableGeneratorTests.cs index d20ca0e41..838e387e7 100644 --- a/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/QuickReportsDataTableGeneratorTests.cs +++ b/api/tests/Vote.Monitor.Hangfire.UnitTests/Jobs/ExportData/QuickReportsDataTableGeneratorTests.cs @@ -20,9 +20,9 @@ public class QuickReportsDataTableGeneratorTests "TimeSubmitted", "FollowUpStatus", "IncidentCategory", + "Ngo", "MonitoringObserverId", - "FirstName", - "LastName", + "Name", "Email", "PhoneNumber", "LocationType", @@ -185,9 +185,9 @@ private object[] GetDefaultExpectedColumns(QuickReportModel quickReport) quickReport.Timestamp.ToString("s"), quickReport.FollowUpStatus.Value, quickReport.IncidentCategory.Value, + quickReport.NgoName, quickReport.MonitoringObserverId.ToString(), - quickReport.FirstName, - quickReport.LastName, + quickReport.DisplayName, quickReport.Email, quickReport.PhoneNumber, quickReport.QuickReportLocationType.Value diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs index 33aa865ed..ed3f7d1d7 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs @@ -32,7 +32,7 @@ public FormAggregateFaker(ElectionRoundAggregate? electionRound = null, new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; SelectOption[] multiSelectOptions = @@ -40,7 +40,7 @@ public FormAggregateFaker(ElectionRoundAggregate? electionRound = null, new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; questions ??= diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs index 3cf54b0ec..174980aea 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs @@ -4,7 +4,7 @@ namespace Vote.Monitor.TestUtils.Fakes.Aggregates; -public sealed class FormTemplateAggregateFaker : PrivateFaker +public sealed class FormTemplateAggregateFaker : PrivateFaker { private readonly List _statuses = [FormTemplateStatus.Drafted, FormTemplateStatus.Published]; private readonly DateTime _baseCreationDate = new(2024, 01, 01, 00, 00, 00, DateTimeKind.Utc); diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs index 7b3c5fcd5..899b6a2cd 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs @@ -12,11 +12,13 @@ public MonitoringObserverFaker(ElectionRound? electionRound = null, MonitoringNg ObserverAggregate? observer = null, Guid? id = null, Guid? observerId = null) { UsePrivateConstructor(); - + electionRound ??= new ElectionRoundAggregateFaker().Generate(); monitoringNgo ??= new MonitoringNgoAggregateFaker(electionRound: electionRound).Generate(); observer ??= new ObserverAggregateFaker(observerId).Generate(); RuleFor(fake => fake.Id, fake => id ?? fake.Random.Guid()); + RuleFor(fake => fake.ElectionRoundId, electionRound.Id); + RuleFor(fake => fake.ElectionRound, electionRound); RuleFor(fake => fake.MonitoringNgo, monitoringNgo); RuleFor(fake => fake.MonitoringNgoId, monitoringNgo.Id); RuleFor(fake => fake.Observer, observer); @@ -24,4 +26,4 @@ public MonitoringObserverFaker(ElectionRound? electionRound = null, MonitoringNg RuleFor(fake => fake.Status, fake => fake.PickRandom(_statuses)); RuleFor(fake => fake.Tags, []); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs index 0fc66d2c7..702367132 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs @@ -27,7 +27,7 @@ public PollingStationInformationFormFaker(ElectionRoundAggregate? electionRound SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate()), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; SelectOption[] multiSelectOptions = @@ -35,7 +35,7 @@ public PollingStationInformationFormFaker(ElectionRoundAggregate? electionRound SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate()), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; questions ??= diff --git a/api/tests/Vote.Monitor.TestUtils/TestContext.cs b/api/tests/Vote.Monitor.TestUtils/TestContext.cs index 6af6f317b..5db1e24aa 100644 --- a/api/tests/Vote.Monitor.TestUtils/TestContext.cs +++ b/api/tests/Vote.Monitor.TestUtils/TestContext.cs @@ -1,7 +1,4 @@ using Microsoft.EntityFrameworkCore; -using NSubstitute; -using Vote.Monitor.Core.Services.Security; -using Vote.Monitor.Core.Services.Serialization; using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.FeedbackAggregate; using Vote.Monitor.Domain.Entities.LocationAggregate; @@ -11,11 +8,8 @@ namespace Vote.Monitor.TestUtils; public class TestContext : VoteMonitorContext { - public TestContext(DbContextOptions options, - ISerializerService serializerService, - ITimeProvider timeProvider, - ICurrentUserProvider currentUserProvider) - : base(options, serializerService, timeProvider, currentUserProvider) + public TestContext(DbContextOptions options) + : base(options) { } @@ -32,17 +26,10 @@ protected override void OnModelCreating(ModelBuilder builder) public static TestContext Fake() { - var timeProvider = Substitute.For(); - var serializationService = Substitute.For(); - var currentUserIdProvider = Substitute.For(); - var dbContextOptions = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()); - var context = new TestContext(dbContextOptions.Options, - serializationService, - timeProvider, - currentUserIdProvider); + var context = new TestContext(dbContextOptions.Options); return context; } diff --git a/api/tests/Vote.Monitor.TestUtils/TestData.cs b/api/tests/Vote.Monitor.TestUtils/TestData.cs index 45bff9980..fdc917f74 100644 --- a/api/tests/Vote.Monitor.TestUtils/TestData.cs +++ b/api/tests/Vote.Monitor.TestUtils/TestData.cs @@ -24,6 +24,6 @@ public class TestData new List { new object[] { Guid.Empty }, - new object[] { null as Guid? }, + new object[] { null as Guid? } }; } diff --git a/api/tests/Vote.Monitor.TestUtils/Utils/StringExtensions.cs b/api/tests/Vote.Monitor.TestUtils/Utils/StringExtensions.cs new file mode 100644 index 000000000..615c0fa35 --- /dev/null +++ b/api/tests/Vote.Monitor.TestUtils/Utils/StringExtensions.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.TestUtils.Utils; + +public static class StringExtensions +{ + public static string OfLength(this string input, int desiredLength) + { + if (string.IsNullOrEmpty(input)) + return input; + + // If the input string length is less than the desired length, return the full string + return input.Length <= desiredLength ? input : input.Substring(0, desiredLength); + } +} diff --git a/terraform/locals.tf b/terraform/locals.tf index 3018b5c11..c55fe6161 100644 --- a/terraform/locals.tf +++ b/terraform/locals.tf @@ -5,12 +5,12 @@ locals { images = { api = { image = "commitglobal/votemonitor" - tag = "0.2.31" + tag = "0.2.40" } hangfire = { image = "commitglobal/votemonitor-hangfire" - tag = "0.2.31" + tag = "0.2.40" } } diff --git a/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs b/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs index 68ea7293d..921672cb4 100644 --- a/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs +++ b/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs @@ -8,7 +8,7 @@ public interface ICitizenApi { [Post("/api/election-rounds/{electionRoundId}/citizen-reports")] Task SubmitForm( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [Body] CitizenReportRequest citizenReport); // [Multipart] @@ -21,8 +21,9 @@ Task SubmitForm( // [AliasAs("QuestionId")] string questionId, // [AliasAs("Attachment")] StreamPart attachment); - [Post("/api/election-rounds/{electionRoundId}/citizen-report-notes")] + [Post("/api/election-rounds/{electionRoundId}/citizen-reports/{citizenReportId}/notes")] Task SubmitNote( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, + [AliasAs("citizenReportId")] Guid citizenReportId, [Body] CitizenReportNoteRequest note); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportAttachmentRequest.cs b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportAttachmentRequest.cs index 70b8068a9..7d55aaaa7 100644 --- a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportAttachmentRequest.cs +++ b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportAttachmentRequest.cs @@ -2,9 +2,9 @@ namespace SubmissionsFaker.Clients.Citizen.Models; public class CitizenReportAttachmentRequest { - public string CitizenReportId { get; set; } + public Guid CitizenReportId { get; set; } public Guid Id { set; get; } - public string FormId { get; set; } + public Guid FormId { get; set; } public Guid QuestionId { get; set; } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportNoteRequest.cs b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportNoteRequest.cs index 741e701d7..3aacb2d27 100644 --- a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportNoteRequest.cs +++ b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportNoteRequest.cs @@ -2,8 +2,8 @@ namespace SubmissionsFaker.Clients.Citizen.Models; public class CitizenReportNoteRequest { - public string CitizenReportId { get; set; } - public string FormId { get; set; } + public Guid CitizenReportId { get; set; } + public Guid FormId { get; set; } public Guid QuestionId { get; set; } public Guid Id { get; set; } public string Text { get; set; } = string.Empty; diff --git a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportRequest.cs b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportRequest.cs index b9687a71e..9b620b421 100644 --- a/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportRequest.cs +++ b/utils/SubmissionsFaker/Clients/Citizen/Models/CitizenReportRequest.cs @@ -4,8 +4,8 @@ namespace SubmissionsFaker.Clients.Citizen.Models; public class CitizenReportRequest { - public string CitizenReportId { get; set; } - public string FormId { get; set; } + public Guid CitizenReportId { get; set; } + public Guid FormId { get; set; } public List Answers { get; set; } = []; public Guid LocationId { get; set; } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/Locations/ILocationsApi.cs b/utils/SubmissionsFaker/Clients/Locations/ILocationsApi.cs deleted file mode 100644 index 9c3045825..000000000 --- a/utils/SubmissionsFaker/Clients/Locations/ILocationsApi.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Refit; - -namespace SubmissionsFaker.Clients.Locations; - -public interface ILocationsApi -{ - [Get("/api/election-rounds/{electionRoundId}/locations:fetchAll")] - Task GetAllLocations([AliasAs("electionRoundId")] string electionRoundId, [Authorize] string token); -} \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/Models/CreateResponse.cs b/utils/SubmissionsFaker/Clients/Models/CreateResponse.cs index c4ca884d8..0dff5d184 100644 --- a/utils/SubmissionsFaker/Clients/Models/CreateResponse.cs +++ b/utils/SubmissionsFaker/Clients/Models/CreateResponse.cs @@ -2,5 +2,5 @@ public class CreateResponse { - public string Id { get; set; } + public Guid Id { get; set; } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/IMonitoringObserverApi.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/IMonitoringObserverApi.cs index aee00079d..5e86523a6 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/IMonitoringObserverApi.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/IMonitoringObserverApi.cs @@ -8,14 +8,14 @@ public interface IMonitoringObserverApi { [Post("/api/election-rounds/{electionRoundId}/form-submissions")] Task SubmitForm( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [Body] SubmissionRequest submission, [Authorize] string token); [Multipart] [Post("/api/election-rounds/{electionRoundId}/attachments")] Task SubmitAttachment( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [AliasAs("PollingStationId")] string pollingStationId, [AliasAs("Id")] string id, [AliasAs("FormId")] string formId, @@ -25,20 +25,20 @@ Task SubmitAttachment( [Post("/api/election-rounds/{electionRoundId}/notes")] Task SubmitNote( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [Body] NoteRequest note, [Authorize] string token); [Post("/api/election-rounds/{electionRoundId}/quick-reports")] Task SubmitQuickReport( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [Body] QuickReportRequest quickReport, [Authorize] string token); [Multipart] [Post("/api/election-rounds/{electionRoundId}/quick-reports/{quickReportId}/attachments")] Task SubmitQuickReportAttachment( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [AliasAs("QuickReportId")] string quickReportId, [AliasAs("Id")] string id, [AliasAs("Attachment")] StreamPart attachment, @@ -46,14 +46,14 @@ Task SubmitQuickReportAttachment( [Post("/api/election-rounds/{electionRoundId}/polling-stations/{pollingStationId}/information")] Task SubmitPSIForm( - [AliasAs("electionRoundId")] string electionRoundId, - [AliasAs("pollingStationId")] string pollingStationId, + [AliasAs("electionRoundId")] Guid electionRoundId, + [AliasAs("pollingStationId")] Guid pollingStationId, [Body] PSISubmissionRequest submission, [Authorize] string token); [Post("/api/election-rounds/{electionRoundId}/incident-reports")] Task SubmitIncidentReport( - [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("electionRoundId")] Guid electionRoundId, [Body] IncidentReportRequest incidentReport, [Authorize] string token); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/AttachmentRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/AttachmentRequest.cs index 657a83ce1..fc609eaa6 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/AttachmentRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/AttachmentRequest.cs @@ -6,6 +6,6 @@ public class AttachmentRequest public Guid PollingStationId { get; set; } public Guid Id { set; get; } - public string FormId { get; set; } + public Guid FormId { get; set; } public Guid QuestionId { get; set; } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs index a91670941..74018c0ed 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs @@ -3,8 +3,8 @@ namespace SubmissionsFaker.Clients.MonitoringObserver.Models; public class IncidentReportRequest { public string ObserverToken { get; set; } - public Guid IncidentReportId { get; set; } - public string FormId { get; set; } + public Guid Id { get; set; } + public Guid FormId { get; set; } public string LocationType { get; set; } public Guid? PollingStationId { set; get; } diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/NoteRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/NoteRequest.cs index be082a9e4..416097ae4 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/NoteRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/NoteRequest.cs @@ -4,7 +4,7 @@ public class NoteRequest { public string ObserverToken { get; set; } public Guid PollingStationId { get; set; } - public string FormId { get; set; } + public Guid FormId { get; set; } public Guid QuestionId { get; set; } public Guid Id { get; set; } public string Text { get; set; } = string.Empty; diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/PSISubmissionRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/PSISubmissionRequest.cs index 6d3057032..536425c2b 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/PSISubmissionRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/PSISubmissionRequest.cs @@ -7,7 +7,7 @@ public class PSISubmissionRequest public DateTime DepartureTime { get; set; } public string ObserverToken { get; set; } - public string PollingStationId { get; set; } + public Guid PollingStationId { get; set; } public List Answers { get; set; } = []; diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/SubmissionRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/SubmissionRequest.cs index d1bacca01..0ff88e441 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/SubmissionRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/SubmissionRequest.cs @@ -5,7 +5,7 @@ public class SubmissionRequest public string ObserverToken { get; set; } public Guid PollingStationId { get; set; } - public string FormId { get; set; } + public Guid FormId { get; set; } public List Answers { get; set; } = []; diff --git a/utils/SubmissionsFaker/Clients/NgoAdmin/INgoAdminApi.cs b/utils/SubmissionsFaker/Clients/NgoAdmin/INgoAdminApi.cs index cb80b9ee6..0b71bef87 100644 --- a/utils/SubmissionsFaker/Clients/NgoAdmin/INgoAdminApi.cs +++ b/utils/SubmissionsFaker/Clients/NgoAdmin/INgoAdminApi.cs @@ -7,17 +7,9 @@ namespace SubmissionsFaker.Clients.NgoAdmin; public interface INgoAdminApi { [Post("/api/election-rounds/{electionRoundId}/forms")] - Task CreateForm([AliasAs("electionRoundId")] string electionRoundId, [Body] NewForm form, - [Authorize] string token); - - [Put("/api/election-rounds/{electionRoundId}/forms/{id}")] - Task UpdateForm([AliasAs("electionRoundId")] string electionRoundId, - [AliasAs("id")] string id, - [Body] UpdateForm form, - [Authorize] string token); + Task CreateForm([AliasAs("electionRoundId")] Guid electionRoundId, [Body] NewForm form); [Post("/api/election-rounds/{electionRoundId}/forms/{id}:publish")] - Task PublishForm([AliasAs("electionRoundId")] string electionRoundId, - [AliasAs("id")] string id, - [Authorize] string token); + Task PublishForm([AliasAs("electionRoundId")] Guid electionRoundId, + [AliasAs("id")] Guid id); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateForm.cs b/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateForm.cs index 670082af1..777ff0d1b 100644 --- a/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateForm.cs +++ b/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateForm.cs @@ -5,4 +5,5 @@ namespace SubmissionsFaker.Clients.NgoAdmin.Models; public record UpdateForm : NewForm { public List Questions { get; set; } = new(); + public Guid Id { get; set; } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateFormResponse.cs b/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateFormResponse.cs index 5e6f46c94..8251e4051 100644 --- a/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateFormResponse.cs +++ b/utils/SubmissionsFaker/Clients/NgoAdmin/Models/UpdateFormResponse.cs @@ -5,6 +5,6 @@ namespace SubmissionsFaker.Clients.NgoAdmin.Models; public class UpdateFormResponse : CreateResponse { - public string Id { get; set; } + public Guid Id { get; set; } public List Questions { get; set; } = new(); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/PlatformAdmin/IPlatformAdminApi.cs b/utils/SubmissionsFaker/Clients/PlatformAdmin/IPlatformAdminApi.cs index 892dea342..194026f05 100644 --- a/utils/SubmissionsFaker/Clients/PlatformAdmin/IPlatformAdminApi.cs +++ b/utils/SubmissionsFaker/Clients/PlatformAdmin/IPlatformAdminApi.cs @@ -1,47 +1,51 @@ using Refit; +using SubmissionsFaker.Clients.Locations; using SubmissionsFaker.Clients.Models; using SubmissionsFaker.Clients.PlatformAdmin.Models; +using SubmissionsFaker.Clients.PollingStations; namespace SubmissionsFaker.Clients.PlatformAdmin; public interface IPlatformAdminApi { [Post("/api/election-rounds")] - Task CreateElectionRound([Body] ElectionRound electionRound, [Authorize] string token); + Task CreateElectionRound([Body] ElectionRound electionRound); [Post("/api/ngos")] - Task CreateNgo([Body] Ngo ngo, [Authorize] string token); + Task CreateNgo([Body] Ngo ngo); [Post("/api/ngos/{ngoId}/admins")] - Task CreateNgoAdmin([Body] ApplicationUser ngoAdmin, [AliasAs("ngoId")] string ngoId, [Authorize] string token); + Task CreateNgoAdmin([Body] ApplicationUser ngoAdmin, [AliasAs("ngoId")] Guid ngoId); [Multipart] [Post("/api/election-rounds/{electionRoundId}/polling-stations:import")] - Task ImportPollingStations([AliasAs("electionRoundId")] string electionRoundId, [AliasAs("File")] StreamPart stream, - [Authorize] string token); + Task ImportPollingStations([AliasAs("electionRoundId")] Guid electionRoundId, [AliasAs("File")] StreamPart stream); [Multipart] [Post("/api/election-rounds/{electionRoundId}/locations:import")] - Task ImportLocations([AliasAs("electionRoundId")] string electionRoundId, [AliasAs("File")] StreamPart stream, - [Authorize] string token); + Task ImportLocations([AliasAs("electionRoundId")] Guid electionRoundId, [AliasAs("File")] StreamPart stream); [Post("/api/observers")] - Task CreateObserver([Body] ApplicationUser observer, [Authorize] string token); + Task CreateObserver([Body] ApplicationUser observer); [Post("/api/election-rounds/{electionRoundId}/monitoring-ngos")] - Task AssignNgoToElectionRound([AliasAs("electionRoundId")] string electionRoundId, - [Body] AssignNgoRequest request, [Authorize] string token); + Task AssignNgoToElectionRound([AliasAs("electionRoundId")] Guid electionRoundId, + [Body] AssignNgoRequest request); [Post("/api/election-rounds/{electionRoundId}:enableCitizenReporting")] - Task EnableCitizenReporting([AliasAs("electionRoundId")] string electionRoundId, - [Body] EnableCitizenReportingRequest request, [Authorize] string token); + Task EnableCitizenReporting([AliasAs("electionRoundId")] Guid electionRoundId, + [Body] EnableCitizenReportingRequest request); [Post("/api/election-rounds/{electionRoundId}/monitoring-ngos/{monitoringNgoId}/monitoring-observers")] - Task AssignObserverToMonitoring([AliasAs("electionRoundId")] string electionRoundId, - [AliasAs("monitoringNgoId")] string monitoringNgoId, [Body] AssignObserverRequest request, - [Authorize] string token); + Task AssignObserverToMonitoring([AliasAs("electionRoundId")] Guid electionRoundId, + [AliasAs("monitoringNgoId")] Guid monitoringNgoId, [Body] AssignObserverRequest request); [Post("/api/election-rounds/{electionRoundId}/polling-station-information-form")] - Task CreatePSIForm([AliasAs("electionRoundId")] string electionRoundId, [Body] UpsertPSIFormRequest psiFormRequest, - [Authorize] string token); + Task CreatePSIForm([AliasAs("electionRoundId")] Guid electionRoundId, [Body] UpsertPSIFormRequest psiFormRequest); + + [Get("/api/election-rounds/{electionRoundId}/polling-stations:fetchAll")] + Task GetAllPollingStations([AliasAs("electionRoundId")] Guid electionRoundId); + + [Get("/api/election-rounds/{electionRoundId}/locations:fetchAll")] + Task GetAllLocations([AliasAs("electionRoundId")] Guid electionRoundId); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/AssignNgoRequest.cs b/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/AssignNgoRequest.cs index 5dd25c940..68c55f127 100644 --- a/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/AssignNgoRequest.cs +++ b/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/AssignNgoRequest.cs @@ -1,4 +1,4 @@ namespace SubmissionsFaker.Clients.PlatformAdmin.Models; -public record AssignNgoRequest(string NgoId); -public record AssignObserverRequest(string ObserverId); \ No newline at end of file +public record AssignNgoRequest(Guid NgoId); +public record AssignObserverRequest(Guid ObserverId); \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/EnableCitizenReportingRequest.cs b/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/EnableCitizenReportingRequest.cs index f79cd68e4..5ea943ca8 100644 --- a/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/EnableCitizenReportingRequest.cs +++ b/utils/SubmissionsFaker/Clients/PlatformAdmin/Models/EnableCitizenReportingRequest.cs @@ -1,3 +1,3 @@ namespace SubmissionsFaker.Clients.PlatformAdmin.Models; -public record EnableCitizenReportingRequest(string NgoId); \ No newline at end of file +public record EnableCitizenReportingRequest(Guid NgoId); \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/PollingStations/IPollingStationsApi.cs b/utils/SubmissionsFaker/Clients/PollingStations/IPollingStationsApi.cs deleted file mode 100644 index 7a41c1393..000000000 --- a/utils/SubmissionsFaker/Clients/PollingStations/IPollingStationsApi.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Refit; - -namespace SubmissionsFaker.Clients.PollingStations; - -public interface IPollingStationsApi -{ - [Get("/api/election-rounds/{electionRoundId}/polling-stations:fetchAll")] - Task GetAllPollingStations([AliasAs("electionRoundId")] string electionRoundId, [Authorize] string token); -} \ No newline at end of file diff --git a/utils/SubmissionsFaker/Consts/ScenarioCoalition.cs b/utils/SubmissionsFaker/Consts/ScenarioCoalition.cs new file mode 100644 index 000000000..dfaecb34c --- /dev/null +++ b/utils/SubmissionsFaker/Consts/ScenarioCoalition.cs @@ -0,0 +1,7 @@ +namespace SubmissionsFaker.Consts; + +public enum ScenarioCoalition +{ + Youth, + Old, +} diff --git a/utils/SubmissionsFaker/Consts/ScenarioElectionRound.cs b/utils/SubmissionsFaker/Consts/ScenarioElectionRound.cs new file mode 100644 index 000000000..4660d8701 --- /dev/null +++ b/utils/SubmissionsFaker/Consts/ScenarioElectionRound.cs @@ -0,0 +1,9 @@ +namespace SubmissionsFaker.Consts; + +public enum ScenarioElectionRound +{ + A, + B, + C, + D, +} diff --git a/utils/SubmissionsFaker/Consts/ScenarioNgos.cs b/utils/SubmissionsFaker/Consts/ScenarioNgos.cs new file mode 100644 index 000000000..c02411514 --- /dev/null +++ b/utils/SubmissionsFaker/Consts/ScenarioNgos.cs @@ -0,0 +1,39 @@ +namespace SubmissionsFaker.Consts; + +public enum ScenarioNgo +{ + Alfa, + Beta, + Delta +} + +public class ScenarioNgos +{ + public static AlfaDetails Alfa => new(); + public static BetaDetails Beta => new BetaDetails(); + public static DeltaDetails Delta => new DeltaDetails(); + + public class AlfaDetails + { + public static implicit operator ScenarioNgo(AlfaDetails _) => ScenarioNgo.Alfa; + public readonly string Anya = "anya@alfa.com"; + public readonly string Ben = "ben@alfa.com"; + public readonly string Cody = "cody@alfa.com"; + } + + public class BetaDetails + { + public static implicit operator ScenarioNgo(BetaDetails _) => ScenarioNgo.Beta; + public readonly string Dana = "dana@beta.com"; + public readonly string Ivy = "ivy@beta.com"; + public readonly string Finn = "finn@beta.com"; + } + + public class DeltaDetails + { + public static implicit operator ScenarioNgo(DeltaDetails _) => ScenarioNgo.Delta; + public readonly string Gia = "gia@delta.com"; + public readonly string Mason = "mason@delta.com"; + public readonly string Omar = "omar@delta.com"; + } +} diff --git a/utils/SubmissionsFaker/Consts/ScenarioObserver.cs b/utils/SubmissionsFaker/Consts/ScenarioObserver.cs new file mode 100644 index 000000000..1ef86c218 --- /dev/null +++ b/utils/SubmissionsFaker/Consts/ScenarioObserver.cs @@ -0,0 +1,13 @@ +namespace SubmissionsFaker.Consts; + +public enum ScenarioObserver +{ + Alice, + Bob, + Charlie, + Dave, + Eve, + Frank, + Grace, + Mallory, +} diff --git a/utils/SubmissionsFaker/Consts/ScenarioPollingStation.cs b/utils/SubmissionsFaker/Consts/ScenarioPollingStation.cs new file mode 100644 index 000000000..789850e9c --- /dev/null +++ b/utils/SubmissionsFaker/Consts/ScenarioPollingStation.cs @@ -0,0 +1,9 @@ +namespace SubmissionsFaker.Consts; + +public enum ScenarioPollingStation +{ + Iasi, + Bacau, + Cluj, + Dolj, +} diff --git a/utils/SubmissionsFaker/Extensions/HttpClientExtensions.cs b/utils/SubmissionsFaker/Extensions/HttpClientExtensions.cs new file mode 100644 index 000000000..47ed6c8e5 --- /dev/null +++ b/utils/SubmissionsFaker/Extensions/HttpClientExtensions.cs @@ -0,0 +1,179 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Json; +using System.Text.Json; +using SubmissionsFaker.Clients.Token; + +namespace SubmissionsFaker.Extensions; + +public static class HttpClientExtensions +{ + private static readonly JsonSerializerOptions _jsonSerializerOptions = + new JsonSerializerOptions(JsonSerializerDefaults.Web); + + public static HttpClient NewForAuthenticatedUser(this Func clientFactory, string email, + string password) + { + var authenticatedUser = clientFactory(); + + var loginResponse = authenticatedUser + .PostWithResponse("/api/auth/login", new { email, password }); + + authenticatedUser.DefaultRequestHeaders.Authorization = new("Bearer", loginResponse.Token); + + return authenticatedUser; + } + + public static void PostWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static void PostWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri) + { + var response = client.PostAsync(requestUri, null).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static void PutWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + + public static TResponse PostWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + public static TResponse PutWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + public static TResponse GetResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri) + { + var response = client.GetAsync(requestUri).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + Console.Write(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync(_jsonSerializerOptions).GetAwaiter().GetResult(); + return result!; + } + + + public static async Task PostWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PostAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + Console.Write(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } + + public static async Task PutWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PutAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + Console.Write(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } + + public static async Task GetResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + CancellationToken cancellationToken = default) + { + var response = await client.GetAsync(requestUri, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + Console.Write(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(_jsonSerializerOptions, cancellationToken); + return result!; + } +} diff --git a/utils/SubmissionsFaker/Fakers/CitizenReportsFaker.cs b/utils/SubmissionsFaker/Fakers/CitizenReportsFaker.cs index ffd8c64d1..c74e15429 100644 --- a/utils/SubmissionsFaker/Fakers/CitizenReportsFaker.cs +++ b/utils/SubmissionsFaker/Fakers/CitizenReportsFaker.cs @@ -14,7 +14,7 @@ public CitizenReportsFaker(List forms, List lo { var form = f.PickRandom(forms); - x.CitizenReportId = f.Random.Guid().ToString(); + x.CitizenReportId = f.Random.Guid(); x.FormId = form.Id; x.LocationId = f.PickRandom(locations).LocationId!.Value; diff --git a/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs b/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs index 882fb3ba0..be1fb9a5a 100644 --- a/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs +++ b/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs @@ -13,7 +13,7 @@ public sealed class IncidentReportFaker : Faker public IncidentReportFaker(List forms,List pollingStations, List observers) { - RuleFor(x => x.IncidentReportId, f => f.Random.Guid()); + RuleFor(x => x.Id, f => f.Random.Guid()); RuleFor(x => x.ObserverToken, f => f.PickRandom(observers).Token); Rules((f, x) => diff --git a/utils/SubmissionsFaker/Fakers/PISSubmissionFaker.cs b/utils/SubmissionsFaker/Fakers/PISSubmissionFaker.cs index 3d5b0db16..e3995512d 100644 --- a/utils/SubmissionsFaker/Fakers/PISSubmissionFaker.cs +++ b/utils/SubmissionsFaker/Fakers/PISSubmissionFaker.cs @@ -8,7 +8,7 @@ public sealed class PISSubmissionFaker : Faker { public PISSubmissionFaker(UpsertPSIFormRequest psiForm, Guid pollingStationId, string observerToken) { - RuleFor(x => x.PollingStationId, pollingStationId.ToString()); + RuleFor(x => x.PollingStationId, pollingStationId); RuleFor(x => x.ObserverToken, observerToken); Rules((f, x) => { diff --git a/utils/SubmissionsFaker/Fakers/QuickReportFaker.cs b/utils/SubmissionsFaker/Fakers/QuickReportRequestFaker.cs similarity index 53% rename from utils/SubmissionsFaker/Fakers/QuickReportFaker.cs rename to utils/SubmissionsFaker/Fakers/QuickReportRequestFaker.cs index 4ad328f3c..4d3c93895 100644 --- a/utils/SubmissionsFaker/Fakers/QuickReportFaker.cs +++ b/utils/SubmissionsFaker/Fakers/QuickReportRequestFaker.cs @@ -5,9 +5,9 @@ namespace SubmissionsFaker.Fakers; -public sealed class QuickReportFaker : Faker +public sealed class QuickReportRequestFaker : Faker { - public QuickReportFaker(List pollingStations, List observers) + public QuickReportRequestFaker(List pollingStations, List observers) { RuleFor(x => x.PollingStationId, f => f.PickRandom(pollingStations).PollingStationId!); RuleFor(x => x.Id, f => f.Random.Guid()); @@ -15,4 +15,12 @@ public QuickReportFaker(List pollingStations, List x.Description, f => f.Lorem.Sentence(1000).OfLength(10000)); RuleFor(x => x.ObserverToken, f => f.PickRandom(observers).Token); } + + public QuickReportRequestFaker(Guid pollingStationId) + { + RuleFor(x => x.PollingStationId, pollingStationId); + RuleFor(x => x.Id, f => f.Random.Guid()); + RuleFor(x => x.Title, f => f.Lorem.Sentence(20).OfLength(1000)); + RuleFor(x => x.Description, f => f.Lorem.Sentence(100).OfLength(10000)); + } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Fakers/SubmissionFaker.cs b/utils/SubmissionsFaker/Fakers/SubmissionFaker.cs index 7d7ab6a8a..fba2f00c1 100644 --- a/utils/SubmissionsFaker/Fakers/SubmissionFaker.cs +++ b/utils/SubmissionsFaker/Fakers/SubmissionFaker.cs @@ -1,4 +1,5 @@ using Bogus; +using SubmissionsFaker.Clients.Models.Questions; using SubmissionsFaker.Clients.MonitoringObserver.Models; using SubmissionsFaker.Clients.NgoAdmin.Models; using SubmissionsFaker.Clients.PollingStations; @@ -23,4 +24,15 @@ public SubmissionFaker(List forms, List .Select(Answers.GetFakeAnswer).ToList(); }); } + + public SubmissionFaker(Guid formId, Guid pollingStationId, List questions) + { + Rules((f, x) => + { + x.PollingStationId = pollingStationId; + x.FormId = formId; + x.Answers = f.PickRandom(questions, f.Random.Int(0, questions.Count)) + .Select(Answers.GetFakeAnswer).ToList(); + }); + } } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs b/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs index 568b84eed..b93511665 100644 --- a/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs +++ b/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs @@ -5,9 +5,10 @@ namespace SubmissionsFaker.Forms; public class CitizenReportingFormData { - public static UpdateForm CitizenReporting = new() + public static UpdateForm CitizenReporting(string code) => new() { - Code = "A2", + Code = code, + FormType = "CitizenReporting", DefaultLanguage = "RO", Languages = new List { "RO", "EN" }, Name = new TranslatedString @@ -20,7 +21,6 @@ public class CitizenReportingFormData { "EN", "test form" }, { "RO", "formular de test" } }, - FormType = "Opening", Questions = [ new NumberQuestionRequest diff --git a/utils/SubmissionsFaker/Forms/FormData.cs b/utils/SubmissionsFaker/Forms/FormData.cs index aef4d272a..941dc83db 100644 --- a/utils/SubmissionsFaker/Forms/FormData.cs +++ b/utils/SubmissionsFaker/Forms/FormData.cs @@ -5,22 +5,14 @@ namespace SubmissionsFaker.Forms; public class FormData { - public static UpdateForm OpeningForm = new() + public static UpdateForm OpeningForm(string code) => new() { - FormType = "Opening", - Code = "A2", + Code = code, DefaultLanguage = "RO", Languages = new List { "RO", "EN" }, - Name = new TranslatedString - { - { "EN", "test form" }, - { "RO", "formular de test" } - }, - Description = new TranslatedString - { - { "EN", "test form" }, - { "RO", "formular de test" } - }, + Name = new TranslatedString { { "EN", code }, { "RO", code } }, + Description = new TranslatedString { { "EN", code }, { "RO", code } }, + FormType = "Opening", Questions = [ new NumberQuestionRequest @@ -34,44 +26,26 @@ public class FormData }, Helptext = new TranslatedString { - { "EN", "Please enter a number" }, - { "RO", "Vă rugăm să introduceți numărul dvs" } + { "EN", "Please enter a number" }, { "RO", "Vă rugăm să introduceți numărul dvs" } }, - InputPlaceholder = new TranslatedString - { - { "EN", "number" }, - { "RO", "numar" } - } + InputPlaceholder = new TranslatedString { { "EN", "number" }, { "RO", "numar" } } }, new TextQuestionRequest { Id = Guid.NewGuid(), Code = "A2", - Text = new TranslatedString - { - { "EN", "How are you today" }, - { "RO", "Cum te simți azi" } - }, + Text = new TranslatedString { { "EN", "How are you today" }, { "RO", "Cum te simți azi" } }, Helptext = new TranslatedString { - { "EN", "Please enter how are you" }, - { "RO", "Vă rugăm să introduceți cum sunteți" } + { "EN", "Please enter how are you" }, { "RO", "Vă rugăm să introduceți cum sunteți" } }, - InputPlaceholder = new TranslatedString - { - { "EN", "mood" }, - { "RO", "dispozitie" } - } + InputPlaceholder = new TranslatedString { { "EN", "mood" }, { "RO", "dispozitie" } } }, new DateQuestionRequest { Id = Guid.NewGuid(), Code = "A3", - Text = new TranslatedString - { - { "EN", "Time of arrival" }, - { "RO", "Timpul sosirii" } - }, + Text = new TranslatedString { { "EN", "Time of arrival" }, { "RO", "Timpul sosirii" } }, Helptext = new TranslatedString { { "EN", "Please enter exact hour when did you arrive" }, @@ -84,13 +58,11 @@ public class FormData Code = "C1", Text = new TranslatedString { - { "EN", "Please rate this form" }, - { "RO", "Vă rugăm să evaluați acest formular" } + { "EN", "Please rate this form" }, { "RO", "Vă rugăm să evaluați acest formular" } }, Helptext = new TranslatedString { - { "EN", "Please give us a rating" }, - { "RO", "Vă rugăm să ne dați o evaluare" } + { "EN", "Please give us a rating" }, { "RO", "Vă rugăm să ne dați o evaluare" } }, Scale = "OneTo10" }, @@ -98,143 +70,106 @@ public class FormData { Id = Guid.NewGuid(), Code = "B1", - Text = new TranslatedString - { - { "EN", "The overall conduct of the opening of this PS was:" }, - { "RO", "Conducerea generală a deschiderii acestui PS a fost:" } - }, - Helptext = new TranslatedString - { - { "EN", "Please select a single option" }, - { "RO", "Vă rugăm să selectați o singură opțiune" } - }, + Text = + new TranslatedString + { + { "EN", "The overall conduct of the opening of this PS was:" }, + { "RO", "Conducerea generală a deschiderii acestui PS a fost:" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select a single option" }, + { "RO", "Vă rugăm să selectați o singură opțiune" } + }, Options = new List { new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Very good" }, - { "RO", "Foarte bun" } - }, + Text = new TranslatedString { { "EN", "Very good" }, { "RO", "Foarte bun" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Good" }, - { "RO", "bun" } - }, + Text = new TranslatedString { { "EN", "Good" }, { "RO", "bun" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Bad" }, - { "RO", "Rea" } - }, + Text = new TranslatedString { { "EN", "Bad" }, { "RO", "Rea" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Very bad" }, - { "RO", "Foarte rea" } - }, + Text = new TranslatedString { { "EN", "Very bad" }, { "RO", "Foarte rea" } }, IsFreeText = false, IsFlagged = true }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Other" }, - { "RO", "Alta" } - }, + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Alta" } }, IsFreeText = true, IsFlagged = true - }, + } } }, new MultiSelectQuestionRequest { Id = Guid.NewGuid(), Code = "B2", - Text = new TranslatedString - { - { "EN", "What party/bloc proxies were present at the opening of this PS" }, - { "RO", "Ce împuterniciri de partid/bloc au fost prezenți la deschiderea acestui PS" } - }, - Helptext = new TranslatedString - { - { "EN", "Please select as many you want" }, - { "RO", "Vă rugăm să selectați câte doriți" } - }, + Text = + new TranslatedString + { + { "EN", "What party/bloc proxies were present at the opening of this PS" }, + { "RO", "Ce împuterniciri de partid/bloc au fost prezenți la deschiderea acestui PS" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select as many you want" }, { "RO", "Vă rugăm să selectați câte doriți" } + }, Options = new List { new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Bloc 1" }, - { "RO", "Bloc 1" } - }, + Text = new TranslatedString { { "EN", "Bloc 1" }, { "RO", "Bloc 1" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Bloc 2" }, - { "RO", "Bloc 2" } - }, + Text = new TranslatedString { { "EN", "Bloc 2" }, { "RO", "Bloc 2" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Bloc 3" }, - { "RO", "Bloc 3" } - }, + Text = new TranslatedString { { "EN", "Bloc 3" }, { "RO", "Bloc 3" } }, IsFreeText = false, IsFlagged = false }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Party 1" }, - { "RO", "Party 1" } - }, + Text = new TranslatedString { { "EN", "Party 1" }, { "RO", "Party 1" } }, IsFreeText = false, IsFlagged = true }, new() { Id = Guid.NewGuid(), - Text = new TranslatedString - { - { "EN", "Other" }, - { "RO", "Other" } - }, + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Other" } }, IsFreeText = true, IsFlagged = true } diff --git a/utils/SubmissionsFaker/Forms/IncidentReportingFormData.cs b/utils/SubmissionsFaker/Forms/IncidentReportingFormData.cs index 25685d691..d55d5fcae 100644 --- a/utils/SubmissionsFaker/Forms/IncidentReportingFormData.cs +++ b/utils/SubmissionsFaker/Forms/IncidentReportingFormData.cs @@ -5,10 +5,11 @@ namespace SubmissionsFaker.Forms; public class IncidentReportingFormData { - public static UpdateForm IncidentReporting = new() + public static UpdateForm IncidentReporting(string code) => new() { - Code = "A2", + Code = code, DefaultLanguage = "RO", + FormType = "IncidentReporting", Languages = new List { "RO", "EN" }, Name = new TranslatedString { @@ -20,7 +21,6 @@ public class IncidentReportingFormData { "EN", "test form" }, { "RO", "formular de test" } }, - FormType = "IncidentReporting", Questions = [ new NumberQuestionRequest diff --git a/utils/SubmissionsFaker/Models/CoalitionMember.cs b/utils/SubmissionsFaker/Models/CoalitionMember.cs new file mode 100644 index 000000000..a73c5bc37 --- /dev/null +++ b/utils/SubmissionsFaker/Models/CoalitionMember.cs @@ -0,0 +1,7 @@ +namespace SubmissionsFaker.Models; + +public class CoalitionMember +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; +} diff --git a/utils/SubmissionsFaker/Models/CoalitionModel.cs b/utils/SubmissionsFaker/Models/CoalitionModel.cs new file mode 100644 index 000000000..4f1067f6c --- /dev/null +++ b/utils/SubmissionsFaker/Models/CoalitionModel.cs @@ -0,0 +1,12 @@ +namespace SubmissionsFaker.Models; + +public class CoalitionModel +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public Guid LeaderId { get; set; } + public string LeaderName { get; set; } = string.Empty; + + public int NumberOfMembers => Members.Length; + public CoalitionMember[] Members { get; init; } = []; +} diff --git a/utils/SubmissionsFaker/Models/Response.cs b/utils/SubmissionsFaker/Models/Response.cs new file mode 100644 index 000000000..f55a54472 --- /dev/null +++ b/utils/SubmissionsFaker/Models/Response.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace SubmissionsFaker.Models; + +public class ListMonitoringNgos +{ + public List MonitoringNgos { get; init; } +} + +public class MonitoringNgoModel +{ + public Guid Id { get; init; } + + public required Guid NgoId { get; init; } + public required string Name { get; init; } +} diff --git a/utils/SubmissionsFaker/Models/ResponseWithId.cs b/utils/SubmissionsFaker/Models/ResponseWithId.cs new file mode 100644 index 000000000..15750dd09 --- /dev/null +++ b/utils/SubmissionsFaker/Models/ResponseWithId.cs @@ -0,0 +1,6 @@ +namespace SubmissionsFaker.Models; + +public class ResponseWithId +{ + public Guid Id { get; set; } +} diff --git a/utils/SubmissionsFaker/Program.cs b/utils/SubmissionsFaker/Program.cs index 0f71035dc..88516995a 100644 --- a/utils/SubmissionsFaker/Program.cs +++ b/utils/SubmissionsFaker/Program.cs @@ -12,8 +12,10 @@ using SubmissionsFaker.Clients.PlatformAdmin.Models; using SubmissionsFaker.Clients.PollingStations; using SubmissionsFaker.Clients.Token; +using SubmissionsFaker.Consts; using SubmissionsFaker.Fakers; using SubmissionsFaker.Forms; +using SubmissionsFaker.Scenarios; using SubmissionsFaker.Seeders; using Credentials = SubmissionsFaker.Clients.Token.Credentials; @@ -34,12 +36,6 @@ #region constants -const string platformAdminUsername = "john.doe@example.com"; -const string platformAdminPassword = "password123"; - -const string ngoAdminUsername = "fake@admin.com"; -const string ngoAdminPassword = "string"; - string[] images = [ "1.jpg", @@ -60,30 +56,15 @@ var client = new HttpClient(new LoggingHandler(new HttpClientHandler())) { BaseAddress = new Uri("https://localhost:7123"), - }; var tokenApi = RestService.For(client); -var platformAdminApi = RestService.For(client); -var pollingStationsApi = RestService.For(client); -var locationsApi = RestService.For(client); -var ngoAdminApi = RestService.For(client); + var observerApi = RestService.For(client); var citizenReportApi = RestService.For(client); #endregion -#region authorize platform admin - -var platformAdminToken = await tokenApi.GetToken(new Credentials(platformAdminUsername, platformAdminPassword)); - -#endregion - -CreateResponse electionRound = default!; -CreateResponse electionRound2 = default!; -CreateResponse ngo = default!; -CreateResponse monitoringNgo = default!; -LoginResponse ngoAdminToken = default!; List pollingStations = []; List locations = []; List forms = []; @@ -91,8 +72,9 @@ List incidentReportingForms = []; List observersTokens = []; -var observers = new ApplicationUserFaker().Generate(Consts.NUMBER_OF_OBSERVERS)!; +var observers = new ApplicationUserFaker().Generate(SeederVars.NUMBER_OF_OBSERVERS)!; +ScenarioData scenarioData = default!; await AnsiConsole.Progress() .AutoRefresh(true) .AutoClear(false) @@ -100,52 +82,76 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var setupTask = ctx.AddTask("[green]Setup election round and NGO [/]", maxValue: 11); - - electionRound = - await platformAdminApi.CreateElectionRound(new ElectionRoundFaker().Generate(), platformAdminToken.Token); - setupTask.Increment(1); - - electionRound2 = - await platformAdminApi.CreateElectionRound(new ElectionRoundFaker().Generate(), platformAdminToken.Token); - setupTask.Increment(1); + var setupTask = ctx.AddTask("[green]Setup election round and NGO [/]", maxValue: 4); - await platformAdminApi.CreatePSIForm(electionRound.Id, PSIFormData.PSIForm, platformAdminToken.Token); - setupTask.Increment(1); - - ngo = await platformAdminApi.CreateNgo(new NgoFaker().Generate(), platformAdminToken.Token); - setupTask.Increment(1); - - monitoringNgo = await platformAdminApi.AssignNgoToElectionRound(electionRound.Id, new AssignNgoRequest(ngo.Id), - platformAdminToken.Token); - setupTask.Increment(1); - - await platformAdminApi.AssignNgoToElectionRound(electionRound2.Id, new AssignNgoRequest(ngo.Id), - platformAdminToken.Token); - setupTask.Increment(1); - - await platformAdminApi.EnableCitizenReporting(electionRound.Id, new EnableCitizenReportingRequest(ngo.Id), - platformAdminToken.Token); - setupTask.Increment(1); - - var ngoAdmin = new ApplicationUserFaker(ngoAdminUsername, ngoAdminPassword).Generate(); - await platformAdminApi.CreateNgoAdmin(ngoAdmin, ngo.Id, platformAdminToken.Token); + scenarioData = ScenarioBuilder.New(() => new HttpClient(new LoggingHandler(new HttpClientHandler())) + { + BaseAddress = new Uri("https://localhost:7123"), + }) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta], form => form + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + ) + .WithForm("A", [ScenarioNgos.Alfa], + form => form.WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau)) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Alice, ScenarioPollingStation.Cluj) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithQuickReport(ScenarioObserver.Bob, ScenarioPollingStation.Cluj) + )) + .WithElectionRound(ScenarioElectionRound.B, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithMonitoringNgo(ScenarioNgos.Beta)) + .WithElectionRound(ScenarioElectionRound.C, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithMonitoringNgo(ScenarioNgos.Beta)) + .WithElectionRound(ScenarioElectionRound.D, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithMonitoringNgo(ScenarioNgos.Beta)) + .Please(); + + await scenarioData.PlatformAdminClient.CreatePSIForm(scenarioData.ElectionRoundId, PSIFormData.PSIForm); setupTask.Increment(1); - ngoAdminToken = await tokenApi.GetToken(new Credentials(ngoAdminUsername, ngoAdminPassword)); + await scenarioData.PlatformAdminClient.EnableCitizenReporting(scenarioData.ElectionRoundId, + new EnableCitizenReportingRequest(scenarioData.NgoIdByName(ScenarioNgo.Alfa))); setupTask.Increment(1); #region import polling stations using var pollingStationsStream = File.OpenRead("polling-stations.csv"); - await platformAdminApi.ImportPollingStations(electionRound.Id, - new StreamPart(pollingStationsStream, "polling-stations.csv", "text/csv"), platformAdminToken.Token); + await scenarioData.PlatformAdminClient.ImportPollingStations(scenarioData.ElectionRoundId, + new StreamPart(pollingStationsStream, "polling-stations.csv", "text/csv")); var pollingStationNodes = - await pollingStationsApi.GetAllPollingStations(electionRound.Id, platformAdminToken.Token); + await scenarioData.PlatformAdminClient.GetAllPollingStations(scenarioData.ElectionRoundId); pollingStations = faker .PickRandom(pollingStationNodes.Nodes.Where(x => x.PollingStationId.HasValue).ToList(), - Consts.NUMBER_OF_POLLING_STATIONS_TO_VISIT) + SeederVars.NUMBER_OF_POLLING_STATIONS_TO_VISIT) .ToList(); setupTask.Increment(1); @@ -155,13 +161,13 @@ await platformAdminApi.ImportPollingStations(electionRound.Id, #region import locations using var locationsStream = File.OpenRead("locations.csv"); - await platformAdminApi.ImportLocations(electionRound.Id, - new StreamPart(locationsStream, "locations.csv", "text/csv"), platformAdminToken.Token); + await scenarioData.PlatformAdminClient.ImportLocations(scenarioData.ElectionRoundId, + new StreamPart(locationsStream, "locations.csv", "text/csv")); - var locationsNodes = await locationsApi.GetAllLocations(electionRound.Id, platformAdminToken.Token); + var locationsNodes = await scenarioData.PlatformAdminClient.GetAllLocations(scenarioData.ElectionRoundId); locations = faker .PickRandom(locationsNodes.Nodes.Where(x => x.LocationId.HasValue).ToList(), - Consts.NUMBER_OF_LOCATIONS_TO_VISIT) + SeederVars.NUMBER_OF_LOCATIONS_TO_VISIT) .ToList(); setupTask.Increment(1); @@ -177,7 +183,8 @@ await AnsiConsole.Progress() .StartAsync(async ctx => { var formsTask = ctx.AddTask("[green]Creating forms[/]", autoStart: false); - forms = await FormsSeeder.Seed(ngoAdminApi, ngoAdminToken, electionRound.Id, formsTask); + forms = await FormsSeeder.Seed(scenarioData.AdminApiOfNgo(ScenarioNgo.Alfa), scenarioData.ElectionRoundId, + formsTask); }); await AnsiConsole.Progress() @@ -189,7 +196,8 @@ await AnsiConsole.Progress() { var formsTask = ctx.AddTask("[green]Creating citizen reporting forms[/]", autoStart: false); citizenReportingForms = - await CitizenReportingFormSeeder.Seed(ngoAdminApi, ngoAdminToken, electionRound.Id, formsTask); + await CitizenReportingFormSeeder.Seed(scenarioData.AdminApiOfNgo(ScenarioNgo.Alfa), + scenarioData.ElectionRoundId, formsTask); }); await AnsiConsole.Progress() @@ -201,7 +209,8 @@ await AnsiConsole.Progress() { var formsTask = ctx.AddTask("[green]Creating incident reporting forms[/]", autoStart: false); incidentReportingForms = - await IncidentReportingFormSeeder.Seed(ngoAdminApi, ngoAdminToken, electionRound.Id, formsTask); + await IncidentReportingFormSeeder.Seed(scenarioData.AdminApiOfNgo(ScenarioNgo.Alfa), + scenarioData.ElectionRoundId, formsTask); }); await AnsiConsole.Progress() @@ -211,10 +220,11 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var observersTask = ctx.AddTask("[green]Seeding observers[/]", maxValue: Consts.NUMBER_OF_OBSERVERS, + var observersTask = ctx.AddTask("[green]Seeding observers[/]", maxValue: SeederVars.NUMBER_OF_OBSERVERS, autoStart: false); - await ObserversSeeder.Seed(platformAdminApi, platformAdminToken, observers, electionRound.Id, monitoringNgo.Id, + await ObserversSeeder.Seed(scenarioData.PlatformAdminClient, observers, scenarioData.ElectionRoundId, + scenarioData.ElectionRound.MonitoringNgoIdByName(ScenarioNgo.Alfa), observersTask); }); @@ -225,9 +235,10 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var observersLoginTask = ctx.AddTask("[green]Logging in observers[/]", maxValue: Consts.NUMBER_OF_OBSERVERS); + var observersLoginTask = + ctx.AddTask("[green]Logging in observers[/]", maxValue: SeederVars.NUMBER_OF_OBSERVERS); - foreach (var observersChunk in observers.Chunk(Consts.CHUNK_SIZE)) + foreach (var observersChunk in observers.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = new List>(); foreach (var observer in observersChunk) @@ -238,12 +249,12 @@ await AnsiConsole.Progress() var tokens = await Task.WhenAll(tasks); observersTokens.AddRange(tokens); - observersLoginTask.Increment(Consts.CHUNK_SIZE); + observersLoginTask.Increment(SeederVars.CHUNK_SIZE); } }); var submissionRequests = new SubmissionFaker(forms, pollingStations, observersTokens) - .GenerateUnique(Consts.NUMBER_OF_SUBMISSIONS); + .GenerateUnique(SeederVars.NUMBER_OF_SUBMISSIONS); var psiRequests = submissionRequests @@ -251,29 +262,29 @@ await AnsiConsole.Progress() .ToList(); var noteRequests = new NoteFaker(submissionRequests.Where(x => x.Answers.Any()).ToList()) - .Generate(Consts.NUMBER_OF_NOTES); + .Generate(SeederVars.NUMBER_OF_NOTES); var attachmentRequests = new AttachmentFaker(submissionRequests.Where(x => x.Answers.Any()).ToList()) - .Generate(Consts.NUMBER_OF_ATTACHMENTS); + .Generate(SeederVars.NUMBER_OF_ATTACHMENTS); -var quickReportRequests = new QuickReportFaker(pollingStations, observersTokens) - .Generate(Consts.NUMBER_OF_QUICK_REPORTS); +var quickReportRequests = new QuickReportRequestFaker(pollingStations, observersTokens) + .Generate(SeederVars.NUMBER_OF_QUICK_REPORTS); var quickReportAttachmentRequests = new QuickReportAttachmentFaker(quickReportRequests) - .GenerateUnique(Consts.NUMBER_OF_QUICK_REPORTS_ATTACHMENTS); + .GenerateUnique(SeederVars.NUMBER_OF_QUICK_REPORTS_ATTACHMENTS); var citizenReportRequests = new CitizenReportsFaker(citizenReportingForms, locations) - .GenerateUnique(Consts.NUMBER_OF_CITIZEN_REPORTS); + .GenerateUnique(SeederVars.NUMBER_OF_CITIZEN_REPORTS); var incidentReportRequests = new IncidentReportFaker(incidentReportingForms, pollingStations, observersTokens) - .GenerateUnique(Consts.NUMBER_OF_INCIDENT_REPORTS); + .GenerateUnique(SeederVars.NUMBER_OF_INCIDENT_REPORTS); var citizenReportNoteRequests = new CitizenReportNoteFaker(citizenReportRequests.Where(x => x.Answers.Any()).ToList()) - .Generate(Consts.NUMBER_OF_CITIZEN_REPORTS_NOTES); + .Generate(SeederVars.NUMBER_OF_CITIZEN_REPORTS_NOTES); -var citizenReportAttachmentRequests = - new CitizenReportAttachmentFaker(citizenReportRequests.Where(x => x.Answers.Any()).ToList()) - .Generate(Consts.NUMBER_OF_CITIZEN_REPORTS_ATTACHMENTS); +// var citizenReportAttachmentRequests = +// new CitizenReportAttachmentFaker(citizenReportRequests.Where(x => x.Answers.Any()).ToList()) +// .Generate(SeederVars.NUMBER_OF_CITIZEN_REPORTS_ATTACHMENTS); await AnsiConsole.Progress() .AutoRefresh(true) @@ -282,14 +293,14 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking PSI submissions [/]", maxValue: Consts.NUMBER_OF_SUBMISSIONS); - foreach (var submissionRequestChunk in psiRequests.Chunk(Consts.CHUNK_SIZE)) + var progressTask = ctx.AddTask("[green]Faking PSI submissions [/]", maxValue: SeederVars.NUMBER_OF_SUBMISSIONS); + foreach (var submissionRequestChunk in psiRequests.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = submissionRequestChunk.Select(sr => - observerApi.SubmitPSIForm(electionRound.Id, sr.PollingStationId, sr, sr.ObserverToken)); + observerApi.SubmitPSIForm(scenarioData.ElectionRoundId, sr.PollingStationId, sr, sr.ObserverToken)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -300,14 +311,14 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking submissions [/]", maxValue: Consts.NUMBER_OF_SUBMISSIONS); - foreach (var submissionRequestChunk in submissionRequests.Chunk(Consts.CHUNK_SIZE)) + var progressTask = ctx.AddTask("[green]Faking submissions [/]", maxValue: SeederVars.NUMBER_OF_SUBMISSIONS); + foreach (var submissionRequestChunk in submissionRequests.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = submissionRequestChunk.Select(sr => - observerApi.SubmitForm(electionRound.Id, sr, sr.ObserverToken)); + observerApi.SubmitForm(scenarioData.ElectionRoundId, sr, sr.ObserverToken)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -318,13 +329,14 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking notes[/]", maxValue: Consts.NUMBER_OF_NOTES); + var progressTask = ctx.AddTask("[green]Faking notes[/]", maxValue: SeederVars.NUMBER_OF_NOTES); - foreach (var notesChunk in noteRequests.Chunk(Consts.CHUNK_SIZE)) + foreach (var notesChunk in noteRequests.Chunk(SeederVars.CHUNK_SIZE)) { - var tasks = notesChunk.Select(n => observerApi.SubmitNote(electionRound.Id, n, n.ObserverToken)); + var tasks = notesChunk.Select(n => + observerApi.SubmitNote(scenarioData.ElectionRoundId, n, n.ObserverToken)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -335,16 +347,16 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking attachments[/]", maxValue: Consts.NUMBER_OF_ATTACHMENTS); + var progressTask = ctx.AddTask("[green]Faking attachments[/]", maxValue: SeederVars.NUMBER_OF_ATTACHMENTS); foreach (var ar in attachmentRequests) { var fileName = faker.PickRandom(images); await using var fileStream = File.OpenRead(Path.Combine("Attachments", fileName)); - await observerApi.SubmitAttachment(electionRound.Id, + await observerApi.SubmitAttachment(scenarioData.ElectionRoundId, ar.PollingStationId.ToString(), ar.Id.ToString(), - ar.FormId, + ar.FormId.ToString(), ar.QuestionId.ToString(), new StreamPart(fileStream, fileName, "image/jpeg"), ar.ObserverToken); @@ -361,13 +373,14 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking citizen reports [/]", maxValue: Consts.NUMBER_OF_CITIZEN_REPORTS); - foreach (var citizenReportBatch in citizenReportRequests.Chunk(Consts.CHUNK_SIZE)) + var progressTask = ctx.AddTask("[green]Faking citizen reports [/]", + maxValue: SeederVars.NUMBER_OF_CITIZEN_REPORTS); + foreach (var citizenReportBatch in citizenReportRequests.Chunk(SeederVars.CHUNK_SIZE)) { - var tasks = citizenReportBatch.Select(sr => citizenReportApi.SubmitForm(electionRound.Id, sr)); + var tasks = citizenReportBatch.Select(sr => citizenReportApi.SubmitForm(scenarioData.ElectionRoundId, sr)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -378,14 +391,15 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking incident reports [/]", maxValue: Consts.NUMBER_OF_INCIDENT_REPORTS); - foreach (var incidentReportBatch in incidentReportRequests.Chunk(Consts.CHUNK_SIZE)) + var progressTask = ctx.AddTask("[green]Faking incident reports [/]", + maxValue: SeederVars.NUMBER_OF_INCIDENT_REPORTS); + foreach (var incidentReportBatch in incidentReportRequests.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = incidentReportBatch.Select(ir => - observerApi.SubmitIncidentReport(electionRound.Id, ir, ir.ObserverToken)); + observerApi.SubmitIncidentReport(scenarioData.ElectionRoundId, ir, ir.ObserverToken)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -397,13 +411,14 @@ await AnsiConsole.Progress() .StartAsync(async ctx => { var progressTask = ctx.AddTask("[green]Faking citizen reports notes[/]", - maxValue: Consts.NUMBER_OF_CITIZEN_REPORTS_NOTES); + maxValue: SeederVars.NUMBER_OF_CITIZEN_REPORTS_NOTES); - foreach (var notesChunk in citizenReportNoteRequests.Chunk(Consts.CHUNK_SIZE)) + foreach (var notesChunk in citizenReportNoteRequests.Chunk(SeederVars.CHUNK_SIZE)) { - var tasks = notesChunk.Select(n => citizenReportApi.SubmitNote(electionRound.Id, n)); + var tasks = notesChunk.Select(n => + citizenReportApi.SubmitNote(scenarioData.ElectionRoundId, n.CitizenReportId, n)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -414,14 +429,14 @@ await AnsiConsole.Progress() .Columns(progressColumns) .StartAsync(async ctx => { - var progressTask = ctx.AddTask("[green]Faking quick reports[/]", maxValue: Consts.NUMBER_OF_QUICK_REPORTS); + var progressTask = ctx.AddTask("[green]Faking quick reports[/]", maxValue: SeederVars.NUMBER_OF_QUICK_REPORTS); - foreach (var quickReportChunk in quickReportRequests.Chunk(Consts.CHUNK_SIZE)) + foreach (var quickReportChunk in quickReportRequests.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = quickReportChunk.Select(qr => - observerApi.SubmitQuickReport(electionRound.Id, qr, qr.ObserverToken)); + observerApi.SubmitQuickReport(scenarioData.ElectionRoundId, qr, qr.ObserverToken)); await Task.WhenAll(tasks); - progressTask.Increment(Consts.CHUNK_SIZE); + progressTask.Increment(SeederVars.CHUNK_SIZE); } }); @@ -433,13 +448,13 @@ await AnsiConsole.Progress() .StartAsync(async ctx => { var progressTask = ctx.AddTask("[green]Faking quick report attachments[/]", - maxValue: Consts.NUMBER_OF_QUICK_REPORTS_ATTACHMENTS); + maxValue: SeederVars.NUMBER_OF_QUICK_REPORTS_ATTACHMENTS); foreach (var qar in quickReportAttachmentRequests) { var fileName = faker.PickRandom(images); await using var fs = File.OpenRead(Path.Combine("Attachments", fileName)); - await observerApi.SubmitQuickReportAttachment(electionRound.Id, qar.QuickReportId.ToString(), + await observerApi.SubmitQuickReportAttachment(scenarioData.ElectionRoundId, qar.QuickReportId.ToString(), qar.Id.ToString(), new StreamPart(fs, fileName, "image/jpeg"), qar.ObserverToken); progressTask.Increment(1); diff --git a/utils/SubmissionsFaker/Scenarios/CoalitionFormScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/CoalitionFormScenarioBuilder.cs new file mode 100644 index 000000000..c8b332d8b --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/CoalitionFormScenarioBuilder.cs @@ -0,0 +1,44 @@ +using SubmissionsFaker.Clients.NgoAdmin.Models; +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Fakers; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class CoalitionFormScenarioBuilder +{ + private readonly CoalitionScenarioBuilder _parentBuilder; + private readonly UpdateFormResponse _form; + + private readonly Dictionary _submissions = new Dictionary(); + + internal CoalitionFormScenarioBuilder( + CoalitionScenarioBuilder parentBuilder, + UpdateFormResponse form) + { + _parentBuilder = parentBuilder; + _form = form; + } + + public CoalitionFormScenarioBuilder WithSubmission(ScenarioObserver observer, ScenarioPollingStation pollingStation) + { + var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStation); + var submission = new SubmissionFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observerClient = _parentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); + + var submissionId = observerClient.PostWithResponse( + $"/api/election-rounds/{_parentBuilder.ParentBuilder.ElectionRoundId}/form-submissions", + submission).Id; + + _submissions.Add($"{observer}_{pollingStation}", submissionId); + return this; + } + + public Guid GetSubmissionId(ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _submissions[$"{observer}_{pollingStation}"]; + + public Guid FormId => _form.Id; + public UpdateFormResponse Form => _form; +} diff --git a/utils/SubmissionsFaker/Scenarios/CoalitionScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/CoalitionScenarioBuilder.cs new file mode 100644 index 000000000..25792e24c --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/CoalitionScenarioBuilder.cs @@ -0,0 +1,83 @@ +using SubmissionsFaker.Clients.NgoAdmin.Models; +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Forms; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class CoalitionScenarioBuilder +{ + private readonly HttpClient _platformAdmin; + private readonly HttpClient _coalitionLeaderAdminAdmin; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly CoalitionModel _coalition; + private readonly Dictionary _forms = new(); + + public CoalitionScenarioBuilder(HttpClient platformAdmin, + HttpClient coalitionLeaderAdmin, + ElectionRoundScenarioBuilder parentBuilder, + CoalitionModel coalition) + { + _platformAdmin = platformAdmin; + _coalitionLeaderAdminAdmin = coalitionLeaderAdmin; + ParentBuilder = parentBuilder; + _coalition = coalition; + } + + public Guid CoalitionId => _coalition.Id; + + public CoalitionScenarioBuilder WithForm(string? formCode = null, + ScenarioNgo[]? sharedWithMembers = null, + Action? cfg = null) + { + sharedWithMembers ??= Array.Empty(); + formCode ??= Guid.NewGuid().ToString(); + + var formRequest = FormData.OpeningForm(formCode); + var ngoForm = + _coalitionLeaderAdminAdmin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + _coalitionLeaderAdminAdmin + .PostWithoutResponse($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish"); + + var members = sharedWithMembers.Select(member => ParentBuilder.ParentBuilder.NgoIdByName(member)) + .ToList(); + _coalitionLeaderAdminAdmin.PutWithoutResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/coalitions/{CoalitionId}/forms/{ngoForm.Id}:access", + new { NgoMembersIds = members }); + + var coalitionFormScenarioBuilder = new CoalitionFormScenarioBuilder(this, ngoForm); + cfg?.Invoke(coalitionFormScenarioBuilder); + + _forms.Add(formCode, coalitionFormScenarioBuilder); + + return this; + } + + public CoalitionScenarioBuilder WithMonitoringObserver(ScenarioNgo ngo, ScenarioObserver observer) + { + ParentBuilder.ParentBuilder.ElectionRound.MonitoringNgoByName(ngo).WithMonitoringObserver(observer); + + return this; + } + + + public CoalitionScenarioBuilder WithQuickReport(ScenarioObserver observer, ScenarioPollingStation pollingStation) + { + var observerClient = ParentBuilder.WithQuickReport(observer, pollingStation); + return this; + } + + + + public UpdateFormResponse Form => _forms.First().Value.Form; + public CoalitionFormScenarioBuilder FormDetails => _forms.First().Value; + public Guid FormId => _forms.First().Value.FormId; + public UpdateFormResponse FormByCode(string formCode) => _forms[formCode].Form; + + public Guid GetSubmissionId(string formCode, ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _forms[formCode].GetSubmissionId(observer, pollingStation); +} diff --git a/utils/SubmissionsFaker/Scenarios/ElectionRoundScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/ElectionRoundScenarioBuilder.cs new file mode 100644 index 000000000..1a6715957 --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/ElectionRoundScenarioBuilder.cs @@ -0,0 +1,128 @@ +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Fakers; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class ElectionRoundScenarioBuilder +{ + public Guid ElectionRoundId { get; } + + private readonly Dictionary _pollingStations = new(); + private readonly Dictionary _monitoringNgos = new(); + private readonly Dictionary _coalitions = new(); + private readonly Dictionary _quickReports = new(); + + private readonly HttpClient _platformAdmin; + public readonly ScenarioBuilder ParentBuilder; + + public ElectionRoundScenarioBuilder WithPollingStation(ScenarioPollingStation pollingStation) + { + var createdPollingStation = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/polling-stations", + new + { + Level1 = Guid.NewGuid().ToString(), + Number = "1", + DisplayOrder = 1, + Address = "Address", + Tags = new { } + }); + + _pollingStations[pollingStation] = createdPollingStation.Id; + return this; + } + + public ElectionRoundScenarioBuilder(ScenarioBuilder parentBuilder, + Guid electionRoundId, + HttpClient platformAdmin) + { + ParentBuilder = parentBuilder; + ElectionRoundId = electionRoundId; + _platformAdmin = platformAdmin; + } + + public ElectionRoundScenarioBuilder WithMonitoringNgo(ScenarioNgo ngo, + Action? cfg = null) + { + var monitoringNgo = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/monitoring-ngos", + new { ngoId = ParentBuilder.NgoIdByName(ngo) }); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ParentBuilder.NgoByName(ngo)); + + cfg?.Invoke(monitoringNgoScenarioBuilder); + + _monitoringNgos.Add(ngo, monitoringNgoScenarioBuilder); + + return this; + } + + public ElectionRoundScenarioBuilder WithCoalition(ScenarioCoalition name, ScenarioNgo leader, ScenarioNgo[] members, + Action? cfg = null) + { + var coalition = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = ParentBuilder.NgoIdByName(leader), + NgoMembersIds = members.Select(member => ParentBuilder.NgoIdByName(member)).ToArray(), + }); + + var response = + _platformAdmin.GetResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos"); + + foreach (var monitoringNgo in response.MonitoringNgos) + { + var ngo = ParentBuilder.NgoById(monitoringNgo.NgoId); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ngo.builder); + + + _monitoringNgos.TryAdd(ngo.ngo, monitoringNgoScenarioBuilder); + } + + var coalitionScenarioBuilder = + new CoalitionScenarioBuilder(_platformAdmin, ParentBuilder.NgoByName(leader).Admin, this, coalition); + cfg?.Invoke(coalitionScenarioBuilder); + + _coalitions.Add(name, coalitionScenarioBuilder); + return this; + } + + public ElectionRoundScenarioBuilder WithQuickReport(ScenarioObserver observer, + ScenarioPollingStation pollingStation) + { + var observerClient = ParentBuilder.ClientFor(observer); + var pollingStationId = PollingStationByName(pollingStation); + + var quickReport = observerClient.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/quick-reports", + new QuickReportRequestFaker(pollingStationId).Generate()); + + _quickReports.Add($"{observer}_{pollingStation}", quickReport.Id); + return this; + } + + public MonitoringNgoScenarioBuilder MonitoringNgo => _monitoringNgos.First().Value; + public MonitoringNgoScenarioBuilder MonitoringNgoByName(ScenarioNgo ngo) => _monitoringNgos[ngo]; + public Guid MonitoringNgoIdByName(ScenarioNgo ngo) => _monitoringNgos[ngo].MonitoringNgoId; + public CoalitionScenarioBuilder Coalition => _coalitions.First().Value; + public Guid CoalitionId => _coalitions.First().Value.CoalitionId; + public CoalitionScenarioBuilder CoalitionByName(ScenarioCoalition coalition) => _coalitions[coalition]; + public Guid CoalitionIdByName(ScenarioCoalition coalition) => _coalitions[coalition].CoalitionId; + + public Guid PollingStation => _pollingStations.First().Value; + + public Guid PollingStationByName(ScenarioPollingStation pollingStation) => _pollingStations[pollingStation]; + + public Guid GetQuickReportId(ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _quickReports[$"{observer}_{pollingStation}"]; +} diff --git a/utils/SubmissionsFaker/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/MonitoringNgoFormScenarioBuilder.cs new file mode 100644 index 000000000..958b8e78f --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -0,0 +1,38 @@ +using SubmissionsFaker.Clients.NgoAdmin.Models; +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Fakers; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class MonitoringNgoFormScenarioBuilder +{ + public readonly MonitoringNgoScenarioBuilder ParentBuilder; + + private readonly UpdateFormResponse _form; + public Guid FormId => _form.Id; + public UpdateFormResponse Form => _form; + + public MonitoringNgoFormScenarioBuilder( + MonitoringNgoScenarioBuilder parentBuilder, + UpdateFormResponse form) + { + ParentBuilder = parentBuilder; + _form = form; + } + + public MonitoringNgoFormScenarioBuilder WithSubmission(ScenarioObserver observer, + ScenarioPollingStation pollingStation) + { + var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStation); + var submission = new SubmissionFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observerClient = ParentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); + + observerClient.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/form-submissions", + submission); + return this; + } +} diff --git a/utils/SubmissionsFaker/Scenarios/MonitoringNgoScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/MonitoringNgoScenarioBuilder.cs new file mode 100644 index 000000000..4933f6f23 --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -0,0 +1,79 @@ +using SubmissionsFaker.Clients.NgoAdmin.Models; +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Forms; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class MonitoringNgoScenarioBuilder +{ + public Guid ElectionRoundId { get; } + private readonly Dictionary _forms = new(); + private readonly Dictionary _monitoringObservers = new(); + public readonly Guid MonitoringNgoId; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly HttpClient _platformAdmin; + public NgoScenarioBuilder NgoScenario { get; } + public Guid FormId => _forms.First().Value.FormId; + public UpdateFormResponse Form => _forms.First().Value.Form; + public MonitoringNgoFormScenarioBuilder FormDetails => _forms.First().Value; + + public (Guid ObserverId, Guid MonitoringObserverId , HttpClient Client, string FullName, string Email, string PhoneNumber) Observer => _monitoringObservers.First().Value; + + public (Guid ObserverId, Guid MonitoringObserverId, HttpClient Client, string DisplayName, string Email, string PhoneNumber) ObserverByName(ScenarioObserver name) => + _monitoringObservers[name]; + + public MonitoringNgoScenarioBuilder(Guid electionRoundId, + Guid monitoringNgoId, + ElectionRoundScenarioBuilder parentBuilder, + HttpClient platformAdmin, + NgoScenarioBuilder ngoScenario) + { + ElectionRoundId = electionRoundId; + MonitoringNgoId = monitoringNgoId; + ParentBuilder = parentBuilder; + _platformAdmin = platformAdmin; + NgoScenario = ngoScenario; + } + + public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, + Action? cfAction = null) + { + formCode ??= Guid.NewGuid().ToString(); + var formRequest = FormData.OpeningForm(formCode); + var admin = NgoScenario.Admin; + + var ngoForm = admin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + admin + .PostWithoutResponse($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish"); + + var monitoringNgoFormScenarioBuilder = new MonitoringNgoFormScenarioBuilder(this, ngoForm); + cfAction?.Invoke(monitoringNgoFormScenarioBuilder); + + _forms.Add(formCode, monitoringNgoFormScenarioBuilder); + return this; + } + + public MonitoringNgoScenarioBuilder WithMonitoringObserver(ScenarioObserver observer) + { + var observerClient = ParentBuilder.ParentBuilder.ClientFor(observer); + var observerId = ParentBuilder.ParentBuilder.IdFor(observer); + var fullName = ParentBuilder.ParentBuilder.FullNameFor(observer); + var email = ParentBuilder.ParentBuilder.EmailFor(observer); + var phone = ParentBuilder.ParentBuilder.PhoneNumberFor(observer); + + var monitoringObserver = _platformAdmin + .PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{MonitoringNgoId}/monitoring-observers" + , new { observerId = observerId }); + + _monitoringObservers.Add(observer, (observerId,monitoringObserver.Id, observerClient, fullName, email,phone)); + return this; + } + + +} diff --git a/utils/SubmissionsFaker/Scenarios/NgoScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/NgoScenarioBuilder.cs new file mode 100644 index 000000000..1c4bbcb06 --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/NgoScenarioBuilder.cs @@ -0,0 +1,41 @@ +using Spectre.Console; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class NgoScenarioBuilder +{ + private readonly Dictionary _admins = new(); + private readonly HttpClient _platformAdmin; + private readonly Func _clientFactory; + public Guid NgoId { get; } + + public NgoScenarioBuilder(HttpClient platformAdmin, + Func clientFactory, + Guid ngoId) + { + _platformAdmin = platformAdmin; + _clientFactory = clientFactory; + NgoId = ngoId; + } + + public NgoScenarioBuilder WithAdmin(string? adminEmail = null) + { + adminEmail ??= Guid.NewGuid().ToString("N"); + var realEmail = $"{Guid.NewGuid()}@example.org"; + _platformAdmin.PostWithResponse($"/api/ngos/{NgoId}/admins", + new { FirstName = "NgoAdmin", LastName = adminEmail, Email = realEmail, Password = "string" }); + + var adminClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + AnsiConsole.WriteLine($"Ngo admin created: {realEmail}"); + + _admins.Add(adminEmail, adminClient); + return this; + } + + public HttpClient Admin => _admins.First().Value; + + public HttpClient AdminByName(string name) => _admins[name]; +} diff --git a/utils/SubmissionsFaker/Scenarios/ScenarioBuilder.cs b/utils/SubmissionsFaker/Scenarios/ScenarioBuilder.cs new file mode 100644 index 000000000..863877fa5 --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/ScenarioBuilder.cs @@ -0,0 +1,117 @@ +using SubmissionsFaker.Consts; +using SubmissionsFaker.Extensions; +using SubmissionsFaker.Models; + +namespace SubmissionsFaker.Scenarios; + +public class ScenarioBuilder +{ + public HttpClient PlatformAdmin { get; } + private readonly Func _clientFactory; + private readonly Dictionary _electionRounds = new(); + private readonly Dictionary _ngos = new(); + private readonly Dictionary _observers = new(); + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + + public string ObserverEmail => _observers.First().Value.Email; + public string ObserverFullName => _observers.First().Value.FullName; + public string ObserverPhoneNumber => _observers.First().Value.PhoneNumber; + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public string FullNameFor(ScenarioObserver observer) => _observers[observer].FullName; + public HttpClient ClientFor(ScenarioObserver observer) => _observers[observer].Client; + public Guid IdFor(ScenarioObserver observer) => _observers[observer].Id; + public string EmailFor(ScenarioObserver observer)=> _observers[observer].Email; + public string PhoneNumberFor(ScenarioObserver observer)=> _observers[observer].PhoneNumber; + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + + public (ScenarioNgo ngo, NgoScenarioBuilder builder) NgoById(Guid ngoId) + { + var ngo = _ngos.First(x => x.Value.NgoId == ngoId); + + return (ngo.Key, ngo.Value); + } + + public Guid NgoIdByName(ScenarioNgo ngo) => _ngos[ngo].NgoId; + + public static ScenarioBuilder New(Func clientFactory) + { + return new ScenarioBuilder(clientFactory); + } + + private ScenarioBuilder(Func clientFactory) + { + PlatformAdmin = clientFactory.NewForAuthenticatedUser(SeederVars.PlatformAdminUsername, + SeederVars.PlatformAdminPassword); + _clientFactory = clientFactory; + } + + public ScenarioBuilder WithNgo(ScenarioNgo ngo, Action? cfg = null) + { + var createdNgo = + PlatformAdmin.PostWithResponse("/api/ngos", new { name = $"{ngo}-{Guid.NewGuid()}"}); + + var ngoScenarioBuilder = new NgoScenarioBuilder(PlatformAdmin, _clientFactory, createdNgo.Id); + ngoScenarioBuilder.WithAdmin(); + + cfg?.Invoke(ngoScenarioBuilder); + _ngos.Add(ngo, ngoScenarioBuilder); + return this; + } + + public ScenarioBuilder WithElectionRound(ScenarioElectionRound electionRound, + Action? cfg = null) + { + var title = "ER" + electionRound + Guid.NewGuid(); + var createdElectionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + new + { + countryId = "6984f722-6963-d067-d4d4-9fd3ef2edbf6", + title = title, + englishTitle = title, + StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddYears(100) + }); + + + var electionRoundScenarioBuilder = + new ElectionRoundScenarioBuilder(this, createdElectionRound.Id, PlatformAdmin); + _electionRounds.Add(electionRound, electionRoundScenarioBuilder); + + cfg?.Invoke(electionRoundScenarioBuilder); + + return this; + } + + public ScenarioBuilder WithObserver(ScenarioObserver observer) + { + var realEmail = $"{Guid.NewGuid()}@example.org"; + var lastName = observer + "-" + Guid.NewGuid(); + var phoneNumber = Guid.NewGuid().ToString("N"); + + var createdObserver = PlatformAdmin.PostWithResponse("/api/observers", + new { FirstName = "Observer", LastName = lastName, Email = realEmail, Password = "string" , PhoneNumber= phoneNumber}); + + var observerClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + _observers.Add(observer, (createdObserver.Id, observerClient, $"Observer {lastName}", realEmail, phoneNumber)); + return this; + } + + public ScenarioData Please() + { + return new(PlatformAdmin, _electionRounds, _ngos, _observers); + } + + +} diff --git a/utils/SubmissionsFaker/Scenarios/ScenarioData.cs b/utils/SubmissionsFaker/Scenarios/ScenarioData.cs new file mode 100644 index 000000000..4b529dd9d --- /dev/null +++ b/utils/SubmissionsFaker/Scenarios/ScenarioData.cs @@ -0,0 +1,51 @@ +using Refit; +using SubmissionsFaker.Clients.NgoAdmin; +using SubmissionsFaker.Clients.PlatformAdmin; +using SubmissionsFaker.Consts; + +namespace SubmissionsFaker.Scenarios; + +public class ScenarioData +{ + public HttpClient PlatformAdmin { get; } + public IPlatformAdminApi PlatformAdminClient => RestService.For(PlatformAdmin); + private readonly IReadOnlyDictionary _electionRounds; + private readonly IReadOnlyDictionary _ngos; + + private readonly IReadOnlyDictionary _observers; + + public ScenarioData(HttpClient platformAdmin, + IReadOnlyDictionary electionRounds, + IReadOnlyDictionary ngos, + IReadOnlyDictionary observers) + { + PlatformAdmin = platformAdmin; + _electionRounds = electionRounds; + _ngos = ngos; + _observers = observers; + } + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public HttpClient ObserverByName(ScenarioObserver observer) => _observers[observer].Client; + public Guid ObserverIdByName(ScenarioObserver observer) => _observers[observer].Id; + + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + public HttpClient AdminOfNgo(ScenarioNgo name) => _ngos[name].Admin; + public INgoAdminApi AdminApiOfNgo(ScenarioNgo name) => RestService.For(_ngos[name].Admin); + public Guid NgoIdByName(ScenarioNgo name) => _ngos[name].NgoId; +} diff --git a/utils/SubmissionsFaker/Consts.cs b/utils/SubmissionsFaker/SeederVars.cs similarity index 87% rename from utils/SubmissionsFaker/Consts.cs rename to utils/SubmissionsFaker/SeederVars.cs index bbd2ee865..f7bea130f 100644 --- a/utils/SubmissionsFaker/Consts.cs +++ b/utils/SubmissionsFaker/SeederVars.cs @@ -1,7 +1,10 @@ namespace SubmissionsFaker; -public class Consts +public static class SeederVars { + public const string PlatformAdminUsername = "john.doe@example.com"; + public const string PlatformAdminPassword = "string"; + public const int CHUNK_SIZE = 2; public const int NUMBER_OF_POLLING_STATIONS_TO_VISIT = 2000; // should be greater than NUMBER_OF_SUBMISSIONS public const int NUMBER_OF_OBSERVERS = 10; diff --git a/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs b/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs index 0c9a9a413..adb190777 100644 --- a/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs @@ -9,18 +9,15 @@ namespace SubmissionsFaker.Seeders; public class CitizenReportingFormSeeder { public static async Task> Seed(INgoAdminApi ngoAdminApi, - LoginResponse ngoAdminToken, - string electionRoundId, + Guid electionRoundId, ProgressTask progressTask) { progressTask.MaxValue(1); progressTask.StartTask(); - var openingForm = await ngoAdminApi.CreateForm(electionRoundId, - CitizenReportingFormData.CitizenReporting with { FormType = "CitizenReporting" }, ngoAdminToken.Token); - await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, CitizenReportingFormData.CitizenReporting, - ngoAdminToken.Token); - await ngoAdminApi.PublishForm(electionRoundId, openingForm.Id, ngoAdminToken.Token); + var form = CitizenReportingFormData.CitizenReporting("CR"); + var citizenReporting = await ngoAdminApi.CreateForm(electionRoundId, form); + await ngoAdminApi.PublishForm(electionRoundId, citizenReporting.Id); progressTask.Increment(1); progressTask.Increment(progressTask.MaxValue); @@ -30,8 +27,8 @@ await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, CitizenReportingFo [ new UpdateFormResponse { - Id = openingForm.Id, - Questions = CitizenReportingFormData.CitizenReporting.Questions, + Id = citizenReporting.Id, + Questions = form.Questions, }, // ... ]; diff --git a/utils/SubmissionsFaker/Seeders/FormsSeeder.cs b/utils/SubmissionsFaker/Seeders/FormsSeeder.cs index 55a59ad12..928b70a08 100644 --- a/utils/SubmissionsFaker/Seeders/FormsSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/FormsSeeder.cs @@ -9,17 +9,16 @@ namespace SubmissionsFaker.Seeders; public class FormsSeeder { public static async Task> Seed(INgoAdminApi ngoAdminApi, - LoginResponse ngoAdminToken, - string electionRoundId, + Guid electionRoundId, ProgressTask progressTask) { progressTask .MaxValue(2) .StartTask(); - var form = await ngoAdminApi.CreateForm(electionRoundId, FormData.OpeningForm, ngoAdminToken.Token); - await ngoAdminApi.UpdateForm(electionRoundId, form.Id, FormData.OpeningForm, ngoAdminToken.Token); - await ngoAdminApi.PublishForm(electionRoundId, form.Id, ngoAdminToken.Token); + var openingForm = FormData.OpeningForm("Opening"); + var form = await ngoAdminApi.CreateForm(electionRoundId, openingForm); + await ngoAdminApi.PublishForm(electionRoundId, form.Id); progressTask.Increment(1); progressTask.Increment(progressTask.MaxValue); @@ -30,7 +29,7 @@ public static async Task> Seed(INgoAdminApi ngoAdminApi new UpdateFormResponse { Id = form.Id, - Questions = FormData.OpeningForm.Questions + Questions = openingForm.Questions }, // ... ]; diff --git a/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs b/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs index 36ecf6e74..a071736b0 100644 --- a/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs @@ -9,18 +9,15 @@ namespace SubmissionsFaker.Seeders; public class IncidentReportingFormSeeder { public static async Task> Seed(INgoAdminApi ngoAdminApi, - LoginResponse ngoAdminToken, - string electionRoundId, + Guid electionRoundId, ProgressTask progressTask) { progressTask.MaxValue(1); progressTask.StartTask(); - var openingForm = await ngoAdminApi.CreateForm(electionRoundId, - IncidentReportingFormData.IncidentReporting with { FormType = "IncidentReporting" }, ngoAdminToken.Token); - await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, IncidentReportingFormData.IncidentReporting, - ngoAdminToken.Token); - await ngoAdminApi.PublishForm(electionRoundId, openingForm.Id, ngoAdminToken.Token); + var form = IncidentReportingFormData.IncidentReporting("IR"); + var incidentReportingForm = await ngoAdminApi.CreateForm(electionRoundId, form); + await ngoAdminApi.PublishForm(electionRoundId, incidentReportingForm.Id); progressTask.Increment(1); progressTask.Increment(progressTask.MaxValue); @@ -30,8 +27,8 @@ await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, IncidentReportingF [ new UpdateFormResponse { - Id = openingForm.Id, - Questions = IncidentReportingFormData.IncidentReporting.Questions, + Id = incidentReportingForm.Id, + Questions = form.Questions, }, // ... ]; diff --git a/utils/SubmissionsFaker/Seeders/ObserversSeeder.cs b/utils/SubmissionsFaker/Seeders/ObserversSeeder.cs index b0dd3fb34..a5d490547 100644 --- a/utils/SubmissionsFaker/Seeders/ObserversSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/ObserversSeeder.cs @@ -9,22 +9,21 @@ namespace SubmissionsFaker.Seeders; public class ObserversSeeder { public static async Task> Seed(IPlatformAdminApi platformAdminApi, - LoginResponse platformAdminToken, List observers, - string electionRoundId, - string monitoringNgoId, + Guid electionRoundId, + Guid monitoringNgoId, ProgressTask progressTask) { progressTask.StartTask(); var observerIds = new List(); - foreach (var observersChunk in observers.Chunk(Consts.CHUNK_SIZE)) + foreach (var observersChunk in observers.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = new List>(); foreach (var observer in observersChunk) { - var createTask = platformAdminApi.CreateObserver(observer, platformAdminToken.Token); + var createTask = platformAdminApi.CreateObserver(observer); tasks.Add(createTask); } @@ -33,12 +32,12 @@ public static async Task> Seed(IPlatformAdminApi platformAd observerIds.AddRange(observersChunkIds); } - foreach (var observersIdsChunk in observerIds.Chunk(Consts.CHUNK_SIZE)) + foreach (var observersIdsChunk in observerIds.Chunk(SeederVars.CHUNK_SIZE)) { var tasks = new List>(); foreach (var observer in observersIdsChunk) { - var assignTask = platformAdminApi.AssignObserverToMonitoring(electionRoundId, monitoringNgoId, new AssignObserverRequest(observer.Id), platformAdminToken.Token); + var assignTask = platformAdminApi.AssignObserverToMonitoring(electionRoundId, monitoringNgoId, new AssignObserverRequest(observer.Id)); tasks.Add(assignTask); } diff --git a/utils/SubmissionsFaker/SubmissionsFaker.csproj b/utils/SubmissionsFaker/SubmissionsFaker.csproj index 08c997908..6c11aab9d 100644 --- a/utils/SubmissionsFaker/SubmissionsFaker.csproj +++ b/utils/SubmissionsFaker/SubmissionsFaker.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/web/package.json b/web/package.json index 93b39f1cb..2a0481967 100644 --- a/web/package.json +++ b/web/package.json @@ -59,6 +59,7 @@ "@tiptap/react": "^2.8.0", "@tiptap/starter-kit": "^2.8.0", "@types/lodash": "^4.17.7", + "@types/papaparse": "^5.3.15", "@uidotdev/usehooks": "^2.4.1", "axios": "^1.6.2", "chart.js": "^4.4.2", @@ -74,6 +75,7 @@ "i18next-browser-languagedetector": "^8.0.0", "lodash": "^4.17.21", "lucide-react": "^0.294.0", + "papaparse": "^5.4.1", "qs": "^6.12.0", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 833ea06b0..6e50d8d38 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: '@types/lodash': specifier: ^4.17.7 version: 4.17.7 + '@types/papaparse': + specifier: ^5.3.15 + version: 5.3.15 '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -179,6 +182,9 @@ importers: lucide-react: specifier: ^0.294.0 version: 0.294.0(react@18.2.0) + papaparse: + specifier: ^5.4.1 + version: 5.4.1 qs: specifier: ^6.12.0 version: 6.12.0 @@ -2121,6 +2127,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/papaparse@5.3.15': + resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2302,8 +2311,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -2490,8 +2499,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001599: - resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} + caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} @@ -3834,6 +3843,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + papaparse@5.4.1: + resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4616,8 +4628,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -6848,6 +6860,10 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/papaparse@5.3.15': + dependencies: + '@types/node': 20.5.1 + '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.11': {} @@ -7092,7 +7108,7 @@ snapshots: acorn@8.11.3: {} - acorn@8.12.1: + acorn@8.14.0: optional: true agent-base@6.0.2: @@ -7220,7 +7236,7 @@ snapshots: autoprefixer@10.4.14(postcss@8.4.27): dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001599 + caniuse-lite: 1.0.30001677 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -7264,7 +7280,7 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001599 + caniuse-lite: 1.0.30001677 electron-to-chromium: 1.4.713 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -7298,7 +7314,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001599: {} + caniuse-lite@1.0.30001677: {} chai@4.4.1: dependencies: @@ -8514,7 +8530,7 @@ snapshots: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.7.0 + tslib: 2.8.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -8846,6 +8862,8 @@ snapshots: p-try@2.2.0: {} + papaparse@5.4.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -9593,7 +9611,7 @@ snapshots: terser@5.29.2: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 optional: true @@ -9685,7 +9703,7 @@ snapshots: tslib@2.6.2: {} - tslib@2.7.0: + tslib@2.8.1: optional: true type-check@0.4.0: diff --git a/web/src/common/data-source-store.ts b/web/src/common/data-source-store.ts new file mode 100644 index 000000000..8ed3d1962 --- /dev/null +++ b/web/src/common/data-source-store.ts @@ -0,0 +1,23 @@ +import { DataSources } from './types'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +type DataSourceStore = { + dataSource: DataSources; + setDataSource: (dataSource: DataSources) => void; +}; + +const useDataSourceStore = create( + persist( + (set) => ({ + dataSource: DataSources.Ngo, + setDataSource: (dataSource: DataSources) => { + set({ dataSource }); + }, + }), + { name: 'data-source' } + ) +); + +export const useDataSource = () => useDataSourceStore((state) => state.dataSource); +export const useSetDataSource = () => useDataSourceStore((state) => state.setDataSource); diff --git a/web/src/common/types.ts b/web/src/common/types.ts index b62d16787..c9cc25dfd 100644 --- a/web/src/common/types.ts +++ b/web/src/common/types.ts @@ -180,16 +180,11 @@ export const MultiSelectAnswerSchema = BaseAnswerSchema.extend({ }); export type MultiSelectAnswer = z.infer; -export type ElectionRoundMonitoring = { - monitoringNgoId: string; - electionRoundId: string; - title: string; - englishTitle: string; - startDate: string; - country: string; - countryId: string; - isMonitoringNgoForCitizenReporting: boolean; -}; +export enum ElectionRoundStatus { + NotStarted = 'NotStarted', + Started = 'Started', + Archived = 'Archived', +} export type LevelNode = { id: number; @@ -235,7 +230,15 @@ export type HistogramData = { [bucket: string]: number; }; -export const ZFormType = z.enum(['PSI', 'Opening', 'Voting', 'ClosingAndCounting', 'CitizenReporting', 'IncidentReporting','Other']); +export const ZFormType = z.enum([ + 'PSI', + 'Opening', + 'Voting', + 'ClosingAndCounting', + 'CitizenReporting', + 'IncidentReporting', + 'Other', +]); export type FormType = z.infer; @@ -245,7 +248,6 @@ export type TranslationStatus = z.infer; const ZLanguagesTranslationStatus = z.record(z.string(), ZTranslationStatus); export type LanguagesTranslationStatus = z.infer; - export interface Country { id: string; iso2: string; @@ -260,4 +262,23 @@ export interface Language { code: string; name: string; nativeName: string; -} \ No newline at end of file +} + +export interface CoalitionMember { + id: string; + name: string; +} +export interface Coalition { + id: string; + isInCoalition: boolean; + name: string; + leaderId: string; + leaderName: string; + numberOfMembers: number; + members: CoalitionMember[]; +} + +export enum DataSources { + Ngo = 'ngo', + Coalition = 'coalition', +} diff --git a/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx new file mode 100644 index 000000000..fff967993 --- /dev/null +++ b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx @@ -0,0 +1,70 @@ +import { useDataSource, useSetDataSource } from '@/common/data-source-store'; +import { DataSources, type FunctionComponent } from '@/common/types'; +import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { useNavigate, useSearch } from '@tanstack/react-router'; +import { useCallback, useEffect, useState } from 'react'; +import { Label } from '../ui/label'; +import { Switch } from '../ui/switch'; +import { FILTER_KEY } from '@/features/filtering/filtering-enums'; +import { omit } from '../../lib/utils'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; + +export function DataSourceSwitcher(): FunctionComponent { + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + + const navigate = useNavigate(); + + const search: any = useSearch({ + strict: false, + }); + + const dataSource = useDataSource(); + const setDataSource = useSetDataSource(); + + const navigateHandler = useCallback( + (dataSource: DataSources) => { + void navigate({ + search: (prev) => { + const newSearch: Record = { + ...prev, + dataSource, + }; + setDataSource(dataSource); + if (dataSource === DataSources.Ngo) { + return omit(newSearch, FILTER_KEY.CoalitionMemberId); + } + + return newSearch; + }, + }); + }, + [navigate, dataSource] + ); + const [isCoalition, setIsCoalition] = useState(false); + + useEffect(() => { + if (search.dataSource === undefined) { + navigateHandler(dataSource ?? DataSources.Ngo); + return; + } + + setIsCoalition((search.dataSource ?? dataSource) === DataSources.Coalition); + }, [search.dataSource]); + + return electionRound?.isCoalitionLeader ? ( +
+ navigateHandler(checked ? DataSources.Coalition : DataSources.Ngo)} + className='data-[state=checked]:bg-primary' + /> + +
+ ) : ( + <> + ); +} diff --git a/web/src/components/dialogs/CreateDialog/index.tsx b/web/src/components/dialogs/CreateDialog/index.tsx index b815b8b4c..f5ace9565 100644 --- a/web/src/components/dialogs/CreateDialog/index.tsx +++ b/web/src/components/dialogs/CreateDialog/index.tsx @@ -12,6 +12,9 @@ import { DialogTrigger, } from '@/components/ui/dialog'; import { useTranslation } from "react-i18next"; +import { useCurrentElectionRoundStore } from "@/context/election-round.store"; +import { useElectionRoundDetails } from "@/features/election-event/hooks/election-event-hooks"; +import { ElectionRoundStatus } from "@/common/types"; interface Props { title?: ReactNode; @@ -20,10 +23,13 @@ interface Props { } const CreateDialog = ({ title, description, children }: Props): ReactNode => { + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + return ( - diff --git a/web/src/components/layout/Header/Header.tsx b/web/src/components/layout/Header/Header.tsx index 22d9992a1..e737c3ee7 100644 --- a/web/src/components/layout/Header/Header.tsx +++ b/web/src/components/layout/Header/Header.tsx @@ -5,8 +5,10 @@ import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, + DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Skeleton } from '@/components/ui/skeleton'; @@ -18,13 +20,15 @@ import { sleep } from '@/lib/utils'; import { queryClient } from '@/main'; import { Disclosure, Menu, Transition } from '@headlessui/react'; import { Bars3Icon, ChevronDownIcon, XMarkIcon } from '@heroicons/react/24/outline'; -import { UserCircleIcon } from '@heroicons/react/24/solid'; +import { PauseCircleIcon, PlayCircleIcon, StopCircleIcon, UserCircleIcon } from '@heroicons/react/24/solid'; import { useQuery } from '@tanstack/react-query'; import { Link, useNavigate, useRouter } from '@tanstack/react-router'; import clsx from 'clsx'; -import { Fragment, useContext, useEffect, useState } from 'react'; -import type { ElectionRoundMonitoring, FunctionComponent } from '../../../common/types'; +import { sortBy } from 'lodash'; +import { Fragment, useContext, useEffect, useMemo, useState } from 'react'; +import { ElectionRoundStatus, type FunctionComponent } from '../../../common/types'; import Logo from './Logo'; +import { ElectionEvent } from '@/features/election-event/models/election-event'; const navigation = [ { name: 'Dashboard', to: '/', roles: ['PlatformAdmin', 'NgoAdmin'] }, @@ -41,16 +45,18 @@ const userNavigation: { name: string; to: string }[] = []; const Header = (): FunctionComponent => { const { userRole, signOut } = useContext(AuthContext); const navigate = useNavigate(); - const [selectedElectionRound, setSelectedElection] = useState(); + const [selectedElectionRound, setSelectedElection] = useState(); const router = useRouter(); - const { setCurrentElectionRoundId, setIsMonitoringNgoForCitizenReporting, currentElectionRoundId } = - useCurrentElectionRoundStore((s) => s); + const { + setCurrentElectionRoundId, + currentElectionRoundId, + } = useCurrentElectionRoundStore((s) => s); - const handleSelectElectionRound = async (electionRound?: ElectionRoundMonitoring): Promise => { - if (electionRound && selectedElectionRound?.electionRoundId != electionRound.electionRoundId) { + const handleSelectElectionRound = async (electionRound?: ElectionEvent): Promise => { + if (electionRound && selectedElectionRound?.id != electionRound.id ) { setSelectedElection(electionRound); - setCurrentElectionRoundId(electionRound.electionRoundId); - setIsMonitoringNgoForCitizenReporting(electionRound.isMonitoringNgoForCitizenReporting); + setCurrentElectionRoundId(electionRound.id); + sleep(1); await queryClient.invalidateQueries({ @@ -74,7 +80,11 @@ const Header = (): FunctionComponent => { const { status, data: electionRounds } = useQuery({ queryKey: electionRoundKeys.all, queryFn: async () => { - const response = await authApi.get<{ electionRounds: ElectionRoundMonitoring[] }>('/election-rounds:monitoring'); + const response = await authApi.get<{ electionRounds: ElectionEvent[] }>('/election-rounds:monitoring'); + + (response.data.electionRounds ?? []).forEach((er) => { + queryClient.setQueryData(electionRoundKeys.detail(er.id), er); + }); return response.data.electionRounds ?? []; }, staleTime: 0, @@ -83,11 +93,27 @@ const Header = (): FunctionComponent => { useEffect(() => { if (!!electionRounds) { - const electionRound = electionRounds.find((x) => x.electionRoundId === currentElectionRoundId); + const electionRound = electionRounds.find((x) => x.id === currentElectionRoundId); handleSelectElectionRound(electionRound ?? electionRounds[0]); } }, [electionRounds]); + const activeElections = useMemo(() => { + return sortBy( + [...(electionRounds ?? [])].filter((er) => er.status !== ElectionRoundStatus.Archived), + (er) => new Date(er.startDate).getTime(), + (er) => er.title + ); + }, [electionRounds]); + + const archivedElections = useMemo(() => { + return sortBy( + [...(electionRounds ?? [])].filter((er) => er.status === ElectionRoundStatus.Archived), + (er) => new Date(er.startDate).getTime(), + (er) => er.title + ); + }, [electionRounds]); + return ( {({ open }) => ( @@ -123,27 +149,58 @@ const Header = (): FunctionComponent => {
{status === 'pending' ? ( - + ) : ( - - {selectedElectionRound?.title} - + +
+
+ {selectedElectionRound?.title} +
+ +
{ - const electionRound = electionRounds?.find((er) => er.electionRoundId === value); + const electionRound = electionRounds?.find((er) => er.id === value); handleSelectElectionRound(electionRound); }}> - {electionRounds?.map((electionRound) => ( + Upcomming elections + + {activeElections?.map((electionRound) => ( + +
+ {electionRound?.status === ElectionRoundStatus.NotStarted ? ( + + ) : null} + {electionRound?.status === ElectionRoundStatus.Started ? ( + + ) : null} +
+ {electionRound.title} +
+
+
+ ))} + + Archived elections + {archivedElections?.map((electionRound) => ( - {electionRound.title} + key={electionRound.id} + value={electionRound.id}> +
+ + +
+ {electionRound.title} +
+
))}
diff --git a/web/src/components/layout/Layout.tsx b/web/src/components/layout/Layout.tsx index 3eb295268..6e6c7f9cc 100644 --- a/web/src/components/layout/Layout.tsx +++ b/web/src/components/layout/Layout.tsx @@ -4,7 +4,7 @@ import Breadcrumbs from './Breadcrumbs/Breadcrumbs'; import BackButton from './Breadcrumbs/BackButton'; interface LayoutProps { - title: string; + title?: string; subtitle?: string; enableBreadcrumbs?: boolean; breadcrumbs?: ReactNode; diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx index 8743a25e0..8e75d679b 100644 --- a/web/src/components/ui/dropdown-menu.tsx +++ b/web/src/components/ui/dropdown-menu.tsx @@ -1,40 +1,42 @@ -import * as React from 'react'; -import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { Check, ChevronRight, Circle } from 'lucide-react'; +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" -import { cn } from '@/lib/utils'; +import { cn } from "@/lib/utils" -const DropdownMenu = DropdownMenuPrimitive.Root; +const DropdownMenu = DropdownMenuPrimitive.Root -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger -const DropdownMenuGroup = DropdownMenuPrimitive.Group; +const DropdownMenuGroup = DropdownMenuPrimitive.Group -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +const DropdownMenuPortal = DropdownMenuPrimitive.Portal -const DropdownMenuSub = DropdownMenuPrimitive.Sub; +const DropdownMenuSub = DropdownMenuPrimitive.Sub -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean; + inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( + {...props} + > {children} - + -)); -DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< React.ElementRef, @@ -43,13 +45,14 @@ const DropdownMenuSubContent = React.forwardRef< -)); -DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< React.ElementRef, @@ -60,32 +63,32 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - 'z-50 min-w-[8rem] overflow-y-auto max-h-[12rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean; + inset?: boolean } >(({ className, inset, ...props }, ref) => ( -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, @@ -94,20 +97,22 @@ const DropdownMenuCheckboxItem = React.forwardRef< - + {...props} + > + - + {children} -)); -DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, @@ -116,46 +121,63 @@ const DropdownMenuRadioItem = React.forwardRef< - + {...props} + > + - + {children} -)); -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean; + inset?: boolean } >(({ className, inset, ...props }, ref) => ( -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)); -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { - return ; -}; -DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { DropdownMenu, @@ -173,4 +195,4 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -}; +} diff --git a/web/src/components/ui/dual-range-slider.tsx b/web/src/components/ui/dual-range-slider.tsx index c746aa262..9911bf412 100644 --- a/web/src/components/ui/dual-range-slider.tsx +++ b/web/src/components/ui/dual-range-slider.tsx @@ -1,5 +1,3 @@ -'use client'; - import * as React from 'react'; import * as SliderPrimitive from '@radix-ui/react-slider'; diff --git a/web/src/components/ui/file-uploader.tsx b/web/src/components/ui/file-uploader.tsx index 0f57943f1..759619e21 100644 --- a/web/src/components/ui/file-uploader.tsx +++ b/web/src/components/ui/file-uploader.tsx @@ -137,7 +137,7 @@ export function FileUploader(props: FileUploaderProps) { return (
- )} - + : null} {files?.length ? (
{files?.map((file, index) => ( diff --git a/web/src/components/ui/multiple-select-dropdown.tsx b/web/src/components/ui/multiple-select-dropdown.tsx index 591bd3ed2..0b268c6b8 100644 --- a/web/src/components/ui/multiple-select-dropdown.tsx +++ b/web/src/components/ui/multiple-select-dropdown.tsx @@ -138,8 +138,9 @@ export const MultiSelectDropdown = React.forwardRef {selectedValues.length > 0 ? ( @@ -159,7 +160,7 @@ export const MultiSelectDropdown = React.forwardRef ) : (
- {placeholder} + {placeholder}
)} diff --git a/web/src/components/ui/stepper.tsx b/web/src/components/ui/stepper.tsx index 562c9cd75..40005bd42 100644 --- a/web/src/components/ui/stepper.tsx +++ b/web/src/components/ui/stepper.tsx @@ -86,6 +86,16 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { // <---------- HOOKS ----------> +function usePrevious(value: T): T | undefined { + const ref = React.useRef() + + React.useEffect(() => { + ref.current = value + }, [value]) + + return ref.current +} + function useStepper() { const context = React.useContext(StepperContext) @@ -98,6 +108,8 @@ function useStepper() { const isLastStep = context.activeStep === context.steps.length - 1 const hasCompletedAllSteps = context.activeStep === context.steps.length + const previousActiveStep = usePrevious(context.activeStep) + const currentStep = context.steps[context.activeStep] const isOptionalStep = !!currentStep?.optional @@ -110,6 +122,7 @@ function useStepper() { isOptionalStep, isDisabledStep, currentStep, + previousActiveStep, } } @@ -147,7 +160,7 @@ interface StepOptions { responsive?: boolean checkIcon?: IconType errorIcon?: IconType - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void mobileBreakpoint?: string variant?: "circle" | "circle-alt" | "line" expandVerticalSteps?: boolean @@ -262,6 +275,7 @@ const Stepper = React.forwardRef( expandVerticalSteps, steps, scrollTracking, + styles, }} >
{ errorIcon?: IconType isCompletedStep?: boolean isKeepError?: boolean - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void } interface StepSharedProps extends StepProps { @@ -452,12 +466,16 @@ type VerticalStepProps = StepSharedProps & { } const verticalStepVariants = cva( - "flex flex-col relative transition-all duration-200", + [ + "flex flex-col relative transition-all duration-200", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", + ], { variants: { variant: { circle: cn( - "pb-[var(--step-gap)] gap-[var(--step-gap)]", + "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", @@ -501,12 +519,17 @@ const VerticalStep = React.forwardRef( scrollTracking, orientation, steps, + setStep, + isLastStep: isLastStepCurrentStep, + previousActiveStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 const localIsLoading = isLoading || state === "loading" const localIsError = isError || state === "error" + const isLastStep = index === steps.length - 1 + const active = variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext @@ -516,7 +539,27 @@ const VerticalStep = React.forwardRef( if (!expandVerticalSteps) { return ( - + { + if ( + // If the step is the first step and the previous step + // was the last step or if the step is not the first step + // This prevents initial scrolling when the stepper + // is located anywhere other than the top of the view. + scrollTracking && + ((index === 0 && + previousActiveStep && + previousActiveStep === steps.length) || + (index && index > 0)) + ) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} + className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up" + > {children} @@ -533,8 +576,7 @@ const VerticalStep = React.forwardRef( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", + isLastStepCurrentStep && "gap-[var(--step-gap)]", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -543,7 +585,8 @@ const VerticalStep = React.forwardRef( data-clickable={clickable || !!onClickStep} data-invalid={localIsError} onClick={() => - onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0) + onClickStep?.(index || 0, setStep) || + onClickStepGeneral?.(index || 0, setStep) } >
( "stepper__vertical-step-container", "flex items-center", variant === "line" && - "border-s-[3px] data-[active=true]:border-blue-500 py-2 ps-3", + "border-s-[3px] data-[active=true]:border-primary py-2 ps-3", styles?.["vertical-step-container"] )} > @@ -580,17 +623,9 @@ const VerticalStep = React.forwardRef( />
{ - if (scrollTracking) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} className={cn( "stepper__vertical-step-content", - "min-h-4", + !isLastStep && "min-h-4", variant !== "line" && "ps-[--step-icon-size]", variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] @@ -617,6 +652,7 @@ const HorizontalStep = React.forwardRef( errorIcon: errorIconContext, styles, steps, + setStep, } = useStepper() const { @@ -653,14 +689,14 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", variant === "circle-alt" && "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", variant === "circle" && - "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-2 [&:not(:last-child)]:after:me-2", + "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]", variant === "line" && - "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", + "flex-col flex-1 border-t-[3px] data-[active=true]:border-primary", styles?.["horizontal-step"] )} data-optional={steps[index || 0]?.optional} @@ -668,7 +704,7 @@ const HorizontalStep = React.forwardRef( data-active={active} data-invalid={localIsError} data-clickable={clickable} - onClick={() => onClickStep?.(index || 0)} + onClick={() => onClickStep?.(index || 0, setStep)} ref={ref} >
= S extends { getState: () => infer T; } ? T : never; -export type ReadonlyStoreApi = Pick, "getState" | "subscribe">; +export type ReadonlyStoreApi = Pick, 'getState' | 'subscribe'>; export type WithReact> = S & { getServerState?: () => ExtractState; }; @@ -19,14 +19,10 @@ export type WithReact> = S & { // not copied from zustand source export type ZustandStore = WithReact>; - export type CurrentElectionRoundState = { currentElectionRoundId: string; setCurrentElectionRoundId(electionRoundId: string): void; - - isMonitoringNgoForCitizenReporting: boolean; - setIsMonitoringNgoForCitizenReporting(isMonitoringNgoForCitizenReporting: boolean): void; -} +}; export type CurrentElectionRoundStoreType = ZustandStore; @@ -38,28 +34,23 @@ export const CurrentElectionRoundStoreProvider = ({ children }: PropsWithChildre if (!storeRef.current) { storeRef.current = create()( persist( - (set, get) => ({ + (set) => ({ currentElectionRoundId: '', setCurrentElectionRoundId: (electionRoundId: string) => set({ currentElectionRoundId: electionRoundId }), - - isMonitoringNgoForCitizenReporting: false, - setIsMonitoringNgoForCitizenReporting: (isMonitoringNgoForCitizenReporting: boolean) => set({ isMonitoringNgoForCitizenReporting }) }), { - name: 'current-election-round', // name of the item in the storage (must be unique), - })); + name: 'current-election-round' + } + ) + ); } return ( - - {children} - + {children} ); }; -export function useCurrentElectionRoundStore( - selector: (state: ExtractState) => U, -) { +export function useCurrentElectionRoundStore(selector: (state: ExtractState) => U) { const store = useContext(CurrentElectionRoundContext); - if (!store) throw "Missing StoreProvider"; + if (!store) throw 'Missing StoreProvider'; return useStoreWithEqualityFn(store, selector); } diff --git a/web/src/features/CitizenNotifications/CitizenNotificationMessageForm/CitizenNotificationMessageForm.tsx b/web/src/features/CitizenNotifications/CitizenNotificationMessageForm/CitizenNotificationMessageForm.tsx index 75705a16a..4334fdc94 100644 --- a/web/src/features/CitizenNotifications/CitizenNotificationMessageForm/CitizenNotificationMessageForm.tsx +++ b/web/src/features/CitizenNotifications/CitizenNotificationMessageForm/CitizenNotificationMessageForm.tsx @@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { authApi } from '@/common/auth-api'; -import type { FunctionComponent } from '@/common/types'; +import { ElectionRoundStatus, type FunctionComponent } from '@/common/types'; import { RichTextEditor } from '@/components/rich-text-editor'; import { toast } from '@/components/ui/use-toast'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; @@ -15,6 +15,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate, useRouter } from '@tanstack/react-router'; import { citizenNotificationsKeys } from '../hooks/citizen-notifications-queries'; import { Button } from '@/components/ui/button'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; const createPushMessageSchema = z.object({ title: z.string().min(1, { message: 'Your message must have a title before sending.' }), @@ -27,6 +28,8 @@ const createPushMessageSchema = z.object({ function CitizenNotificationMessageForm(): FunctionComponent { const navigate = useNavigate(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const router = useRouter(); const queryClient = useQueryClient(); @@ -113,7 +116,7 @@ function CitizenNotificationMessageForm(): FunctionComponent { />
- +
diff --git a/web/src/features/CitizenNotifications/CitizenNotificationsDashboard/CitizenNotificationsDashboard.tsx b/web/src/features/CitizenNotifications/CitizenNotificationsDashboard/CitizenNotificationsDashboard.tsx index 4116400c2..fad103b65 100644 --- a/web/src/features/CitizenNotifications/CitizenNotificationsDashboard/CitizenNotificationsDashboard.tsx +++ b/web/src/features/CitizenNotifications/CitizenNotificationsDashboard/CitizenNotificationsDashboard.tsx @@ -9,13 +9,14 @@ import type { CellContext, ColumnDef } from '@tanstack/react-table'; import { Plus } from 'lucide-react'; import { DateTimeFormat } from '@/common/formats'; -import type { FunctionComponent } from '@/common/types'; +import { ElectionRoundStatus, type FunctionComponent } from '@/common/types'; import type { TableCellProps } from '@/components/ui/DataTable/DataTable'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { format } from 'date-fns'; import { useCallback } from 'react'; import { useCitizenNotifications } from '../hooks/citizen-notifications-queries'; import { CitizenNotificationModel } from '../models/citizen-notification'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; function CitizenNotificationsDashboard(): FunctionComponent { const pushMessagesColDefs: ColumnDef[] = [ @@ -68,6 +69,7 @@ function CitizenNotificationsDashboard(): FunctionComponent { const navigate = useNavigate(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); const navigateToPushMessage = useCallback( (notificationId: string) => { @@ -82,8 +84,8 @@ function CitizenNotificationsDashboard(): FunctionComponent {
Push messages
- - diff --git a/web/src/features/election-event/components/Dashboard/Dashboard.tsx b/web/src/features/election-event/components/Dashboard/Dashboard.tsx index 4e047fe5d..8a1c583f2 100644 --- a/web/src/features/election-event/components/Dashboard/Dashboard.tsx +++ b/web/src/features/election-event/components/Dashboard/Dashboard.tsx @@ -5,7 +5,7 @@ import FormsDashboard from '@/features/forms/components/Dashboard/Dashboard'; import LocationsDashboard from '@/features/locations/components/Dashboard/Dashboard'; import PollingStationsDashboard from '@/features/polling-stations/components/Dashboard/Dashboard'; import { cn } from '@/lib/utils'; -import { getRouteApi } from '@tanstack/react-router'; +import { getRouteApi, useNavigate } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useElectionRoundDetails } from '../../hooks/election-event-hooks'; @@ -13,17 +13,16 @@ import GuidesDashboard from '../Guides/GuidesDashboard'; import ElectionEventDetails from '../ElectionEventDetails/ElectionEventDetails'; import { GuidePageType } from '../../models/guide'; import CitizenNotificationsDashboard from '@/features/CitizenNotifications/CitizenNotificationsDashboard/CitizenNotificationsDashboard'; +import { Route } from '@/routes/election-event/$tab'; -const routeApi = getRouteApi('/election-event/$tab'); export default function ElectionEventDashboard(): ReactElement { const { t } = useTranslation(); - const { tab } = routeApi.useParams(); + const { tab } = Route.useParams(); const [currentTab, setCurrentTab] = useState(tab); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); - const isMonitoringNgoForCitizenReporting = useCurrentElectionRoundStore((s) => s.isMonitoringNgoForCitizenReporting); - const navigate = routeApi.useNavigate(); + const navigate = useNavigate(); function handleTabChange(tab: string): void { setCurrentTab(tab); @@ -35,21 +34,20 @@ export default function ElectionEventDashboard(): ReactElement { } const { data: electionEvent } = useElectionRoundDetails(currentElectionRoundId); - return ( } backButton={<>}> {t('electionEvent.eventDetails.tabTitle')} {t('electionEvent.pollingStations.tabTitle')} - {isMonitoringNgoForCitizenReporting && ( + {electionEvent?.isMonitoringNgoForCitizenReporting && ( {t('electionEvent.locations.tabTitle')} )} {t('electionEvent.guides.observerGuidesTabTitle')} - {isMonitoringNgoForCitizenReporting && ( + {electionEvent?.isMonitoringNgoForCitizenReporting && ( <> {t('electionEvent.guides.citizenGuidesTabTitle')} @@ -68,7 +66,7 @@ export default function ElectionEventDashboard(): ReactElement { - {isMonitoringNgoForCitizenReporting && ( + {electionEvent?.isMonitoringNgoForCitizenReporting && ( @@ -78,7 +76,7 @@ export default function ElectionEventDashboard(): ReactElement { - {isMonitoringNgoForCitizenReporting && ( + {electionEvent?.isMonitoringNgoForCitizenReporting && ( <> diff --git a/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx b/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx index 2933cb1ab..5118d2016 100644 --- a/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx +++ b/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx @@ -6,52 +6,84 @@ import { cn } from '@/lib/utils'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { useTranslation } from 'react-i18next'; import { useElectionRoundDetails } from '../../hooks/election-event-hooks'; +import { useCoalitionDetails } from '../../hooks/coalition-hooks'; export default function ElectionEventDetails() { const { t } = useTranslation(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); const { data: electionEvent } = useElectionRoundDetails(currentElectionRoundId); + const { data: coalitionDetails } = useCoalitionDetails(currentElectionRoundId); return ( - - -
- - {t('electionEvent.eventDetails.cardTitle')} - -
- -
- -
-

{t('electionEvent.eventDetails.title')}

-

{electionEvent?.title}

-
-
-

{t('electionEvent.eventDetails.englishTitle')}

-

{electionEvent?.englishTitle}

-
-
-

{t('electionEvent.eventDetails.country')}

-

{electionEvent?.countryFullName}

-
-
-

{t('electionEvent.eventDetails.startDate')}

-

{electionEvent?.startDate}

-
+
+ + +
+ + {t('electionEvent.eventDetails.cardTitle')} + +
+ +
+ +
+

{t('electionEvent.eventDetails.title')}

+

{electionEvent?.title}

+
+
+

{t('electionEvent.eventDetails.englishTitle')}

+

{electionEvent?.englishTitle}

+
+
+

{t('electionEvent.eventDetails.country')}

+

{electionEvent?.countryFullName}

+
+
+

{t('electionEvent.eventDetails.startDate')}

+

{electionEvent?.startDate}

+
-
-

{t('electionEvent.eventDetails.status')}

- - {electionEvent?.status} - -
-
-
+
+

{t('electionEvent.eventDetails.status')}

+ + {electionEvent?.status} + +
+ + + {coalitionDetails?.isInCoalition && ( + + +
+ + {t('electionEvent.eventDetails.coalition.cardTitle')} + +
+ +
+ +
+

{t('electionEvent.eventDetails.coalition.coalitionName')}

+

{coalitionDetails.name}

+
+ +
+

{t('electionEvent.eventDetails.coalition.leaderName')}

+

{coalitionDetails.leaderName}

+
+ +
+

{t('electionEvent.eventDetails.coalition.members')}

+

{coalitionDetails?.members.map((m) => m.name).join(', ')}

+
+
+
+ )} +
); } diff --git a/web/src/features/election-event/components/Guides/GuidesDashboard.tsx b/web/src/features/election-event/components/Guides/GuidesDashboard.tsx index bca4a45b1..70ce1478b 100644 --- a/web/src/features/election-event/components/Guides/GuidesDashboard.tsx +++ b/web/src/features/election-event/components/Guides/GuidesDashboard.tsx @@ -30,6 +30,8 @@ import { observerGuidesKeys, useObserverGuides } from '../../hooks/observer-guid import { GuideModel, GuidePageType, GuideType } from '../../models/guide'; import AddGuideDialog from './AddGuideDialog'; import EditGuideDialog from './EditGuideDialog'; +import { useElectionRoundDetails } from '../../hooks/election-event-hooks'; +import { ElectionRoundStatus } from '@/common/types'; export interface GuidesDashboardProps { guidePageType: GuidePageType; @@ -45,6 +47,7 @@ export default function GuidesDashboard({ guidePageType }: GuidesDashboardProps) const [newGuideType, setNewGuideType] = useState(undefined); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); async function handleDeleteGuide(guideId: string, guideName: string): Promise { if ( @@ -222,7 +225,7 @@ export default function GuidesDashboard({ guidePageType }: GuidesDashboardProps) : i18n.t('electionEvent.guides.citizenGuidesCardTitle')} - + + + + + +
+ ); +} + +export default EditFormAccessDialog; diff --git a/web/src/features/forms/components/EditForm/EditForm.tsx b/web/src/features/forms/components/EditForm/EditForm.tsx index 3fdc67932..dfeceed8b 100644 --- a/web/src/features/forms/components/EditForm/EditForm.tsx +++ b/web/src/features/forms/components/EditForm/EditForm.tsx @@ -34,7 +34,7 @@ import { import { queryClient } from '@/main'; import { Route } from '@/routes/forms_.$formId.edit'; import { useMutation } from '@tanstack/react-query'; -import { useBlocker, useNavigate } from '@tanstack/react-router'; +import { useBlocker, useNavigate, useRouter } from '@tanstack/react-router'; import { useEffect, useState } from 'react'; import { UpdateFormRequest } from '../../models/form'; import { formDetailsQueryOptions, formsKeys } from '../../queries'; @@ -222,6 +222,7 @@ export default function EditForm(): FunctionComponent { const confirm = useConfirm(); const [shouldExitEditor, setShouldExitEditor] = useState(false); const navigate = useNavigate(); + const router = useRouter(); const editQuestions = formData.questions.map((question) => { if (isNumberQuestion(question)) { @@ -429,6 +430,7 @@ export default function EditForm(): FunctionComponent { }); await queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId), type: 'all' }); + router.invalidate(); if (shouldExitEditor) { if ( diff --git a/web/src/features/forms/components/EditForm/EditFormDetails.tsx b/web/src/features/forms/components/EditForm/EditFormDetails.tsx index 984840ec6..ed526c537 100644 --- a/web/src/features/forms/components/EditForm/EditFormDetails.tsx +++ b/web/src/features/forms/components/EditForm/EditFormDetails.tsx @@ -12,6 +12,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { EditFormType } from './EditForm'; import { changeLanguageCode, mapFormType } from '@/lib/utils'; import { useEffect, useRef, useState } from 'react'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export interface EditFormDetailsProps { languageCode: string; @@ -20,7 +21,8 @@ export interface EditFormDetailsProps { function EditFormDetails({ languageCode }: EditFormDetailsProps) { const { t } = useTranslation(); const form = useFormContext(); - const isMonitoringNgoForCitizenReporting = useCurrentElectionRoundStore((s) => s.isMonitoringNgoForCitizenReporting); + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); const formType = useWatch({ control: form.control, name: 'formType' }); const icon = useWatch({ control: form.control, name: 'icon' }); @@ -141,7 +143,7 @@ function EditFormDetails({ languageCode }: EditFormDetailsProps) { {mapFormType(ZFormType.Values.ClosingAndCounting)} - {isMonitoringNgoForCitizenReporting && ( + {electionRound?.isMonitoringNgoForCitizenReporting && ( {mapFormType(ZFormType.Values.CitizenReporting)} @@ -157,7 +159,7 @@ function EditFormDetails({ languageCode }: EditFormDetailsProps) { )} /> - {formType === ZFormType.Values.CitizenReporting && isMonitoringNgoForCitizenReporting ? ( + {formType === ZFormType.Values.CitizenReporting && electionRound?.isMonitoringNgoForCitizenReporting ? ( <> { if (isNumberQuestion(question)) { @@ -237,6 +239,7 @@ export default function EditFormTranslation(): FunctionComponent { }); void queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); if (shouldExitEditor) { void navigate({ to: '/election-event/$tab', params: { tab: 'observer-forms' } }); diff --git a/web/src/features/forms/components/EditFormTranslation/EditFormTranslationDetails.tsx b/web/src/features/forms/components/EditFormTranslation/EditFormTranslationDetails.tsx index d347a37ed..ec550fdc9 100644 --- a/web/src/features/forms/components/EditFormTranslation/EditFormTranslationDetails.tsx +++ b/web/src/features/forms/components/EditFormTranslation/EditFormTranslationDetails.tsx @@ -10,6 +10,7 @@ import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { mapFormType } from '@/lib/utils'; import { useFormContext } from 'react-hook-form'; import { EditFormType } from '../EditForm/EditForm'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export interface EditFormTranslationDetailsProps { languageCode: string; @@ -18,8 +19,8 @@ export interface EditFormTranslationDetailsProps { function EditFormTranslationDetails({ languageCode }: EditFormTranslationDetailsProps) { const { t } = useTranslation(); const form = useFormContext(); - const isMonitoringNgoForCitizenReporting = useCurrentElectionRoundStore(s => s.isMonitoringNgoForCitizenReporting); - + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); return (
@@ -39,7 +40,7 @@ function EditFormTranslationDetails({ languageCode }: EditFormTranslationDetails {mapFormType(ZFormType.Values.Opening)} {mapFormType(ZFormType.Values.Voting)} {mapFormType(ZFormType.Values.ClosingAndCounting)} - {isMonitoringNgoForCitizenReporting && {mapFormType(ZFormType.Values.CitizenReporting)}} + {electionRound?.isMonitoringNgoForCitizenReporting && {mapFormType(ZFormType.Values.CitizenReporting)}} {mapFormType(ZFormType.Values.IncidentReporting)} {mapFormType(ZFormType.Values.Other)} diff --git a/web/src/features/forms/models/form.ts b/web/src/features/forms/models/form.ts index 2a22f2316..7a40471fd 100644 --- a/web/src/features/forms/models/form.ts +++ b/web/src/features/forms/models/form.ts @@ -12,6 +12,11 @@ export const FormStatusList: FormStatus[] = [ FormStatus.Obsolete ] +export interface FormAccessModel { + ngoId: string; + name: string; +} + export interface FormBase { id: string; formType: FormType; @@ -20,12 +25,14 @@ export interface FormBase { icon?: string; name: TranslatedString; description?: TranslatedString; + isFormOwner: boolean; status: FormStatus; languages: string[]; lastModifiedOn: string; lastModifiedBy: string; numberOfQuestions: number; languagesTranslationStatus: LanguagesTranslationStatus; + formAccess: FormAccessModel[] } export interface FormFull extends FormBase { diff --git a/web/src/features/forms/queries.ts b/web/src/features/forms/queries.ts index c7674ae9a..70d5988f5 100644 --- a/web/src/features/forms/queries.ts +++ b/web/src/features/forms/queries.ts @@ -3,6 +3,8 @@ import { DataTableParameters, PageResponse } from '@/common/types'; import { UseQueryResult, queryOptions, useQuery } from '@tanstack/react-query'; import { FormBase, FormFull } from './models/form'; import { buildURLSearchParams } from '@/lib/utils'; +import { queryClient } from '@/main'; +const STALE_TIME = 1000 * 60 * 5; // five minutes export const formsKeys = { all: (electionRoundId: string) => ['forms', electionRoundId] as const, @@ -11,6 +13,7 @@ export const formsKeys = { [...formsKeys.lists(electionRoundId), { ...params }] as const, details: (electionRoundId: string) => [...formsKeys.all(electionRoundId), 'detail'] as const, detail: (electionRoundId: string, id: string) => [...formsKeys.details(electionRoundId), id] as const, + baseDetails: (electionRoundId: string, id: string) => [...formsKeys.details(electionRoundId), 'base', id] as const, }; export const formDetailsQueryOptions = (electionRoundId: string, formId: string) => { @@ -58,8 +61,13 @@ export function useForms( throw new Error('Failed to fetch forms'); } + response.data.items.forEach((form) => { + queryClient.setQueryData(formsKeys.baseDetails(electionRoundId, form.id), form); + }); + return response.data; }, enabled: !!electionRoundId, + staleTime: STALE_TIME }); } diff --git a/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx b/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx index 0de0eacb2..6c889ba17 100644 --- a/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx +++ b/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx @@ -10,15 +10,15 @@ import TagsSelectFormField from '@/components/ui/tag-selector'; import { useToast } from '@/components/ui/use-toast'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { useMonitoringObserversTags } from '@/hooks/tags-queries'; -import { monitoringObserverDetailsQueryOptions, Route } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; +import { Route, monitoringObserverDetailsQueryOptions } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; import { useNavigate, useRouter } from '@tanstack/react-router'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +import { monitoringObserversKeys } from '../../hooks/monitoring-observers-queries'; import { MonitoringObserverStatus, UpdateMonitoringObserverRequest } from '../../models/monitoring-observer'; import { MonitorObserverBackButton } from '../MonitoringObserverBackButton'; -import { monitoringObserversKeys } from '../../hooks/monitoring-observers-queries'; export default function EditObserver() { const navigate = useNavigate(); @@ -80,7 +80,7 @@ export default function EditObserver() { request ); }, - onSuccess: (_, {electionRoundId}) => { + onSuccess: (_, { electionRoundId }) => { toast({ title: 'Success', description: 'Observer successfully updated', @@ -96,9 +96,7 @@ export default function EditObserver() { }); return ( - }> + }>
@@ -120,7 +118,7 @@ export default function EditObserver() { First name - + @@ -133,7 +131,7 @@ export default function EditObserver() { Last name - + @@ -146,7 +144,7 @@ export default function EditObserver() { Phone number - + @@ -164,6 +162,7 @@ export default function EditObserver() { defaultValue={field.value} onValueChange={field.onChange} placeholder='Observer tags' + disabled={!monitoringObserver.isOwnObserver} /> @@ -217,7 +216,7 @@ export default function EditObserver() { }}> Cancel -
diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx index 0025b325d..ff8e22f99 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx @@ -6,14 +6,13 @@ import { MonitoringObserverFormSubmissions } from '../MonitoringObserverFormSubm import type { FunctionComponent } from '@/common/types'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { monitoringObserverDetailsQueryOptions } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; import { Route } from '@/routes/monitoring-observers/view/$monitoringObserverId.$tab'; import { useSuspenseQuery } from '@tanstack/react-query'; +import { useNavigate } from '@tanstack/react-router'; +import { useState } from 'react'; import { MonitorObserverBackButton } from '../MonitoringObserverBackButton'; import { MonitoringObserverQuickReports } from '../MonitoringObserverQuickReports/MonitoringObserverQuickReports'; -import { useState } from 'react'; -import { useNavigate } from '@tanstack/react-router'; -import { MonitoringObserverIncidentReports } from '../MonitoringObserverIncidentReports/MonitoringObserverIncidentReports'; -import { monitoringObserverDetailsQueryOptions } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; export default function MonitoringObserverDetails(): FunctionComponent { const { monitoringObserverId, tab } = Route.useParams(); @@ -29,21 +28,19 @@ export default function MonitoringObserverDetails(): FunctionComponent { function handleTabChange(tab: string): void { setCurrentTab(tab); navigate({ - params(prev) { + params(prev: any) { return { ...prev, tab }; }, }); } return ( - } - title={`${monitoringObserver.firstName} ${monitoringObserver.lastName}`}> + } title={`${monitoringObserver.displayName}`}> - + Observer details Form responses Quick reports - Incident reports + {/* Incident reports */} @@ -54,9 +51,9 @@ export default function MonitoringObserverDetails(): FunctionComponent { - + {/* - + */} ); diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx index 166630e03..9f80bcbe0 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx @@ -7,17 +7,22 @@ import { PencilIcon } from '@heroicons/react/24/outline'; import { useNavigate } from '@tanstack/react-router'; import { DateTimeFormat } from '@/common/formats'; -import type { FunctionComponent } from '@/common/types'; +import { ElectionRoundStatus, type FunctionComponent } from '@/common/types'; +import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { monitoringObserverDetailsQueryOptions } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; import { Route } from '@/routes/monitoring-observers/view/$monitoringObserverId.$tab'; import { useSuspenseQuery } from '@tanstack/react-query'; import { format } from 'date-fns'; -import { useCurrentElectionRoundStore } from '@/context/election-round.store'; -import { monitoringObserverDetailsQueryOptions } from '@/routes/monitoring-observers/edit.$monitoringObserverId'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export default function MonitoringObserverDetailsView(): FunctionComponent { const { monitoringObserverId } = Route.useParams(); - const currentElectionRoundId = useCurrentElectionRoundStore(s => s.currentElectionRoundId); - const monitoringObserverQuery = useSuspenseQuery(monitoringObserverDetailsQueryOptions(currentElectionRoundId, monitoringObserverId)); + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const monitoringObserverQuery = useSuspenseQuery( + monitoringObserverDetailsQueryOptions(currentElectionRoundId, monitoringObserverId) + ); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const monitoringObserver = monitoringObserverQuery.data; const navigate = useNavigate(); @@ -33,7 +38,10 @@ export default function MonitoringObserverDetailsView(): FunctionComponent {
Monitoring observer details - @@ -43,9 +51,7 @@ export default function MonitoringObserverDetailsView(): FunctionComponent {

Name

-

- {monitoringObserver.firstName} {monitoringObserver.lastName} -

+

{monitoringObserver.displayName}

Email

@@ -61,7 +67,10 @@ export default function MonitoringObserverDetailsView(): FunctionComponent {

Last activity

-

{monitoringObserver.latestActivityAt ? format(monitoringObserver.latestActivityAt, DateTimeFormat) : '-'}

+

+ {' '} + {monitoringObserver.latestActivityAt ? format(monitoringObserver.latestActivityAt, DateTimeFormat) : '-'} +

Status

diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissions/MonitoringObserverFormSubmissions.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissions/MonitoringObserverFormSubmissions.tsx index 954b6e0f7..fb6b69a2d 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissions/MonitoringObserverFormSubmissions.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissions/MonitoringObserverFormSubmissions.tsx @@ -33,8 +33,7 @@ export function MonitoringObserverFormSubmissions(): FunctionComponent { const debouncedSearchText = useDebounce(searchText, 300); const handleSearchInput = (ev: ChangeEvent): void => { - const value = ev.currentTarget.value; - if (!value || value.length >= 2) setSearchText(ev.currentTarget.value); + setSearchText(ev.currentTarget.value); }; return ( diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissionsTable/MonitoringObserverFormSubmissionsTable.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissionsTable/MonitoringObserverFormSubmissionsTable.tsx index 435d40db1..f09240b87 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissionsTable/MonitoringObserverFormSubmissionsTable.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverFormSubmissionsTable/MonitoringObserverFormSubmissionsTable.tsx @@ -47,7 +47,7 @@ export function MonitoringObserverFormSubmissionsTable({ const navigateToFormSubmission = useCallback( (submissionId: string) => { - void navigate({ to: '/responses/$submissionId', params: { submissionId }}); + void navigate({ to: '/responses/form-submissions/$submissionId', params: { submissionId }}); }, [navigate] ); diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverIncidentReports/MonitoringObserverIncidentReports.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverIncidentReports/MonitoringObserverIncidentReports.tsx index aecdecede..1700db508 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverIncidentReports/MonitoringObserverIncidentReports.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverIncidentReports/MonitoringObserverIncidentReports.tsx @@ -33,8 +33,7 @@ export function MonitoringObserverIncidentReports(): FunctionComponent { const debouncedSearchText = useDebounce(searchText, 300); const handleSearchInput = (ev: ChangeEvent): void => { - const value = ev.currentTarget.value; - if (!value || value.length >= 2) setSearchText(ev.currentTarget.value); + setSearchText(ev.currentTarget.value); }; return ( diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverQuickReports/MonitoringObserverQuickReports.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverQuickReports/MonitoringObserverQuickReports.tsx index afa8268a6..cf8b16734 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverQuickReports/MonitoringObserverQuickReports.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverQuickReports/MonitoringObserverQuickReports.tsx @@ -14,17 +14,15 @@ import { observerQuickReportsColumns, quickReportsColumnVisibilityOptions } from '@/features/responses/utils/column-visibility-options'; +import { Route } from '@/routes/monitoring-observers/view/$monitoringObserverId.$tab'; import { Cog8ToothIcon, FunnelIcon } from '@heroicons/react/24/outline'; -import { getRouteApi } from '@tanstack/react-router'; import { useDebounce } from '@uidotdev/usehooks'; import { useState, type ChangeEvent } from 'react'; import { MonitoringObserverQuickReportsFilters } from '../MonitoringObserverQuickReportsFilters/MonitoringObserverQuickReportsFilters'; import { MonitoringObserverQuickReportsTable } from '../MonitoringObserverQuickReportsTable/MonitoringObserverQuickReportsTable'; -const routeApi = getRouteApi('/monitoring-observers/view/$monitoringObserverId/$tab'); - export function MonitoringObserverQuickReports(): FunctionComponent { - const search = routeApi.useSearch(); + const search = Route.useSearch(); const [isFiltering, setIsFiltering] = useState(() => Object.keys(search).length !== 0); const [columnsVisibility, setColumnsVisibility] = useState(observerQuickReportsColumns); @@ -33,8 +31,7 @@ export function MonitoringObserverQuickReports(): FunctionComponent { const debouncedSearchText = useDebounce(searchText, 300); const handleSearchInput = (ev: ChangeEvent): void => { - const value = ev.currentTarget.value; - if (!value || value.length >= 2) setSearchText(ev.currentTarget.value); + setSearchText(ev.currentTarget.value); }; return ( diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverQuickReportsTable/MonitoringObserverQuickReportsTable.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverQuickReportsTable/MonitoringObserverQuickReportsTable.tsx index 78d3a0690..12f7fc53a 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverQuickReportsTable/MonitoringObserverQuickReportsTable.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverQuickReportsTable/MonitoringObserverQuickReportsTable.tsx @@ -1,16 +1,15 @@ -import type { FunctionComponent } from '@/common/types'; +import { DataSources, type FunctionComponent } from '@/common/types'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; import { CardContent } from '@/components/ui/card'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { QuickReportFilterRequest } from '@/features/responses/components/QuickReportsTab/QuickReportsTab'; import { useQuickReports } from '@/features/responses/hooks/quick-reports'; import { observerQuickReportsColumnDefs } from '@/features/responses/utils/column-defs'; -import { getRouteApi } from '@tanstack/react-router'; +import { Route } from '@/routes/monitoring-observers/view/$monitoringObserverId.$tab'; +import { useNavigate } from '@tanstack/react-router'; import type { VisibilityState } from '@tanstack/react-table'; import { useDebounce } from '@uidotdev/usehooks'; import { useCallback, useMemo } from 'react'; -import type { MonitoringObserverDetailsRouteSearch } from '../../models/monitoring-observer'; - -const routeApi = getRouteApi('/monitoring-observers/view/$monitoringObserverId/$tab'); type QuickReportsTableByEntryProps = { columnsVisibility: VisibilityState; @@ -21,29 +20,30 @@ export function MonitoringObserverQuickReportsTable({ columnsVisibility, searchText, }: QuickReportsTableByEntryProps): FunctionComponent { - const navigate = routeApi.useNavigate(); - const { monitoringObserverId } = routeApi.useParams(); - const search = routeApi.useSearch(); + const navigate = useNavigate(); + const { monitoringObserverId } = Route.useParams(); + const search = Route.useSearch(); const debouncedSearch = useDebounce(search, 300); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); const queryParams = useMemo(() => { - const params = [ - ['formCodeFilter', searchText], - ['formTypeFilter', debouncedSearch.formTypeFilter], - ['hasFlaggedAnswers', debouncedSearch.hasFlaggedAnswers], - ['quickReportLocationType', debouncedSearch.quickReportLocationType], - ['level1Filter', debouncedSearch.level1Filter], - ['level2Filter', debouncedSearch.level2Filter], - ['level3Filter', debouncedSearch.level3Filter], - ['level4Filter', debouncedSearch.level4Filter], - ['level5Filter', debouncedSearch.level5Filter], - ['pollingStationNumberFilter', debouncedSearch.pollingStationNumberFilter], - ['monitoringObserverId', monitoringObserverId], - ['followUpStatus', debouncedSearch.quickReportFollowUpStatus], - ].filter(([_, value]) => value); + const params: QuickReportFilterRequest = { + quickReportLocationType: debouncedSearch.quickReportLocationType, + level1Filter: debouncedSearch.level1Filter, + level2Filter: debouncedSearch.level2Filter, + level3Filter: debouncedSearch.level3Filter, + level4Filter: debouncedSearch.level4Filter, + level5Filter: debouncedSearch.level5Filter, + pollingStationNumberFilter: debouncedSearch.pollingStationNumberFilter, + quickReportFollowUpStatus: debouncedSearch.quickReportFollowUpStatus, + incidentCategory: debouncedSearch.incidentCategory, + dataSource: DataSources.Ngo, + monitoringObserverId: debouncedSearch.monitoringObserverId, + coalitionMemberId: undefined, + searchText: searchText + }; - return Object.fromEntries(params) as MonitoringObserverDetailsRouteSearch; + return params; }, [searchText, debouncedSearch, monitoringObserverId]); const navigateToQuickReport = useCallback( diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportObserverDataTableRowActions.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportObserverDataTableRowActions.tsx new file mode 100644 index 000000000..34b87e1ee --- /dev/null +++ b/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportObserverDataTableRowActions.tsx @@ -0,0 +1,71 @@ + + +import { Row } from '@tanstack/react-table'; +import * as React from 'react'; + +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; + +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; +import { MoreHorizontal, Pencil, Trash2 } from 'lucide-react'; +import { ImportObserverRow } from './MonitoringObserversImport'; +import DeleteDialog from './modals/delete-modal'; +import EditDialog from './modals/edit-modal'; + +interface DataTableRowActionsProps { + row: Row; + updateObserver: (observer: ImportObserverRow) => void; + deleteObserver: (observer: ImportObserverRow) => void; +} + +export function ImportObserverDataTableRowActions({ row, deleteObserver, updateObserver }: DataTableRowActionsProps) { + const [dialogContent, setDialogContent] = React.useState(null); + const [showDeleteDialog, setShowDeleteDialog] = React.useState(false); + + const handleEditClick = () => { + setDialogContent(); + }; + + return ( + + + + + + + Actions + + + + + + Edit + + + setShowDeleteDialog(true)} className='text-red-600'> + + Delete + + + + + {dialogContent && {dialogContent}} + + + ); +} diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportedObserversDataTable.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportedObserversDataTable.tsx new file mode 100644 index 000000000..d7e51e2d9 --- /dev/null +++ b/web/src/features/monitoring-observers/components/MonitoringObserversImport/ImportedObserversDataTable.tsx @@ -0,0 +1,226 @@ +import { FunctionComponent } from '@/common/types'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table'; +import { ImportObserverRow } from './MonitoringObserversImport'; + +import { DataTablePagination } from '@/components/ui/DataTable/DataTablePagination'; +import { ColumnDef } from '@tanstack/react-table'; + +import { DataTableColumnHeader } from '@/components/ui/DataTable/DataTableColumnHeader'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; + +import { ExclamationTriangleIcon } from '@heroicons/react/24/solid'; +import { useMemo, useState } from 'react'; +import { ImportObserverDataTableRowActions } from './ImportObserverDataTableRowActions'; +import { Input } from '@/components/ui/input'; + +type ImportedObserversDataTableProps = { + data: ImportObserverRow[]; + updateObserver: (observer: ImportObserverRow) => void; + deleteObserver: (observer: ImportObserverRow) => void; +}; + +export function ImportedObserversDataTable({ + data, + updateObserver, + deleteObserver, +}: ImportedObserversDataTableProps): FunctionComponent { + const tableCols: ColumnDef[] = useMemo( + () => [ + { + accessorKey: 'firstName', + header: ({ column }) => , + cell: ({ row }) => + row.original.errors?.some((er) => er.path.some((path) => path === 'firstName')) ? ( +
+ {row.original.firstName} + + + + + + + + + +
+ {row.original.errors + ?.filter((error) => error.path.some((path) => path === 'firstName')) + .map((error) =>
{error.message}
)} +
+
+
+
+
+ ) : ( +
{row.original.firstName}
+ ), + }, + { + accessorKey: 'lastName', + header: ({ column }) => , + cell: ({ row }) => + row.original.errors?.some((er) => er.path.some((path) => path === 'lastName')) ? ( +
+ {row.original.lastName} + + + + + + + + + +
+ {row.original.errors + ?.filter((error) => error.path.some((path) => path === 'lastName')) + .map((error) =>
{error.message}
)} +
+
+
+
+
+ ) : ( +
{row.original.lastName}
+ ), + }, + { + accessorKey: 'email', + header: ({ column }) => , + cell: ({ row }) => + row.original.errors?.some((er) => er.path.some((path) => path === 'email')) ? ( +
+ {row.original.email} + + + + + + + + + +
+ {row.original.errors + ?.filter((error) => error.path.some((path) => path === 'email')) + .map((error) =>
{error.message}
)} +
+
+
+
+
+ ) : ( +
{row.original.email}
+ ), + }, + { + accessorKey: 'phoneNumber', + header: ({ column }) => , + cell: ({ row }) =>
{row.original.phoneNumber}
, + }, + + { + accessorKey: 'errors', + header: ({ column }) => , + cell: ({ row }) => + row.original.errors?.length ? ( + + + + + {row.original.errors?.length} + + + +
+ {row.original.errors?.map((error) =>
{error.message}
)} +
+
+
+
+ ) : null, + }, + + { + id: 'actions', + cell: ({ row }) => ( + + ), + }, + ], + [updateObserver, deleteObserver] + ); + const [globalFilter, setGlobalFilter] = useState(''); + + const table = useReactTable({ + columns: tableCols, + data, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + globalFilterFn: (row, columnId, filterValue) => { + // Custom filter function + return Object.values(row.original).some((value) => + String(value).toLowerCase().includes(filterValue.toLowerCase()) + ); + }, + state: { globalFilter }, + }); + + const rows = table.getFilteredRowModel().rows; + + return ( +
+
+ setGlobalFilter(e.target.value)} /> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + + {rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + + No results. + + + )} + +
+ +
+ ); +} diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversImport/MonitoringObserversImport.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversImport/MonitoringObserversImport.tsx new file mode 100644 index 000000000..e0a62f33c --- /dev/null +++ b/web/src/features/monitoring-observers/components/MonitoringObserversImport/MonitoringObserversImport.tsx @@ -0,0 +1,188 @@ +import { FunctionComponent } from '@/common/types'; +import Layout from '@/components/layout/Layout'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { FileUploader } from '@/components/ui/file-uploader'; +import { Separator } from '@/components/ui/separator'; +import Papa from 'papaparse'; +import { useMemo, useState } from 'react'; +import { ZodIssue, z } from 'zod'; + +import { authApi } from '@/common/auth-api'; +import { Button } from '@/components/ui/button'; +import { toast } from '@/components/ui/use-toast'; +import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { downloadImportExample } from '@/features/monitoring-observers/helpers'; +import { queryClient } from '@/main'; +import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; +import { useMutation } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { monitoringObserversKeys } from '../../hooks/monitoring-observers-queries'; +import { LoaderIcon } from 'lucide-react'; +import { ImportedObserversDataTable } from './ImportedObserversDataTable'; +import { useNavigate } from '@tanstack/react-router'; + +export const importObserversSchema = z.object({ + firstName: z + .string() + .min(1, { message: 'First name is required.' }) + .max(256, { message: 'First name cannot exceed 256 characters.' }), + lastName: z + .string() + .min(1, { message: 'Last name is required.' }) + .max(256, { message: 'Last name cannot exceed 256 characters.' }), + email: z + .string() + .min(1, { message: 'Email is required.' }) + .refine((value) => z.string().email().safeParse(value).success, { + message: 'Invalid email format.', + }), + phoneNumber: z.string().max(256, { message: 'Phone number cannot exceed 256 characters.' }).optional(), +}); + +export type ImportObserverRow = z.infer & { id: string; errors: ZodIssue[] }; + +export function MonitoringObserversImport(): FunctionComponent { + const [observers, setObservers] = useState([]); + const { t } = useTranslation('translation', { keyPrefix: 'observers.addObserver' }); + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const navigate = useNavigate(); + + function deleteObserver(observer: ImportObserverRow) { + setObservers((prev) => [...prev.filter((obs) => obs.id !== observer.id)]); + } + + function updateObserver(observer: ImportObserverRow) { + const validationResult = importObserversSchema.safeParse(observer); + + const observerWithErorrs = { + ...observer, + errors: validationResult.success ? [] : validationResult.error.errors, + }; + + setObservers((prevData) => prevData.map((o) => (o.id === observer.id ? { ...o, ...observerWithErorrs } : o))); + } + + const hasInvalidObservers = useMemo(() => { + return observers.some((observer) => observer.errors.length > 0); + }, [observers]); + + const { mutate, isPending } = useMutation({ + mutationFn: ({ electionRoundId, observers }: { electionRoundId: string; observers: ImportObserverRow[] }) => { + return authApi.post(`/election-rounds/${electionRoundId}/monitoring-observers`, { observers }); + }, + + onSuccess: (_, { electionRoundId }) => { + toast({ + title: 'Success', + description: t('onSuccess'), + }); + + queryClient.invalidateQueries({ queryKey: monitoringObserversKeys.all(electionRoundId) }); + navigate({ to: '/monitoring-observers' }); + }, + onError: () => { + toast({ + title: t('onError'), + description: 'Please contact tech support', + variant: 'destructive', + }); + }, + }); + + function handleImportObservers() { + mutate({ electionRoundId: currentElectionRoundId, observers }); + } + + return ( + + + + Import monitoring observer list + + + In order to successfully import a list of monitoring observers, please use the template provided below. + Download the template, fill it in with the observer information and then upload it. No other format is + accepted for import. + + + + +
+
+ + monitoring_observers_template.csv +
+
+ { + const file = files[0]; + if (!file) { + setObservers([]); + } else { + Papa.parse(file, { + header: true, + skipEmptyLines: true, + // worker: true, + transformHeader: (header) => header.charAt(0).toLowerCase() + header.slice(1), + async complete(results) { + if (results.errors.length) { + console.error('Parsing errors:', results.errors); + // Optionally show an error message to the user. + } + + const validatedObservers = results.data.map((observer) => { + const observerWithId = { + ...observer, + id: crypto.randomUUID(), + }; + + const validationResult = importObserversSchema.safeParse(observerWithId); + + return { + ...observerWithId, + errors: validationResult.success ? [] : validationResult.error.errors, + }; + }); + + setObservers(validatedObservers); + }, + }); + } + }} + /> +
+
+ {observers.length ? ( + + + Observers to be imported + +
+ Review the data and correct the errors before import {' '} + +
{' '} +
+ +
+ +
+ {' '} + {' '} +
+
+
+ ) : null} +
+ ); +} diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/delete-modal.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/delete-modal.tsx new file mode 100644 index 000000000..d51836ba3 --- /dev/null +++ b/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/delete-modal.tsx @@ -0,0 +1,50 @@ +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + } from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { ImportObserverRow } from "../MonitoringObserversImport"; + + type DeleteProps = { + observer: ImportObserverRow; + isOpen: boolean; + showActionToggle: (open: boolean) => void; + deleteObserver: (observer:ImportObserverRow)=>void; + }; + + export default function DeleteDialog({ + observer, + isOpen, + showActionToggle, + deleteObserver + }: DeleteProps) { + return ( + + + + Are you sure absolutely sure ? + + This action cannot be undone. You are about to delete {observer.firstName} {observer.lastName} {observer.email} + + + + Cancel + + + + + ); + } \ No newline at end of file diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/edit-modal.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/edit-modal.tsx new file mode 100644 index 000000000..192710381 --- /dev/null +++ b/web/src/features/monitoring-observers/components/MonitoringObserversImport/modals/edit-modal.tsx @@ -0,0 +1,115 @@ + +import { Button } from '@/components/ui/button'; +import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { ImportObserverRow, importObserversSchema } from '../MonitoringObserversImport'; +import { z } from 'zod'; +import { useEffect } from 'react'; + +type EditProps = { + observer: ImportObserverRow; + updateObserver: (observer: ImportObserverRow) => void; +}; + +export default function EditDialog({ observer, updateObserver }: EditProps) { + const editObserversSchema = importObserversSchema.extend({ + id: z.string(), + }); + + const form = useForm({ + resolver: zodResolver(editObserversSchema), + mode: 'onChange', + reValidateMode: 'onChange', + defaultValues: { + id: observer.id, + firstName: observer.firstName, + lastName: observer.lastName, + email: observer.email, + phoneNumber: observer.phoneNumber, + }, + }); + + useEffect(() => { + form.trigger(); + }, [form.trigger]); + + function onSubmit(updatedObserver: ImportObserverRow) { + updateObserver(updatedObserver); + } + + return ( + <> + + Edit Observer + +
+
+ + ( + + First name + + + + + + )} + /> + + ( + + Last name + + + + + + )} + /> + + ( + + Email + + + + + + )} + /> + + ( + + Phone number + + + + + + )} + /> + + + + +
+ + ); +} diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversDialog.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversDialog.tsx deleted file mode 100644 index 879d4c191..000000000 --- a/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversDialog.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { authApi } from '@/common/auth-api'; -import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Separator } from '@/components/ui/separator'; -import { toast } from '@/components/ui/use-toast'; -import { queryClient } from '@/main'; -import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; -import { useMutation } from '@tanstack/react-query'; -import { AxiosError } from 'axios'; - -import { FileUploader } from '@/components/ui/file-uploader'; -import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; -import { useCurrentElectionRoundStore } from '@/context/election-round.store'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useEffect } from 'react'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { downloadImportExample } from '../../helpers'; -import { monitoringObserversKeys } from '../../hooks/monitoring-observers-queries'; -export interface ImportMonitoringObserversDialogProps { - onImportError: (fileId: string) => void; - open: boolean; - onOpenChange: (open: any) => void; -} - -function ImportMonitoringObserversDialog({ onImportError, open, onOpenChange }: ImportMonitoringObserversDialogProps) { - const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); - - const importObserversSchema = z.object({ - file: z.array(z.instanceof(File)).length(1), - }); - - type ImportObserverType = z.infer; - - const form = useForm({ - resolver: zodResolver(importObserversSchema), - defaultValues: { - file: [], - }, - }); - - useEffect(() => { - if (form.formState.isSubmitSuccessful) { - form.reset({}, { keepValues: false }); - onOpenChange(false); - } - }, [form.formState.isSubmitSuccessful, form.reset]); - - const importObserversMutation = useMutation({ - mutationFn: ({ electionRoundId, file }: { electionRoundId: string; file: File }) => { - // create a new FormData object and append the file to it - const formData = new FormData(); - formData.append('file', file); - - return authApi.post(`/election-rounds/${electionRoundId}/monitoring-observers:import`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - }, - - onSuccess: (_, {electionRoundId}) => { - queryClient.invalidateQueries({ queryKey: monitoringObserversKeys.lists(electionRoundId) }); - queryClient.invalidateQueries({ queryKey: monitoringObserversKeys.tags(electionRoundId) }); - - toast({ - title: 'Success', - description: 'Import was successful', - }); - }, - - onError: (error: AxiosError, variables, ctx) => { - if (error.response?.status === 400) { - // @ts-ignore - const importErrorsFileId = error.response.data.id; - if (importErrorsFileId) { - onImportError(importErrorsFileId); - } else { - toast({ - title: 'Error importing monitoring observers', - description: 'Please contact Platform admins', - variant: 'destructive', - }); - } - } else { - toast({ - title: 'Error importing monitoring observers', - description: 'Please contact Platform admins', - variant: 'destructive', - }); - } - - onOpenChange(false); - }, - }); - - function onSubmit({ file }: ImportObserverType): void { - importObserversMutation.mutate({ electionRoundId: currentElectionRoundId, file: file[0]! }); - } - - return ( - - { - e.preventDefault(); - }} - onEscapeKeyDown={(e) => { - e.preventDefault(); - }}> - - Import monitoring observer list - - -
- In order to successfully import a list of monitoring observers, please use the template provided below. - Download the template, fill it in with the observer information and then upload it. No other format is - accepted for import. -
-
-
-
-

- Download template * -

-
-
- - monitoring_observers_template.csv -
-
28kb
-
- -
- - ( -
- - - - - - - -
- )} - /> - - - - - - - - - -
-
-
- ); -} - -export default ImportMonitoringObserversDialog; diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversErrorsDialog.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversErrorsDialog.tsx deleted file mode 100644 index b8c14553b..000000000 --- a/web/src/features/monitoring-observers/components/MonitoringObserversList/ImportMonitoringObserversErrorsDialog.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { authApi } from '@/common/auth-api'; -import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Separator } from '@/components/ui/separator'; -import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; - -export interface ImportMonitoringObserversErrorsDialogProps { - fileId: string; - open: boolean; - onOpenChange: (open: any) => void; -} - -function ImportMonitoringObserversErrorsDialog({ - fileId, - open, - onOpenChange -}: ImportMonitoringObserversErrorsDialogProps) { - - const downloadImportErrorsFile = async () => { - const res = await authApi.get(`/import-errors/${fileId}`, { responseType: "blob" }); - const csvData = res.data; - - const blob = new Blob([csvData], { type: 'text/csv' }); - const url = window.URL.createObjectURL(blob); - - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = 'import-errors.csv'; - - document.body.appendChild(a); - a.click(); - - window.URL.revokeObjectURL(url); - }; - - return ( - - { - e.preventDefault(); - }} onEscapeKeyDown={(e) => { - e.preventDefault(); - }}> - - Data import failed - - -
- We encountered issues during the data import process from your CSV file. - To assist you in resolving these issues, you can download a detailed error report by clicking the link provided below. - We kindly ask you to review this report thoroughly and address any errors before proceeding with the reimport of your data. -
-
-
-
-

- Download error response -

-
-
- - import-errors.csv -
-
-
- If you require any further assistance or have questions, please do not hesitate to reach out to our support team -
- -
- - - - - -
-
- ) -} - -export default ImportMonitoringObserversErrorsDialog; \ No newline at end of file diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx index 1a00ae0db..b267bbf94 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx @@ -3,7 +3,6 @@ import TableTagList from '@/components/table-tag-list/TableTagList'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { DataTableColumnHeader } from '@/components/ui/DataTable/DataTableColumnHeader'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; import { DropdownMenu, @@ -16,14 +15,17 @@ import { Separator } from '@/components/ui/separator'; import { useDialog } from '@/components/ui/use-dialog'; import { Cog8ToothIcon, EllipsisVerticalIcon, FunnelIcon, PaperAirplaneIcon } from '@heroicons/react/24/outline'; import { useMutation } from '@tanstack/react-query'; -import { useNavigate, useRouter } from '@tanstack/react-router'; +import { Link, useNavigate, useRouter } from '@tanstack/react-router'; import { CellContext, ColumnDef } from '@tanstack/react-table'; import { useEffect, useMemo, useState } from 'react'; import { DateTimeFormat } from '@/common/formats'; +import { ElectionRoundStatus } from '@/common/types'; import { TableCellProps } from '@/components/ui/DataTable/DataTable'; +import { DataTableColumnHeader } from '@/components/ui/DataTable/DataTableColumnHeader'; import { toast } from '@/components/ui/use-toast'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; import { FILTER_KEY } from '@/features/filtering/filtering-enums'; import { useFilteringContainer } from '@/features/filtering/hooks/useFilteringContainer'; import i18n from '@/i18n'; @@ -35,8 +37,6 @@ import { Plus } from 'lucide-react'; import { MonitoringObserversListFilters } from '../../filtering/MonitoringObserversListFilters'; import { monitoringObserversKeys, useMonitoringObservers } from '../../hooks/monitoring-observers-queries'; import { MonitoringObserver, MonitoringObserverStatus } from '../../models/monitoring-observer'; -import ImportMonitoringObserversDialog from '../MonitoringObserversList/ImportMonitoringObserversDialog'; -import ImportMonitoringObserversErrorsDialog from '../MonitoringObserversList/ImportMonitoringObserversErrorsDialog'; import ConfirmResendInvitationDialog from './ConfirmResendInvitationDialog'; import CreateMonitoringObserverDialog from './CreateMonitoringObserverDialog'; @@ -45,32 +45,27 @@ function MonitoringObserversList() { const router = useRouter(); const search = Route.useSearch(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); const monitoringObserverColDefs: ColumnDef[] = useMemo(() => { return [ { + id: 'displayName', header: ({ column }) => , - accessorKey: 'name', + accessorFn: (row) => row.displayName, enableSorting: true, enableGlobalFilter: true, - cell: ({ - row: { - original: { firstName, lastName }, - }, - }) => ( -

- {firstName} {lastName} -

- ), }, { + id: 'email', header: ({ column }) => , - accessorKey: 'email', + accessorFn: (row) => row.email, enableSorting: true, }, { + id: 'tags', header: ({ column }) => , - accessorKey: 'tags', + accessorFn: (row) => row.tags, cell: ({ row: { original: { tags }, @@ -78,13 +73,15 @@ function MonitoringObserversList() { }) => , }, { + id: 'phoneNumber', header: ({ column }) => , - accessorKey: 'phoneNumber', + accessorFn: (row) => row.phoneNumber, enableSorting: true, }, { + id: 'status', header: ({ column }) => , - accessorKey: 'status', + accessorFn: (row) => row.status, enableSorting: true, cell: ({ row: { @@ -93,8 +90,9 @@ function MonitoringObserversList() { }) => {status}, }, { + id: 'latestActivityAt', header: ({ column }) => , - accessorKey: 'latestActivityAt', + accessorFn: (row) => row.latestActivityAt, enableSorting: true, cell: ({ row: { @@ -103,8 +101,8 @@ function MonitoringObserversList() { }) =>

{latestActivityAt ? format(latestActivityAt, DateTimeFormat) : '-'}

, }, { + id: 'actions', header: '', - accessorKey: 'action', enableSorting: true, cell: ({ row }) => ( @@ -113,9 +111,16 @@ function MonitoringObserversList() { navigateToObserver(row.original.id)}>View - navigateToEdit(row.original.id)}>Edit navigateToEdit(row.original.id)}> + Edit + + handleResendInviteToObserver(row.original.id)}> Resend invitation email @@ -130,11 +135,9 @@ function MonitoringObserversList() { const debouncedSearch = useDebounce(search, 300); const debouncedSearchText = useDebounce(searchText, 300); - const [importErrorsFileId, setImportErrorsFileId] = useState(); const [monitoringObserverId, setMonitoringObserverId] = useState(); const createMonitoringObserverDialog = useDialog(); const importMonitoringObserversDialog = useDialog(); - const importMonitoringObserverErrorsDialog = useDialog(); const confirmResendInvitesDialog = useDialog(); const { filteringIsActive, navigateHandler } = useFilteringContainer(); const [filtersExpanded, setFiltersExpanded] = useState(false); @@ -255,42 +258,35 @@ function MonitoringObserversList() { Monitoring observers list -
- {!!importErrorsFileId && ( - - )} +
+ + + - { - setImportErrorsFileId(fileId); - importMonitoringObserverErrorsDialog.trigger(); - }} - /> - @@ -315,7 +311,10 @@ function MonitoringObserversList() { Export monitoring observer list - diff --git a/web/src/features/monitoring-observers/components/PushMessageDetails/PushMessageDetails.tsx b/web/src/features/monitoring-observers/components/PushMessageDetails/PushMessageDetails.tsx index ade080c45..d42e25b7f 100644 --- a/web/src/features/monitoring-observers/components/PushMessageDetails/PushMessageDetails.tsx +++ b/web/src/features/monitoring-observers/components/PushMessageDetails/PushMessageDetails.tsx @@ -18,7 +18,9 @@ export default function PushMessageDetails(): FunctionComponent { const { data: pushMessage } = useSuspenseQuery(pushMessageDetailsQueryOptions(currentElectionRoundId, id)); return ( - } title=''> + } + breadcrumbs={<>} + title={id}>
diff --git a/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx b/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx index ae7889e1e..5dd7abce4 100644 --- a/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx +++ b/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx @@ -32,7 +32,6 @@ import type { SendPushNotificationRequest } from '../../models/push-message'; import type { PushMessageTargetedObserversSearchParams } from '../../models/search-params'; import { targetedMonitoringObserverColDefs } from '../../utils/column-defs'; import { FILTER_KEY } from '@/features/filtering/filtering-enums'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { QuickReportsIncidentCategoryFilter } from '@/features/filtering/components/QuickReportsIncidentCategoryFilter'; import { FormSubmissionsFollowUpFilter } from '@/features/filtering/components/FormSubmissionsFollowUpFilter'; import { QuickReportsFollowUpFilter } from '@/features/filtering/components/QuickReportsFollowUpFilter'; @@ -89,7 +88,6 @@ function PushMessageForm(): FunctionComponent { formId: debouncedSearch.formId, fromDateFilter: debouncedSearch.submissionsFromDate?.toISOString(), toDateFilter: debouncedSearch.submissionsToDate?.toISOString(), - isCompletedFilter: toBoolean(debouncedSearch.formIsCompleted), monitoringObserverStatus: debouncedSearch.monitoringObserverStatus, quickReportIncidentCategory: debouncedSearch.incidentCategory, quickReportFollowUpStatus: debouncedSearch.quickReportFollowUpStatus, @@ -208,7 +206,6 @@ function PushMessageForm(): FunctionComponent { - diff --git a/web/src/features/monitoring-observers/components/PushMessages/PushMessages.tsx b/web/src/features/monitoring-observers/components/PushMessages/PushMessages.tsx index d4cb54879..e82a8d1bb 100644 --- a/web/src/features/monitoring-observers/components/PushMessages/PushMessages.tsx +++ b/web/src/features/monitoring-observers/components/PushMessages/PushMessages.tsx @@ -9,13 +9,14 @@ import type { CellContext, ColumnDef } from '@tanstack/react-table'; import { Plus } from 'lucide-react'; import { DateTimeFormat } from '@/common/formats'; -import type { FunctionComponent } from '@/common/types'; +import { ElectionRoundStatus, type FunctionComponent } from '@/common/types'; import type { TableCellProps } from '@/components/ui/DataTable/DataTable'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { format } from 'date-fns'; import { useCallback } from 'react'; import { usePushMessages } from '../../hooks/push-messages-queries'; import type { PushMessageModel } from '../../models/push-message'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; function PushMessages(): FunctionComponent { const pushMessagesColDefs: ColumnDef[] = [ @@ -77,6 +78,7 @@ function PushMessages(): FunctionComponent { const navigate = useNavigate(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); const navigateToPushMessage = useCallback( (id: string) => { @@ -91,8 +93,11 @@ function PushMessages(): FunctionComponent {
Push messages
- - diff --git a/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx b/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx index 1091b64db..3534eb42a 100644 --- a/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx +++ b/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx @@ -30,7 +30,7 @@ export const MonitoringObserverTagsSelect: FC onValueChange={toggleTagsFilter} placeholder='Observer tags' defaultValue={currentTags} - className='text-slate-700' + className='text-slate-900' selectionDisplay={
Observer tags diff --git a/web/src/features/monitoring-observers/models/monitoring-observer.ts b/web/src/features/monitoring-observers/models/monitoring-observer.ts index c2c57589b..4ee59c76d 100644 --- a/web/src/features/monitoring-observers/models/monitoring-observer.ts +++ b/web/src/features/monitoring-observers/models/monitoring-observer.ts @@ -1,7 +1,7 @@ /* eslint-disable unicorn/prefer-top-level-await */ import { FormSubmissionFollowUpStatus, IncidentReportFollowUpStatus, QuickReportFollowUpStatus } from '@/common/types'; import { IncidentReportLocationType } from '@/features/responses/models/incident-report'; -import { QuickReportLocationType } from '@/features/responses/models/quick-report'; +import { IncidentCategory, QuickReportLocationType } from '@/features/responses/models/quick-report'; import { z } from 'zod'; export enum MonitoringObserverStatus { @@ -14,10 +14,12 @@ export interface MonitoringObserver { id: string; firstName: string; lastName: string; + displayName: string; email: string; status: MonitoringObserverStatus; phoneNumber: string; tags: string[]; + isOwnObserver: boolean; latestActivityAt?: string; } @@ -36,6 +38,7 @@ export const monitoringObserverDetailsRouteSearchSchema = z.object({ quickReportFollowUpStatus: z.nativeEnum(QuickReportFollowUpStatus).optional(), quickReportLocationType: z.nativeEnum(QuickReportLocationType).optional(), + incidentCategory: z.nativeEnum(IncidentCategory).optional(), incidentReportFollowUpStatus: z.nativeEnum(IncidentReportFollowUpStatus).optional(), incidentReportLocationType: z.nativeEnum(IncidentReportLocationType).optional(), diff --git a/web/src/features/monitoring-observers/models/push-message.ts b/web/src/features/monitoring-observers/models/push-message.ts index 0c95cd772..6091afa99 100644 --- a/web/src/features/monitoring-observers/models/push-message.ts +++ b/web/src/features/monitoring-observers/models/push-message.ts @@ -41,7 +41,6 @@ export interface SendPushNotificationRequest { questionsAnswered?: QuestionsAnswered; formId?: string; formTypeFilter?: FormType; - formIsCompleted?: boolean; followUpStatus?: FormSubmissionFollowUpStatus; monitoringObserverId?: string; hasFlaggedAnswers?: boolean; diff --git a/web/src/features/monitoring-observers/models/search-params.ts b/web/src/features/monitoring-observers/models/search-params.ts index 027a2332f..fcca16929 100644 --- a/web/src/features/monitoring-observers/models/search-params.ts +++ b/web/src/features/monitoring-observers/models/search-params.ts @@ -21,7 +21,6 @@ export const PushMessageTargetedObserversSearchParamsSchema = z.object({ questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), formId: z.string().optional(), formTypeFilter: ZFormType.optional(), - formIsCompleted: z.string().optional(), followUpStatus: z.nativeEnum(FormSubmissionFollowUpStatus).optional(), hasFlaggedAnswers: z.string().catch('').optional(), monitoringObserverId: z.string().catch('').optional(), diff --git a/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx b/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx index 48d9a6d4c..612bbb0f6 100644 --- a/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx +++ b/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx @@ -28,6 +28,9 @@ import { useElectionRoundStatistics } from '../../hooks/statistics-queries'; import { HistogramEntry } from '../../models/ngo-admin-statistics-models'; import LevelStatistics from '../LevelStatisticsCard/LevelStatisticsCard'; import useDashboardExpandedChartsStore from './dashboard-config.store'; +import { DataSourceSwitcher } from '@/components/DataSourceSwitcher/DataSourceSwitcher'; +import { useDataSource } from '@/common/data-source-store'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export default function NgoAdminDashboard(): FunctionComponent { const { t } = useTranslation('translation', { keyPrefix: 'ngoAdminDashboard' }); @@ -45,9 +48,10 @@ export default function NgoAdminDashboard(): FunctionComponent { const incidentReportsChartRef = useRef(null); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); - const isMonitoringNgoForCitizenReporting = useCurrentElectionRoundStore((s) => s.isMonitoringNgoForCitizenReporting); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const dataSource = useDataSource(); - const { data: statistics } = useElectionRoundStatistics(currentElectionRoundId); + const { data: statistics } = useElectionRoundStatistics(currentElectionRoundId, dataSource); const getInterval = useCallback((histogram: HistogramEntry[] | undefined) => { if (histogram && histogram.some((x) => x)) { @@ -63,7 +67,7 @@ export default function NgoAdminDashboard(): FunctionComponent { return (formsHistogram ?? []).reduce((acc, { value }) => acc + value, 0); }, []); - const saveChartCallback = useCallback((chartRef:any, fileName:string) => { + const saveChartCallback = useCallback((chartRef: any, fileName: string) => { if (chartRef?.current) { saveChart(chartRef, fileName); } @@ -76,267 +80,143 @@ export default function NgoAdminDashboard(): FunctionComponent { [] ); + const observersOnTheFieldData = useCallback( + (totalNumberOfObservers: number | undefined, numberOfObserversOnTheField?: number) => + observersOnTheFieldDataConfig(totalNumberOfObservers, numberOfObserversOnTheField ?? 0), + [] + ); return ( - -
-
-
- - - {t('observersAccounts.cardTitle')} - - - - - - - - - {t('observersOnFieldCardTitle')} - - - - - - - - - {t('pollingStationCardTitle')} - - - - - - - - - {t('timeSpentObserving.cardTitle')} - - - - - - + <> +
+
+

+ {t('title')} +

+
+

{t('subtitle')}

+
-
0 ? 'row dense' : 'unset', - }}> - - - {t('startedForms.cardTitle')} -
+
+
+
+
+
+
+ + + {t('observersAccounts.cardTitle')} - -
- - - - - - - - {t('questionsAnswered.cardTitle')} -
+ + + + + + + + {t('observersOnFieldCardTitle')} -
-
- - - -
- - - {t('flaggedAnswers.cardTitle')} -
+ value={statistics?.totalStats?.activeObservers ?? 0} + total={statistics?.observersStats?.totalNumberOfObservers ?? 0} + ref={observersOnFieldChartRef} + /> + + + + + {t('pollingStationCardTitle')} - -
-
- - - -
- - - {t('quickReports.cardTitle')} -
+ + + + + + + + {t('timeSpentObserving.cardTitle')} - -
-
- - - -
- {isMonitoringNgoForCitizenReporting && ( + + + + + +
+
0 ? 'row dense' : 'unset', + }}> - {t('citizenReports.cardTitle')} + {t('startedForms.cardTitle')}
- - -
-
- - - -
-
-
- - - Level 1 - {statistics?.level2Stats?.length ? Level 2 : null} - {statistics?.level3Stats?.length ? Level 3 : null} - {statistics?.level4Stats?.length ? Level 4 : null} - {statistics?.level5Stats?.length ? Level 5 : null} - - - - - + + {t('questionsAnswered.cardTitle')} +
+ + +
+
+ + + + + + + {t('flaggedAnswers.cardTitle')} +
+ + +
+
+ + + +
+ + + {t('quickReports.cardTitle')} +
+ + +
+
+ + + +
+ {electionRound?.isMonitoringNgoForCitizenReporting && ( + + + {t('citizenReports.cardTitle')} +
+ + +
+
+ + + +
+ )} + {/* + + {t('incidentReports.cardTitle')} +
+ + +
+
+ + + +
*/} +
+
+ + + Level 1 + {statistics?.level2Stats?.length ? Level 2 : null} + {statistics?.level3Stats?.length ? Level 3 : null} + {statistics?.level4Stats?.length ? Level 4 : null} + {statistics?.level5Stats?.length ? Level 5 : null} + - {statistics?.level2Stats?.length ? ( - - + + - ) : null} - {statistics?.level3Stats?.length ? ( - - - - ) : null} + {statistics?.level2Stats?.length ? ( + + + + ) : null} - {statistics?.level4Stats?.length ? ( - - - - ) : null} + {statistics?.level3Stats?.length ? ( + + + + ) : null} - {statistics?.level5Stats?.length ? ( - - - - ) : null} - + {statistics?.level4Stats?.length ? ( + + + + ) : null} + + {statistics?.level5Stats?.length ? ( + + + + ) : null} + +
-
- + + ); } diff --git a/web/src/features/ngo-admin-dashboard/components/LevelStatisticsCard/LevelStatisticsCard.tsx b/web/src/features/ngo-admin-dashboard/components/LevelStatisticsCard/LevelStatisticsCard.tsx index 086b5d615..45565a622 100644 --- a/web/src/features/ngo-admin-dashboard/components/LevelStatisticsCard/LevelStatisticsCard.tsx +++ b/web/src/features/ngo-admin-dashboard/components/LevelStatisticsCard/LevelStatisticsCard.tsx @@ -159,7 +159,7 @@ export default function LevelStatistics({ level, levelStats }: LevelStatisticsPr /> - + {/* */}
); diff --git a/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts b/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts index 45d86eef7..3e5c2512a 100644 --- a/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts +++ b/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts @@ -1,26 +1,29 @@ import { authApi } from '@/common/auth-api'; import { useQuery, UseQueryResult } from '@tanstack/react-query'; import { MonitoringNgoStats } from '../models/ngo-admin-statistics-models'; +import { DataSources } from '@/common/types'; const STALE_TIME = 1000 * 10 * 60; // 10 minutes export const statisticsCacheKey = { - all: (electionRoundId: string) => ['election-round-statistics', electionRoundId] as const, + all: (electionRoundId: string, dataSource: DataSources) => ['election-round-statistics', electionRoundId, dataSource] as const, } type UseElectionRoundStatisticsResult = UseQueryResult; export function useElectionRoundStatistics( - electionRoundId: string + electionRoundId: string, + dataSource: DataSources ): UseElectionRoundStatisticsResult { return useQuery({ - queryKey: statisticsCacheKey.all(electionRoundId), + queryKey: statisticsCacheKey.all(electionRoundId, dataSource), queryFn: async () => { - const response = await authApi.get(`/election-rounds/${electionRoundId}/statistics`); + const response = await authApi.get(`/election-rounds/${electionRoundId}/statistics?dataSource=${dataSource}`); return response.data; }, + refetchOnMount: false, staleTime: STALE_TIME, }); } \ No newline at end of file diff --git a/web/src/features/responses/components/AggregateCard/AggregateCard.tsx b/web/src/features/responses/components/AggregateCard/AggregateCard.tsx index 7ea97c257..8c15a648e 100644 --- a/web/src/features/responses/components/AggregateCard/AggregateCard.tsx +++ b/web/src/features/responses/components/AggregateCard/AggregateCard.tsx @@ -67,35 +67,36 @@ export function AggregateCard({

{aggregate.answersAggregated} answers

+ {aggregate.answersAggregated > 0 ? ( + + {isDateAggregate(aggregate) && } - - {isDateAggregate(aggregate) && } - - {isMultiSelectAggregate(aggregate) && ( - - )} + {isMultiSelectAggregate(aggregate) && ( + + )} - {isNumberAggregate(aggregate) && } + {isNumberAggregate(aggregate) && } - {isRatingAggregate(aggregate) && } + {isRatingAggregate(aggregate) && } - {isSingleSelectAggregate(aggregate) && ( - - )} + {isSingleSelectAggregate(aggregate) && ( + + )} - {isTextAggregate(aggregate) && ( - - )} + {isTextAggregate(aggregate) && ( + + )} - {(notes.length > 0 || attachments.length > 0) && ( - - )} - + {(notes.length > 0 || attachments.length > 0) && ( + + )} + + ) : null} ); } diff --git a/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx b/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx index 40f5f372b..041491c2b 100644 --- a/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx +++ b/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx @@ -1,5 +1,10 @@ import { authApi } from '@/common/auth-api'; -import { CitizenReportFollowUpStatus, FormSubmissionFollowUpStatus, type FunctionComponent } from '@/common/types'; +import { + CitizenReportFollowUpStatus, + ElectionRoundStatus, + FormSubmissionFollowUpStatus, + type FunctionComponent, +} from '@/common/types'; import Layout from '@/components/layout/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -14,15 +19,23 @@ import { citizenReportKeys } from '../../hooks/citizen-reports'; import PreviewAnswer from '../PreviewAnswer/PreviewAnswer'; import { SubmissionType } from '../../models/common'; import { mapCitizenReportFollowUpStatus } from '../../utils/helpers'; +import { usePrevSearch } from '@/common/prev-search-store'; +import { NavigateBack } from '@/components/NavigateBack/NavigateBack'; +import { DateTimeFormat } from '@/common/formats'; +import { format } from 'date-fns'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export default function CitizenReportDetails(): FunctionComponent { const { citizenReportId } = Route.useParams(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const { data: citizenReport } = useSuspenseQuery( citizenReportDetailsQueryOptions(currentElectionRoundId, citizenReportId) ); const router = useRouter(); + const prevSearch = usePrevSearch(); const updateSubmissionFollowUpStatusMutation = useMutation({ mutationKey: citizenReportKeys.detail(currentElectionRoundId, citizenReportId), @@ -62,8 +75,53 @@ export default function CitizenReportDetails(): FunctionComponent { } return ( - + } + breadcrumbs={<>} + title={`#${citizenReport.citizenReportId}`}>
+ + +
+

Time submitted

+ {citizenReport.timeSubmitted &&

{format(citizenReport.timeSubmitted, DateTimeFormat)}

} +
+ + {citizenReport.level1 && ( +
+
+

Location - L1

+ {citizenReport.level1} +
+ {citizenReport.level2 && ( +
+

Location - L2

+ {citizenReport.level2} +
+ )} + {citizenReport.level3 && ( +
+

Location - L3

+ {citizenReport.level3} +
+ )} + {citizenReport.level4 && ( +
+

Location - L4

+ {citizenReport.level4} +
+ )} + {citizenReport.level5 && ( +
+

Location - L5

+ {citizenReport.level5} +
+ )} +
+ )} +
+
+ @@ -73,6 +131,7 @@ export default function CitizenReportDetails(): FunctionComponent { + value={formSubmission.followUpStatus} + disabled={!formSubmission.isOwnObserver || electionRound?.status === ElectionRoundStatus.Archived}> diff --git a/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx b/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx index a7bfa4e17..3bbcb926d 100644 --- a/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx @@ -1,14 +1,15 @@ -import { FunctionComponent } from '@/common/types'; -import { CardContent } from '@/components/ui/card'; +import { DataSources, FunctionComponent } from '@/common/types'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; +import { CardContent } from '@/components/ui/card'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { getValueOrDefault, toBoolean } from '@/lib/utils'; import { getRouteApi } from '@tanstack/react-router'; import { useDebounce } from '@uidotdev/usehooks'; import { useCallback, useMemo } from 'react'; import { useFormSubmissionsByForm } from '../../hooks/form-submissions-queries'; -import { FormSubmissionsSearchParams } from '../../models/search-params'; import { useFormSubmissionsByFormColumns } from '../../store/column-visibility'; import { formSubmissionsByFormColumnDefs } from '../../utils/column-defs'; +import { FormSubmissionsSearchRequest } from '../FormSubmissionsByEntryTable/FormSubmissionsByEntryTable'; const routeApi = getRouteApi('/responses/'); @@ -24,11 +25,11 @@ export function FormSubmissionsAggregatedByFormTable({ const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); const search = routeApi.useSearch(); const debouncedSearch = useDebounce(search, 300); - + const navigateToAggregatedForm = useCallback( (formId: string) => { void navigate({ - to: '/responses/$formId/aggregated', + to: '/responses/form-submissions/$formId/aggregated', params: { formId }, search: { hasFlaggedAnswers: search.hasFlaggedAnswers, @@ -45,7 +46,8 @@ export function FormSubmissionsAggregatedByFormTable({ tagsFilter: search.tagsFilter, submissionsFromDate: search.submissionsFromDate, submissionsToDate: search.submissionsToDate, - formIsCompleted: search.formIsCompleted, + dataSource: getValueOrDefault(search.dataSource, DataSources.Ngo), + coalitionMemberId: search.coalitionMemberId }, }); }, @@ -53,27 +55,29 @@ export function FormSubmissionsAggregatedByFormTable({ ); const queryParams = useMemo(() => { - const params = [ - ['searchText', searchText], - ['hasFlaggedAnswers', search.hasFlaggedAnswers], - ['level1Filter', search.level1Filter], - ['level2Filter', search.level2Filter], - ['level3Filter', search.level3Filter], - ['level4Filter', search.level4Filter], - ['level5Filter', search.level5Filter], - ['pollingStationNumberFilter', search.pollingStationNumberFilter], - ['followUpStatus', search.followUpStatus], - ['questionsAnswered', search.questionsAnswered], - ['hasNotes', search.hasNotes], - ['hasAttachments', search.hasAttachments], - ['tagsFilter', search.tagsFilter], - ['formId', search.formId], - ['fromDateFilter', search.submissionsFromDate?.toISOString()], - ['toDateFilter', search.submissionsToDate?.toISOString()], - ['isCompletedFilter', search.formIsCompleted], - ].filter(([_, value]) => value); + const params: FormSubmissionsSearchRequest = { + dataSource: getValueOrDefault(search.dataSource, DataSources.Ngo), + searchText: searchText, + hasFlaggedAnswers: toBoolean(search.hasFlaggedAnswers), + level1Filter: search.level1Filter, + level2Filter: search.level2Filter, + level3Filter: search.level3Filter, + level4Filter: search.level4Filter, + level5Filter: search.level5Filter, + pollingStationNumberFilter: search.pollingStationNumberFilter, + followUpStatus: search.followUpStatus, + questionsAnswered: search.questionsAnswered, + hasNotes: toBoolean(search.hasNotes), + hasAttachments: toBoolean(search.hasAttachments), + tagsFilter: search.tagsFilter, + formId: search.formId, + fromDateFilter: search.submissionsFromDate?.toISOString(), + toDateFilter: search.submissionsToDate?.toISOString(), + formTypeFilter: search.formTypeFilter, + coalitionMemberId: search.coalitionMemberId + }; - return Object.fromEntries(params) as FormSubmissionsSearchParams; + return params; }, [searchText, debouncedSearch]); return ( diff --git a/web/src/features/responses/components/FormSubmissionsAggregatedDetails/FormSubmissionsAggregatedDetails.tsx b/web/src/features/responses/components/FormSubmissionsAggregatedDetails/FormSubmissionsAggregatedDetails.tsx index 103e028fc..d69923676 100644 --- a/web/src/features/responses/components/FormSubmissionsAggregatedDetails/FormSubmissionsAggregatedDetails.tsx +++ b/web/src/features/responses/components/FormSubmissionsAggregatedDetails/FormSubmissionsAggregatedDetails.tsx @@ -1,27 +1,30 @@ +import { usePrevSearch } from '@/common/prev-search-store'; import type { FunctionComponent } from '@/common/types'; import Layout from '@/components/layout/Layout'; import { NavigateBack } from '@/components/NavigateBack/NavigateBack'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { mapFormType } from '@/lib/utils'; -import { formAggregatedDetailsQueryOptions, Route } from '@/routes/responses/$formId.aggregated'; +import { formAggregatedDetailsQueryOptions, Route } from '@/routes/responses/form-submissions/$formId.aggregated'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { Link, useRouter } from '@tanstack/react-router'; +import { Link } from '@tanstack/react-router'; +import { SubmissionType } from '../../models/common'; import type { Responder } from '../../models/form-submissions-aggregated'; import { AggregateCard } from '../AggregateCard/AggregateCard'; -import { SubmissionType } from '../../models/common'; export default function FormSubmissionsAggregatedDetails(): FunctionComponent { - const { state } = useRouter(); - const { formId } = Route.useParams(); + const prevSearch = usePrevSearch(); const params = Route.useSearch(); + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); - const { data: formSubmission } = useSuspenseQuery( - formAggregatedDetailsQueryOptions(currentElectionRoundId, formId, params) - ); - const { submissionsAggregate } = formSubmission; - const { defaultLanguage, formCode, formType, aggregates, responders } = submissionsAggregate; + const { + data: { + submissionsAggregate: { defaultLanguage, formCode, formType, aggregates, responders }, + attachments, + notes, + }, + } = useSuspenseQuery(formAggregatedDetailsQueryOptions(currentElectionRoundId, formId, params)); const respondersAggregated = responders.reduce>( (grouped, responder) => ({ @@ -33,16 +36,9 @@ export default function FormSubmissionsAggregatedDetails(): FunctionComponent { return ( } - breadcrumbs={ -
- - responses - - {formId} -
- } - title={`${formCode} - ${mapFormType(formType)}`}> + backButton={} + breadcrumbs={<>} + title={`${formCode} - ${mapFormType(formType)}`}>
{Object.values(aggregates).map((aggregate) => { return ( @@ -52,8 +48,8 @@ export default function FormSubmissionsAggregatedDetails(): FunctionComponent { aggregate={aggregate} language={defaultLanguage} responders={respondersAggregated} - attachments={formSubmission.attachments} - notes={formSubmission.notes} + attachments={attachments} + notes={notes} /> ); })} diff --git a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx index 252dd7c8f..3aca59e18 100644 --- a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx @@ -1,13 +1,13 @@ -import type { FunctionComponent } from '@/common/types'; -import { CardContent } from '@/components/ui/card'; +import { DataSources, FormSubmissionFollowUpStatus, FunctionComponent, QuestionsAnswered } from '@/common/types'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; +import { CardContent } from '@/components/ui/card'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { getValueOrDefault, toBoolean } from '@/lib/utils'; import { Route } from '@/routes/responses'; import { useNavigate } from '@tanstack/react-router'; import { useDebounce } from '@uidotdev/usehooks'; import { useCallback, useMemo } from 'react'; import { useFormSubmissionsByEntry } from '../../hooks/form-submissions-queries'; -import type { FormSubmissionsSearchParams } from '../../models/search-params'; import { useFormSubmissionsByEntryColumns } from '../../store/column-visibility'; import { formSubmissionsByEntryColumnDefs } from '../../utils/column-defs'; @@ -15,6 +15,28 @@ type FormSubmissionsByEntryTableProps = { searchText: string; }; +export interface FormSubmissionsSearchRequest{ + dataSource: DataSources; + searchText: string | undefined; + formTypeFilter: string | undefined; + hasFlaggedAnswers: boolean | undefined; + level1Filter: string | undefined; + level2Filter: string | undefined; + level3Filter: string | undefined; + level4Filter: string | undefined; + level5Filter: string | undefined; + pollingStationNumberFilter: string | undefined; + followUpStatus: FormSubmissionFollowUpStatus | undefined; + questionsAnswered: QuestionsAnswered | undefined; + hasNotes: boolean | undefined; + hasAttachments: boolean | undefined; + tagsFilter: string[] | undefined; + formId: string | undefined; + fromDateFilter: string | undefined; + toDateFilter: string | undefined; + coalitionMemberId: string | undefined; +} + export function FormSubmissionsByEntryTable({ searchText }: FormSubmissionsByEntryTableProps): FunctionComponent { const navigate = useNavigate(); const search = Route.useSearch(); @@ -24,33 +46,34 @@ export function FormSubmissionsByEntryTable({ searchText }: FormSubmissionsByEnt const columnsVisibility = useFormSubmissionsByEntryColumns(); const queryParams = useMemo(() => { - const params = [ - ['searchText', searchText], - ['formTypeFilter', debouncedSearch.formTypeFilter], - ['hasFlaggedAnswers', debouncedSearch.hasFlaggedAnswers], - ['level1Filter', debouncedSearch.level1Filter], - ['level2Filter', debouncedSearch.level2Filter], - ['level3Filter', debouncedSearch.level3Filter], - ['level4Filter', debouncedSearch.level4Filter], - ['level5Filter', debouncedSearch.level5Filter], - ['pollingStationNumberFilter', debouncedSearch.pollingStationNumberFilter], - ['followUpStatus', debouncedSearch.followUpStatus], - ['questionsAnswered', debouncedSearch.questionsAnswered], - ['hasNotes', debouncedSearch.hasNotes], - ['hasAttachments', debouncedSearch.hasAttachments], - ['tagsFilter', debouncedSearch.tagsFilter], - ['formId', debouncedSearch.formId], - ['fromDateFilter', debouncedSearch.submissionsFromDate?.toISOString()], - ['toDateFilter', debouncedSearch.submissionsToDate?.toISOString()], - ['isCompletedFilter', debouncedSearch.formIsCompleted], - ].filter(([_, value]) => value); + const params: FormSubmissionsSearchRequest = { + dataSource: getValueOrDefault(search.dataSource, DataSources.Ngo), + searchText: searchText, + formTypeFilter: debouncedSearch.formTypeFilter, + hasFlaggedAnswers: toBoolean(debouncedSearch.hasFlaggedAnswers), + level1Filter: debouncedSearch.level1Filter, + level2Filter: debouncedSearch.level2Filter, + level3Filter: debouncedSearch.level3Filter, + level4Filter: debouncedSearch.level4Filter, + level5Filter: debouncedSearch.level5Filter, + pollingStationNumberFilter: debouncedSearch.pollingStationNumberFilter, + followUpStatus: debouncedSearch.followUpStatus, + questionsAnswered: debouncedSearch.questionsAnswered, + hasNotes: toBoolean(debouncedSearch.hasNotes), + hasAttachments: toBoolean(debouncedSearch.hasAttachments), + tagsFilter: debouncedSearch.tagsFilter, + formId: debouncedSearch.formId, + fromDateFilter: debouncedSearch.submissionsFromDate?.toISOString(), + toDateFilter: debouncedSearch.submissionsToDate?.toISOString(), + coalitionMemberId: debouncedSearch.coalitionMemberId + }; - return Object.fromEntries(params) as FormSubmissionsSearchParams; + return params; }, [searchText, debouncedSearch]); const navigateToFormSubmission = useCallback( (submissionId: string) => { - void navigate({ to: '/responses/$submissionId', params: { submissionId } }); + void navigate({ to: '/responses/form-submissions/$submissionId', params: { submissionId } }); }, [navigate] ); diff --git a/web/src/features/responses/components/FormSubmissionsByObserverTable/FormSubmissionsByObserverTable.tsx b/web/src/features/responses/components/FormSubmissionsByObserverTable/FormSubmissionsByObserverTable.tsx index 7de75d124..454d934cb 100644 --- a/web/src/features/responses/components/FormSubmissionsByObserverTable/FormSubmissionsByObserverTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsByObserverTable/FormSubmissionsByObserverTable.tsx @@ -1,13 +1,13 @@ -import type { FunctionComponent } from '@/common/types'; -import { CardContent } from '@/components/ui/card'; +import { DataSources, FormSubmissionFollowUpStatus, FunctionComponent } from '@/common/types'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; +import { CardContent } from '@/components/ui/card'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { getValueOrDefault, toBoolean } from '@/lib/utils'; import { Route } from '@/routes/responses'; import { useNavigate } from '@tanstack/react-router'; import { useDebounce } from '@uidotdev/usehooks'; import { useCallback, useMemo } from 'react'; import { useFormSubmissionsByObserver } from '../../hooks/form-submissions-queries'; -import type { FormSubmissionsSearchParams } from '../../models/search-params'; import { useFormSubmissionsByObserverColumns } from '../../store/column-visibility'; import { formSubmissionsByObserverColumnDefs } from '../../utils/column-defs'; @@ -15,6 +15,14 @@ type FormSubmissionsByObserverTableProps = { searchText: string; }; +export interface FormSubmissionsByObserverFilterRequest { + followUpStatus: FormSubmissionFollowUpStatus | undefined; + searchText: string | undefined; + tagsFilter: string[] | undefined; + hasFlaggedAnswers: boolean | undefined; + dataSource: DataSources; + coalitionMemberId: string | undefined; +} export function FormSubmissionsByObserverTable({ searchText }: FormSubmissionsByObserverTableProps): FunctionComponent { const navigate = useNavigate(); const search = Route.useSearch(); @@ -23,14 +31,16 @@ export function FormSubmissionsByObserverTable({ searchText }: FormSubmissionsBy const columnsVisibility = useFormSubmissionsByObserverColumns(); const queryParams = useMemo(() => { - const params = [ - ['followUpStatus', debouncedSearch.followUpStatus], - ['searchText', searchText], - ['tagsFilter', debouncedSearch.tagsFilter], - ['hasFlaggedAnswers', debouncedSearch.hasFlaggedAnswers], - ].filter(([_, value]) => value); + const params: FormSubmissionsByObserverFilterRequest = { + dataSource: getValueOrDefault(search.dataSource, DataSources.Ngo), + followUpStatus: debouncedSearch.followUpStatus, + searchText: searchText, + tagsFilter: debouncedSearch.tagsFilter, + hasFlaggedAnswers: toBoolean(debouncedSearch.hasFlaggedAnswers), + coalitionMemberId: debouncedSearch.coalitionMemberId, + }; - return Object.fromEntries(params) as FormSubmissionsSearchParams; + return params; }, [searchText, debouncedSearch]); const navigateToMonitoringObserver = useCallback( diff --git a/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx b/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx index b0883dbed..67f9a326e 100644 --- a/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx +++ b/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx @@ -1,6 +1,5 @@ import { PollingStationsFilters } from '@/components/PollingStationsFilters/PollingStationsFilters'; import { FilteringContainer } from '@/features/filtering/components/FilteringContainer'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { FormTypeFilter } from '@/features/filtering/components/FormTypeFilter'; import { MonitoringObserverTagsSelect } from '@/features/monitoring-observers/filtering/MonitoringObserverTagsSelect'; import { FC } from 'react'; @@ -12,13 +11,18 @@ import { FormSubmissionsMediaFilesFilter } from '../../../filtering/components/F import { FormSubmissionsQuestionNotesFilter } from '../../../filtering/components/FormSubmissionsQuestionNotesFilter'; import { FormSubmissionsQuestionsAnsweredFilter } from '../../../filtering/components/FormSubmissionsQuestionsAnsweredFilter'; import { FormSubmissionsToDateFilter } from '../../../filtering/components/FormSubmissionsToDateFilter'; +import { useDataSource } from '@/common/data-source-store'; +import { DataSources } from '@/common/types'; +import { CoalitionMemberFilter } from '@/features/filtering/components/CoalitionMemberFilter'; export const FormSubmissionsFiltersByEntry: FC = () => { + const dataSource = useDataSource(); + return ( - + {dataSource === DataSources.Coalition ? : null} diff --git a/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx b/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx index d693e2453..ebd76db30 100644 --- a/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx +++ b/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx @@ -1,6 +1,5 @@ import { PollingStationsFilters } from '@/components/PollingStationsFilters/PollingStationsFilters'; import { FilteringContainer } from '@/features/filtering/components/FilteringContainer'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { FormSubmissionsFollowUpFilter } from '@/features/filtering/components/FormSubmissionsFollowUpFilter'; import { FormSubmissionsFormFilter } from '@/features/filtering/components/FormSubmissionsFormFilter'; import { FormSubmissionsFromDateFilter } from '@/features/filtering/components/FormSubmissionsFromDateFilter'; @@ -12,12 +11,18 @@ import { FC } from 'react'; import { FormSubmissionsFlaggedAnswersFilter } from '../../../filtering/components/FormSubmissionsFlaggedAnswersFilter'; import { FormSubmissionsMediaFilesFilter } from '../../../filtering/components/FormSubmissionsMediaFilesFilter'; import { FormSubmissionsQuestionNotesFilter } from '../../../filtering/components/FormSubmissionsQuestionNotesFilter'; +import { useDataSource } from '@/common/data-source-store'; +import { DataSources } from '@/common/types'; +import { CoalitionMemberFilter } from '@/features/filtering/components/CoalitionMemberFilter'; export const FormSubmissionsFiltersByForm: FC = () => { + const dataSource = useDataSource(); + return ( - + {dataSource === DataSources.Coalition ? : null} + diff --git a/web/src/features/responses/components/FormSubmissionsFiltersByObserver/FormSubmissionsFiltersByObserver.tsx b/web/src/features/responses/components/FormSubmissionsFiltersByObserver/FormSubmissionsFiltersByObserver.tsx index 0ce055d2c..7e79da6a5 100644 --- a/web/src/features/responses/components/FormSubmissionsFiltersByObserver/FormSubmissionsFiltersByObserver.tsx +++ b/web/src/features/responses/components/FormSubmissionsFiltersByObserver/FormSubmissionsFiltersByObserver.tsx @@ -1,13 +1,18 @@ -import { type FunctionComponent } from '@/common/types'; +import { useDataSource } from '@/common/data-source-store'; +import { DataSources, type FunctionComponent } from '@/common/types'; +import { CoalitionMemberFilter } from '@/features/filtering/components/CoalitionMemberFilter'; import { FilteringContainer } from '@/features/filtering/components/FilteringContainer'; import { MonitoringObserverTagsSelect } from '@/features/monitoring-observers/filtering/MonitoringObserverTagsSelect'; import { FormSubmissionsFlaggedAnswersFilter } from '../../../filtering/components/FormSubmissionsFlaggedAnswersFilter'; import { FormSubmissionsFollowUpFilter } from '../../../filtering/components/FormSubmissionsFollowUpFilter'; export function FormSubmissionsFiltersByObserver(): FunctionComponent { + const dataSource = useDataSource(); + return ( + {dataSource === DataSources.Coalition ? : null} diff --git a/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx b/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx index 31bfe1c0b..0dc298ac7 100644 --- a/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx +++ b/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx @@ -17,10 +17,10 @@ import { ExportedDataType } from '../../models/data-export'; import type { FormSubmissionsViewBy } from '../../utils/column-visibility-options'; import { ExportDataButton } from '../ExportDataButton/ExportDataButton'; import { FormSubmissionsAggregatedByFormTable } from '../FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable'; -import { FormSubmissionsByEntryTable } from '../FormSubmissionsByEntryTable/FormSubmissionsByEntryTable'; +import { FormSubmissionsByEntryTable, FormSubmissionsSearchRequest } from '../FormSubmissionsByEntryTable/FormSubmissionsByEntryTable'; import { FormSubmissionsColumnsVisibilitySelector } from '../FormSubmissionsColumnsVisibilitySelector/FormSubmissionsColumnsVisibilitySelector'; -import { FunctionComponent } from '@/common/types'; +import { DataSources, FunctionComponent } from '@/common/types'; import { FILTER_KEY } from '@/features/filtering/filtering-enums'; import { useFilteringContainer } from '@/features/filtering/hooks/useFilteringContainer'; import { FormSubmissionsByObserverTable } from '../FormSubmissionsByObserverTable/FormSubmissionsByObserverTable'; @@ -30,6 +30,7 @@ import { FormSubmissionsFiltersByObserver } from '../FormSubmissionsFiltersByObs import { Route } from '@/routes/responses'; import { useNavigate } from '@tanstack/react-router'; +import { getValueOrDefault, toBoolean } from '@/lib/utils'; const viewBy: Record = { byEntry: 'View by entry', @@ -51,33 +52,33 @@ export default function FormSubmissionsTab(): FunctionComponent { const setPrevSearch = useSetPrevSearch(); const handleSearchInput = (ev: ChangeEvent): void => { - const value = ev.currentTarget.value; - setSearchText(value); + setSearchText(ev.currentTarget.value); }; const formSubmissionsFilter = useMemo(() => { - const params = [ - ['searchText', search.searchText], - ['formTypeFilter', search.formTypeFilter], - ['hasFlaggedAnswers', search.hasFlaggedAnswers], - ['level1Filter', search.level1Filter], - ['level2Filter', search.level2Filter], - ['level3Filter', search.level3Filter], - ['level4Filter', search.level4Filter], - ['level5Filter', search.level5Filter], - ['pollingStationNumberFilter', search.pollingStationNumberFilter], - ['followUpStatus', search.followUpStatus], - ['questionsAnswered', search.questionsAnswered], - ['hasNotes', search.hasNotes], - ['hasAttachments', search.hasAttachments], - ['tagsFilter', search.tagsFilter], - ['formId', search.formId], - ['fromDateFilter', search.submissionsFromDate?.toISOString()], - ['toDateFilter', search.submissionsToDate?.toISOString()], - ['isCompletedFilter', search.formIsCompleted], - ].filter(([_, value]) => value); - - return Object.fromEntries(params); + const params: FormSubmissionsSearchRequest = { + dataSource: getValueOrDefault(search.dataSource, DataSources.Ngo), + searchText: searchText, + formTypeFilter: search.formTypeFilter, + hasFlaggedAnswers: toBoolean(search.hasFlaggedAnswers), + level1Filter: search.level1Filter, + level2Filter: search.level2Filter, + level3Filter: search.level3Filter, + level4Filter: search.level4Filter, + level5Filter: search.level5Filter, + pollingStationNumberFilter: search.pollingStationNumberFilter, + followUpStatus: search.followUpStatus, + questionsAnswered: search.questionsAnswered, + hasNotes: toBoolean(search.hasNotes), + hasAttachments: toBoolean(search.hasAttachments), + tagsFilter: search.tagsFilter, + formId: search.formId, + fromDateFilter: search.submissionsFromDate?.toISOString(), + toDateFilter: search.submissionsToDate?.toISOString(), + coalitionMemberId: search.coalitionMemberId + }; + + return params; }, [searchText, search]); useEffect(() => { diff --git a/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx b/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx index cf7c1441e..e85459ff5 100644 --- a/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx +++ b/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx @@ -1,5 +1,5 @@ import { authApi } from '@/common/auth-api'; -import { IncidentReportFollowUpStatus, type FunctionComponent } from '@/common/types'; +import { IncidentReportFollowUpStatus, type FunctionComponent, ElectionRoundStatus } from '@/common/types'; import Layout from '@/components/layout/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -17,10 +17,13 @@ import { mapIncidentReportFollowUpStatus, mapIncidentReportLocationType } from ' import PreviewAnswer from '../PreviewAnswer/PreviewAnswer'; import { format } from 'date-fns'; import { DateTimeFormat } from '@/common/formats'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; export default function IncidentReportDetails(): FunctionComponent { const { incidentReportId } = Route.useParams(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const { data: incidentReport } = useSuspenseQuery( incidentReportDetailsQueryOptions(currentElectionRoundId, incidentReportId) ); @@ -138,11 +141,6 @@ export default function IncidentReportDetails(): FunctionComponent {
)} - -
-

Is completed:

- {incidentReport.isCompleted.toString()} -
@@ -155,7 +153,8 @@ export default function IncidentReportDetails(): FunctionComponent { - - - - )} - {search.formIsCompleted && ( - - )} - {search.hasFlaggedAnswers && ( )} - - {search.formIsCompleted && ( - - )}
)} diff --git a/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx b/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx index a7cfefff8..9a0f7717a 100644 --- a/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx +++ b/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx @@ -100,26 +100,6 @@ export function IncidentReportsFiltersByForm(): FunctionComponent { - - - - )} - {search.formIsCompleted && ( - - )} - {search.hasFlaggedAnswers && ( )} - - {search.formIsCompleted && ( - - )}
)} diff --git a/web/src/features/responses/components/IncidentReportsTab/IncidentReportsTab.tsx b/web/src/features/responses/components/IncidentReportsTab/IncidentReportsTab.tsx index 5d09aee63..3328b705c 100644 --- a/web/src/features/responses/components/IncidentReportsTab/IncidentReportsTab.tsx +++ b/web/src/features/responses/components/IncidentReportsTab/IncidentReportsTab.tsx @@ -43,7 +43,7 @@ export default function IncidentReportsTab(): FunctionComponent { const { viewBy: byFilter } = search; - const [isFiltering, setIsFiltering] = useState(filteringIsActive); + const [filtersExpanded, setFiltersExpanded] = useState(filteringIsActive); const [searchText, setSearchText] = useState(''); const debouncedSearchText = useDebounce(searchText, 300); @@ -94,7 +94,7 @@ export default function IncidentReportsTab(): FunctionComponent { onValueChange={(value) => { setPrevSearch({ [FILTER_KEY.ViewBy]: value, [FILTER_KEY.Tab]: 'incident-reports' }); void navigate({ search: { [FILTER_KEY.ViewBy]: value, [FILTER_KEY.Tab]: 'incident-reports' } }); - setIsFiltering(false); + setFiltersExpanded(false); }} value={byFilter}> {Object.entries(viewBy).map(([value, label]) => ( @@ -112,12 +112,12 @@ export default function IncidentReportsTab(): FunctionComponent {
<> - + { - setIsFiltering((prev) => !prev); + setFiltersExpanded((prev) => !prev); }} /> @@ -127,7 +127,7 @@ export default function IncidentReportsTab(): FunctionComponent { - {isFiltering && ( + {filtersExpanded && (
{byFilter === 'byEntry' && } {byFilter === 'byObserver' && } diff --git a/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx b/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx index 984a55bdf..70b690734 100644 --- a/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx +++ b/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx @@ -1,15 +1,15 @@ import { authApi } from '@/common/auth-api'; import { DateTimeFormat } from '@/common/formats'; import { usePrevSearch } from '@/common/prev-search-store'; -import { QuickReportFollowUpStatus, type FunctionComponent } from '@/common/types'; +import { QuickReportFollowUpStatus, type FunctionComponent, ElectionRoundStatus } from '@/common/types'; import { NavigateBack } from '@/components/NavigateBack/NavigateBack'; import Layout from '@/components/layout/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { toast } from '@/components/ui/use-toast'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { useElectionRoundDetails } from '@/features/election-event/hooks/election-event-hooks'; import { queryClient } from '@/main'; import { Route, quickReportDetailsQueryOptions } from '@/routes/responses/quick-reports/$quickReportId'; import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'; @@ -17,14 +17,15 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query'; import { Link, useRouter } from '@tanstack/react-router'; import { format } from 'date-fns'; import { quickReportKeys } from '../../hooks/quick-reports'; +import { SubmissionType } from '../../models/common'; import { mapIncidentCategory, mapQuickReportFollowUpStatus, mapQuickReportLocationType } from '../../utils/helpers'; -import { MediaFilesCell } from '../MediaFilesCell/MediaFilesCell'; import { ResponseExtraDataSection } from '../ReponseExtraDataSection/ResponseExtraDataSection'; -import { SubmissionType } from '../../models/common'; export default function QuickReportDetails(): FunctionComponent { const { quickReportId } = Route.useParams(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const { data: electionRound } = useElectionRoundDetails(currentElectionRoundId); + const quickReportQuery = useSuspenseQuery(quickReportDetailsQueryOptions(currentElectionRoundId, quickReportId)); const quickReport = quickReportQuery.data; const { invalidate } = useRouter(); @@ -70,14 +71,7 @@ export default function QuickReportDetails(): FunctionComponent { return ( } - breadcrumbs={ -
- - responses - - {quickReport.id} -
- } + breadcrumbs={<>} title={quickReport.id}>
@@ -85,10 +79,9 @@ export default function QuickReportDetails(): FunctionComponent {

Observer:

{quickReport.observerName} @@ -159,7 +152,8 @@ export default function QuickReportDetails(): FunctionComponent { + { + setFiltersExpanded((prev) => !prev); + }} + /> + @@ -131,8 +161,10 @@ export function QuickReportsTab(): FunctionComponent { - {isFiltering && ( + {filtersExpanded && (
+ {dataSource === DataSources.Coalition ? : null} + - + - {isFiltering && ( + {filtersExpanded && (
{search.quickReportFollowUpStatus && ( {responders && (

- {responders[responderId]?.firstName ?? ''} {responders[responderId]?.lastName ?? ''} + {responders[responderId]?.displayName ?? ''} {':'}

)} @@ -52,7 +52,7 @@ export function TextAggregateContent({ diff --git a/web/src/features/responses/hooks/form-submissions-queries.ts b/web/src/features/responses/hooks/form-submissions-queries.ts index 75ba91e73..5704774e2 100644 --- a/web/src/features/responses/hooks/form-submissions-queries.ts +++ b/web/src/features/responses/hooks/form-submissions-queries.ts @@ -1,5 +1,6 @@ import { authApi } from '@/common/auth-api'; import type { + DataSources, DataTableParameters, FormSubmissionFollowUpStatus, PageResponse, @@ -14,7 +15,7 @@ import type { FormSubmissionByObserver, FormSubmissionsFilters, } from '../models/form-submission'; -import { SubmissionsAggregatedByFormParams } from '@/routes/responses/$formId.aggregated'; +import { SubmissionsAggregatedByFormParams } from '@/routes/responses/form-submissions/$formId.aggregated'; const STALE_TIME = 1000 * 60; // one minute @@ -27,7 +28,8 @@ export const formSubmissionsByEntryKeys = { detail: (electionRoundId: string, id: string) => [...formSubmissionsByEntryKeys.details(electionRoundId), id] as const, - filters: (electionRoundId: string) => [...formSubmissionsByEntryKeys.all(electionRoundId), 'filters'] as const, + filters: (electionRoundId: string, dataSource: DataSources) => + [...formSubmissionsByEntryKeys.all(electionRoundId), dataSource, 'filters'] as const, }; export const formSubmissionsByObserverKeys = { @@ -124,12 +126,12 @@ export function useFormSubmissionsByObserver( }); } -export function useFormSubmissionsFilters(electionRoundId: string) { +export function useFormSubmissionsFilters(electionRoundId: string, dataSource : DataSources) { return useQuery({ - queryKey: formSubmissionsByEntryKeys.filters(electionRoundId), + queryKey: formSubmissionsByEntryKeys.filters(electionRoundId, dataSource), queryFn: async () => { const response = await authApi.get( - `/election-rounds/${electionRoundId}/form-submissions:filters` + `/election-rounds/${electionRoundId}/form-submissions:filters?dataSource=${dataSource}` ); return response.data; diff --git a/web/src/features/responses/hooks/incident-reports-queries.ts b/web/src/features/responses/hooks/incident-reports-queries.ts index feaaa8877..da21f9703 100644 --- a/web/src/features/responses/hooks/incident-reports-queries.ts +++ b/web/src/features/responses/hooks/incident-reports-queries.ts @@ -1,5 +1,5 @@ import { authApi } from '@/common/auth-api'; -import type { DataTableParameters, PageResponse } from '@/common/types'; +import type { DataSources, DataTableParameters, PageResponse } from '@/common/types'; import type { RowData } from '@/components/ui/DataTable/DataTable'; import { buildURLSearchParams } from '@/lib/utils'; import { useQuery, type UseQueryResult } from '@tanstack/react-query'; @@ -15,7 +15,8 @@ export const incidentReportsByEntryKeys = { details: (electionRoundId: string) => [...incidentReportsByEntryKeys.all(electionRoundId), 'detail'] as const, detail: (electionRoundId: string, id: string) => [...incidentReportsByEntryKeys.details(electionRoundId), id] as const, - filters: (electionRoundId: string) => [...incidentReportsByEntryKeys.all(electionRoundId), 'filters'] as const, + filters: (electionRoundId: string, dataSource: DataSources) => + [...incidentReportsByEntryKeys.all(electionRoundId), dataSource, 'filters'] as const, }; export const incidentReportsByObserverKeys = { @@ -154,12 +155,12 @@ export function useIncidentReportsByForm( }); } -export function useIncidentReportsFilters(electionRoundId: string) { +export function useIncidentReportsFilters(electionRoundId: string, dataSource: DataSources) { return useQuery({ - queryKey: incidentReportsByEntryKeys.filters(electionRoundId), + queryKey: incidentReportsByEntryKeys.filters(electionRoundId, dataSource), queryFn: async () => { const response = await authApi.get( - `/election-rounds/${electionRoundId}/incident-reports:filters` + `/election-rounds/${electionRoundId}/incident-reports:filters?dataSource=${dataSource}` ); return response.data; diff --git a/web/src/features/responses/hooks/quick-reports.ts b/web/src/features/responses/hooks/quick-reports.ts index fc0ec7998..ac5a7225b 100644 --- a/web/src/features/responses/hooks/quick-reports.ts +++ b/web/src/features/responses/hooks/quick-reports.ts @@ -1,17 +1,20 @@ import { authApi } from '@/common/auth-api'; -import type { DataTableParameters, PageResponse } from '@/common/types'; +import type { DataSources, DataTableParameters, PageResponse } from '@/common/types'; import { type UseQueryResult, useQuery } from '@tanstack/react-query'; import type { QuickReport, QuickReportsFilters } from '../models/quick-report'; +import { buildURLSearchParams } from '@/lib/utils'; const STALE_TIME = 1000 * 60; // one minute export const quickReportKeys = { all: (electionRoundId: string) => ['quick-reports', electionRoundId] as const, lists: (electionRoundId: string) => [...quickReportKeys.all(electionRoundId), 'list'] as const, - list: (electionRoundId: string, params: DataTableParameters) => [...quickReportKeys.lists(electionRoundId), { ...params }] as const, + list: (electionRoundId: string, params: DataTableParameters) => + [...quickReportKeys.lists(electionRoundId), { ...params }] as const, details: (electionRoundId: string) => [...quickReportKeys.all(electionRoundId), 'detail'] as const, detail: (electionRoundId: string, id: string) => [...quickReportKeys.details(electionRoundId), id] as const, - filters: (electionRoundId: string) => [...quickReportKeys.details(electionRoundId), 'filters'] as const, + filters: (electionRoundId: string, dataSource: DataSources) => + [...quickReportKeys.details(electionRoundId), dataSource, 'filters'] as const, }; type QuickReportsResponse = PageResponse; @@ -31,7 +34,7 @@ export function useQuickReports(electionRoundId: string, queryParams: DataTableP SortOrder: queryParams.sortOrder, }; - const searchParams = new URLSearchParams(params); + const searchParams = buildURLSearchParams(params); const response = await authApi.get(`/election-rounds/${electionRoundId}/quick-reports`, { params: searchParams, @@ -46,12 +49,12 @@ export function useQuickReports(electionRoundId: string, queryParams: DataTableP -export function useQuickReportsFilters(electionRoundId: string) { +export function useQuickReportsFilters(electionRoundId: string, dataSource: DataSources) { return useQuery({ - queryKey: quickReportKeys.filters(electionRoundId), + queryKey: quickReportKeys.filters(electionRoundId, dataSource), queryFn: async () => { const response = await authApi.get( - `/election-rounds/${electionRoundId}/quick-reports:filters` + `/election-rounds/${electionRoundId}/quick-reports:filters?dataSource=${dataSource}` ); return response.data; diff --git a/web/src/features/responses/models/citizen-report.ts b/web/src/features/responses/models/citizen-report.ts index bd1c9313b..aadc540d6 100644 --- a/web/src/features/responses/models/citizen-report.ts +++ b/web/src/features/responses/models/citizen-report.ts @@ -19,6 +19,11 @@ export interface CitizenReportByEntry { timeSubmitted: Date; numberOfQuestionsAnswered: number; numberOfFlaggedAnswers: number; + level1: string; + level2: string; + level3: string; + level4: string; + level5: string; notesCount: number; mediaFilesCount: number; followUpStatus: CitizenReportFollowUpStatus; diff --git a/web/src/features/responses/models/form-submission.ts b/web/src/features/responses/models/form-submission.ts index e1790c58e..2e855c064 100644 --- a/web/src/features/responses/models/form-submission.ts +++ b/web/src/features/responses/models/form-submission.ts @@ -15,6 +15,8 @@ import { Attachment, Note } from './common'; export interface FormSubmissionByEntry { email: string; observerName: string; + ngoName: string; + phoneNumber: string; formCode: string; formType: FormType; formName: TranslatedString; @@ -31,20 +33,19 @@ export interface FormSubmissionByEntry { mediaFilesCount: number; notesCount: number; number: string; - phoneNumber: string; submissionId: string; + isOwnObserver: boolean; tags: string[]; timeSubmitted: string; followUpStatus: FormSubmissionFollowUpStatus; - isCompleted: boolean; } export interface FormSubmissionByObserver { email: string; observerName: string; + ngoName: string; monitoringObserverId: string; numberOfFlaggedAnswers: number; - numberOfCompletedForms: number; numberOfFormsSubmitted: number; numberOfLocations: number; phoneNumber: string; diff --git a/web/src/features/responses/models/form-submissions-aggregated.ts b/web/src/features/responses/models/form-submissions-aggregated.ts index c55e4ac53..bea2e6cbd 100644 --- a/web/src/features/responses/models/form-submissions-aggregated.ts +++ b/web/src/features/responses/models/form-submissions-aggregated.ts @@ -12,8 +12,7 @@ import { export interface Responder { responderId: string; - firstName: string; - lastName: string; + displayName: string; email: string; phoneNumber: string; } diff --git a/web/src/features/responses/models/incident-report.ts b/web/src/features/responses/models/incident-report.ts index f15e57663..dfc3f5bfa 100644 --- a/web/src/features/responses/models/incident-report.ts +++ b/web/src/features/responses/models/incident-report.ts @@ -19,6 +19,7 @@ export enum IncidentReportLocationType { export interface IncidentReportByEntry { incidentReportId: string; observerName: string; + ngoName: string; formCode: string; formName: TranslatedString; formDefaultLanguage: string; @@ -39,16 +40,16 @@ export interface IncidentReportByEntry { phoneNumber: string; tags: string[]; timeSubmitted: string; + isOwnObserver: boolean; followUpStatus: IncidentReportFollowUpStatus; - isCompleted: boolean; } export interface IncidentReportByObserver { observerName: string; + ngoName: string; monitoringObserverId: string; numberOfFlaggedAnswers: number; numberOfIncidentsSubmitted: number; - numberOfCompletedForms: number; phoneNumber: string; tags: string[]; followUpStatus?: IncidentReportFollowUpStatus; diff --git a/web/src/features/responses/models/quick-report.ts b/web/src/features/responses/models/quick-report.ts index cb77650b1..984024191 100644 --- a/web/src/features/responses/models/quick-report.ts +++ b/web/src/features/responses/models/quick-report.ts @@ -64,6 +64,7 @@ export interface QuickReport { description: string; email: string; observerName: string; + ngoName: string; level1: string; level2: string; level3: string; @@ -77,6 +78,7 @@ export interface QuickReport { timestamp: string; title: string; monitoringObserverId: string; + isOwnObserver: boolean; attachments: Attachment[]; followUpStatus: QuickReportFollowUpStatus; incidentCategory: IncidentCategory; diff --git a/web/src/features/responses/models/search-params.ts b/web/src/features/responses/models/search-params.ts index 5e904fc80..897cfeea0 100644 --- a/web/src/features/responses/models/search-params.ts +++ b/web/src/features/responses/models/search-params.ts @@ -9,11 +9,15 @@ import { import { z } from 'zod'; import { IncidentCategory, QuickReportLocationType } from './quick-report'; import { IncidentReportLocationType } from './incident-report'; +import { ZDataSourceSearchSchema } from '@/routes'; export const ResponsesPageSearchParamsSchema = z.object({ viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), - tab: z.enum(['form-answers', 'quick-reports','citizen-reports','incident-reports']).catch('form-answers').optional(), -}); + tab: z + .enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']) + .catch('form-answers') + .optional(), +}).merge(ZDataSourceSearchSchema); export const FormSubmissionsSearchParamsSchema = ResponsesPageSearchParamsSchema.merge( z.object({ @@ -40,55 +44,63 @@ export const FormSubmissionsSearchParamsSchema = ResponsesPageSearchParamsSchema questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), hasNotes: z.string().catch('').optional(), hasAttachments: z.string().catch('').optional(), - formIsCompleted: z.string().catch('').optional(), formId: z.string().optional(), submissionsFromDate: z.coerce.date().optional(), submissionsToDate: z.coerce.date().optional(), - })); + coalitionMemberId: z.string().optional() + }) +); export type FormSubmissionsSearchParams = z.infer; -export const QuickReportsSearchParamsSchema = z.object({ - level1Filter: z.string().catch('').optional(), - level2Filter: z.string().catch('').optional(), - level3Filter: z.string().catch('').optional(), - level4Filter: z.string().catch('').optional(), - level5Filter: z.string().catch('').optional(), - pollingStationNumberFilter: z.string().catch('').optional(), - quickReportFollowUpStatus: z.nativeEnum(QuickReportFollowUpStatus).optional(), - quickReportLocationType: z.nativeEnum(QuickReportLocationType).optional(), - incidentCategory: z.nativeEnum(IncidentCategory).optional(), -}); +export const QuickReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + level1Filter: z.string().catch('').optional(), + level2Filter: z.string().catch('').optional(), + level3Filter: z.string().catch('').optional(), + level4Filter: z.string().catch('').optional(), + level5Filter: z.string().catch('').optional(), + pollingStationNumberFilter: z.string().catch('').optional(), + quickReportFollowUpStatus: z.nativeEnum(QuickReportFollowUpStatus).optional(), + quickReportLocationType: z.nativeEnum(QuickReportLocationType).optional(), + incidentCategory: z.nativeEnum(IncidentCategory).optional(), + }) +); export type QuickReportsSearchParams = z.infer; -export const CitizenReportsSearchParamsSchema = z.object({ - citizenReportFollowUpStatus: z - .nativeEnum(CitizenReportFollowUpStatus) - .optional(), -}); +export const CitizenReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + citizenReportFollowUpStatus: z.nativeEnum(CitizenReportFollowUpStatus).optional(), + }) +); export type CitizenReportsSearchParams = z.infer; -export const IncidentReportsSearchParamsSchema = z.object({ - viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), - tab: z.enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']).catch('form-answers').optional(), - searchText: z.string().catch('').optional(), - level1Filter: z.string().catch('').optional(), - level2Filter: z.string().catch('').optional(), - level3Filter: z.string().catch('').optional(), - level4Filter: z.string().catch('').optional(), - level5Filter: z.string().catch('').optional(), - pollingStationNumberFilter: z.string().catch('').optional(), - hasFlaggedAnswers: z.string().catch('').optional(), - monitoringObserverId: z.string().catch('').optional(), - tagsFilter: z.array(z.string()).optional().catch([]).optional(), - incidentReportFollowUpStatus: z.nativeEnum(IncidentReportLocationType).optional(), - incidentReportLocationType: z.nativeEnum(IncidentReportLocationType).optional(), - questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), - hasNotes: z.string().catch('').optional(), - hasAttachments: z.string().catch('').optional(), -}); +export const IncidentReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), + tab: z + .enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']) + .catch('form-answers') + .optional(), + searchText: z.string().catch('').optional(), + level1Filter: z.string().catch('').optional(), + level2Filter: z.string().catch('').optional(), + level3Filter: z.string().catch('').optional(), + level4Filter: z.string().catch('').optional(), + level5Filter: z.string().catch('').optional(), + pollingStationNumberFilter: z.string().catch('').optional(), + hasFlaggedAnswers: z.string().catch('').optional(), + monitoringObserverId: z.string().catch('').optional(), + tagsFilter: z.array(z.string()).optional().catch([]).optional(), + incidentReportFollowUpStatus: z.nativeEnum(IncidentReportLocationType).optional(), + incidentReportLocationType: z.nativeEnum(IncidentReportLocationType).optional(), + questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), + hasNotes: z.string().catch('').optional(), + hasAttachments: z.string().catch('').optional(), + }) +); export type IncidentReportsSearchParams = z.infer; diff --git a/web/src/features/responses/utils/column-defs.tsx b/web/src/features/responses/utils/column-defs.tsx index 7888ce454..13ea3d95c 100644 --- a/web/src/features/responses/utils/column-defs.tsx +++ b/web/src/features/responses/utils/column-defs.tsx @@ -33,7 +33,6 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef , accessorFn: (row)=> row.timeSubmitted, @@ -42,7 +41,6 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef
{format(row.original.timeSubmitted, DateTimeFormat)}
, }, - { header: ({ column }) => , accessorFn: (row)=> row.formCode, @@ -65,14 +63,6 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef
{row.original.formName[row.original.defaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn:(row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn: (row)=> row.level1, @@ -128,6 +118,14 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef
{row.original.observerName}
, }, + { + header: ({ column }) => , + accessorFn: (row)=> row.ngoName, + id: 'ngoName', + enableSorting: false, + enableGlobalFilter: true, + cell: ({ row }) =>
{row.original.ngoName}
, + }, { header: ({ column }) => , accessorFn: (row)=> row.tags, @@ -198,7 +196,7 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef + to='/responses/form-submissions/$submissionId'>
@@ -237,14 +235,6 @@ export const observerFormSubmissionsColumnDefs: ColumnDef
{row.original.formName[row.original.defaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn: (row) => row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) => {row.original.isCompleted.toString()}, - }, { header: ({ column }) => , accessorFn: (row) => row.level1, @@ -350,7 +340,7 @@ export const observerFormSubmissionsColumnDefs: ColumnDef + to='/responses/form-submissions/$submissionId'>
@@ -374,6 +364,14 @@ export const formSubmissionsByObserverColumnDefs: ColumnDef , + accessorFn: (row)=> row.ngoName, + id: 'ngoName', + enableSorting: false, + enableGlobalFilter: true, + cell: ({ row }) =>
{row.original.ngoName}
, + }, { header: ({ column }) => , accessorFn: (row)=> row.tags, @@ -400,13 +398,6 @@ export const formSubmissionsByObserverColumnDefs: ColumnDef , - accessorFn: (row)=> row.numberOfCompletedForms, - id: 'numberOfCompletedForms', - enableSorting: true, - enableGlobalFilter: true, - }, { header: ({ column }) => , accessorFn: (row)=> row.numberOfFlaggedAnswers, @@ -513,7 +504,7 @@ export const formSubmissionsByFormColumnDefs: ColumnDef + to='/responses/form-submissions/$formId/aggregated'>
@@ -571,7 +562,7 @@ export const aggregatedAnswerExtraInfoColumnDefs: ColumnDef[] ) : ( @@ -637,6 +628,14 @@ export const quickReportsColumnDefs: ColumnDef[] = [ enableSorting: false, enableGlobalFilter: true, }, + { + header: ({ column }) => , + accessorFn: (row)=> row.ngoName, + id: 'ngoName', + enableSorting: false, + enableGlobalFilter: true, + cell: ({ row }) =>
{row.original.ngoName}
, + }, { header: ({ column }) => , accessorFn: (row) => row.quickReportLocationType, @@ -946,6 +945,41 @@ export const citizenReportsByEntryColumnDefs: ColumnDef , + accessorFn:(row)=> row.level1, + id: 'level1', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level2, + id: 'level2', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level3, + id: 'level3', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level4, + id: 'level4', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level5, + id: 'level5', + enableSorting: true, + enableGlobalFilter: true, + }, { header: ({ column }) => , accessorFn:(row)=> row.notesCount, @@ -1092,14 +1126,6 @@ export const incidentReportsByEntryColumnDefs: ColumnDef
{row.original.formName[row.original.formDefaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn: (row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn:(row)=> row.pollingStationLevel1, @@ -1165,6 +1191,14 @@ export const incidentReportsByEntryColumnDefs: ColumnDef
{row.original.observerName}
, }, + { + header: ({ column }) => , + accessorFn: (row)=> row.ngoName, + id: 'ngoName', + enableSorting: false, + enableGlobalFilter: true, + cell: ({ row }) =>
{row.original.ngoName}
, + }, { header: ({ column }) => , accessorFn:(row)=> row.tags, @@ -1267,14 +1301,6 @@ export const observerIncidentReportsColumnDefs: ColumnDef
{row.original.formName[row.original.formDefaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn:(row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn:(row)=> row.locationType, @@ -1421,6 +1447,14 @@ export const incidentReportsByObserverColumnDefs: ColumnDef , + accessorFn: (row)=> row.ngoName, + id: 'ngoName', + enableSorting: false, + enableGlobalFilter: true, + cell: ({ row }) =>
{row.original.ngoName}
, + }, { header: ({ column }) => , accessorFn:(row)=> row.tags, @@ -1539,7 +1573,7 @@ export const incidentReportsByFormColumnDefs: ColumnDef + to='/responses/form-submissions/$formId/aggregated'>
diff --git a/web/src/features/responses/utils/column-visibility-options.tsx b/web/src/features/responses/utils/column-visibility-options.tsx index 76adaa1d2..12fd63f21 100644 --- a/web/src/features/responses/utils/column-visibility-options.tsx +++ b/web/src/features/responses/utils/column-visibility-options.tsx @@ -16,14 +16,14 @@ export const formSubmissionsByEntryDefaultColumns: TableColumnVisibilityState = { observerName: true, phoneNumber: true, tags: true, + ngoName: true, numberOfLocations: true, - numberOfCompletedForms: true, numberOfFormsSubmitted: true, numberOfFlaggedAnswers: true, followUpStatus: true, @@ -71,7 +72,6 @@ export const observerFormSubmissionsDefaultColumns: TableColumnVisibilityState = { @@ -114,20 +116,20 @@ const formSubmissionsByEntryColumnVisibilityOptions: ColumnOption[] = [ { id: 'observerName', label: 'Observer name', enableHiding: false }, { id: 'phoneNumber', label: 'Observer contact', enableHiding: true }, + { id: 'ngoName', label: 'NGO', enableHiding: true }, { id: 'tags', label: 'Observer tags', enableHiding: true }, - { id: 'numberOfCompletedForms', label: 'Completed forms', enableHiding: false }, { id: 'numberOfLocations', label: 'Locations', enableHiding: false }, { id: 'numberOfFormsSubmitted', label: 'Forms', enableHiding: false }, { id: 'numberOfFlaggedAnswers', label: 'Flagged answers', enableHiding: true }, @@ -159,6 +161,8 @@ export const quickReportsColumnVisibilityOptions: ColumnOption[] = { id: 'timestamp', label: 'Time submitted', enableHiding: true }, { id: 'quickReportLocationType', label: 'Location type', enableHiding: true }, { id: 'incidentCategory', label: 'Incident category', enableHiding: true }, + { id: 'observerName', label: 'Observer', enableHiding: true }, + { id: 'ngoName', label: 'NGO', enableHiding: true }, { id: 'followUpStatus', label: 'Follow-up status', enableHiding: true }, { id: 'title', label: 'Issue title', enableHiding: true }, { id: 'description', label: 'Description', enableHiding: true }, @@ -170,7 +174,7 @@ export const quickReportsColumnVisibilityOptions: ColumnOption[] = { id: 'level5', label: 'Location - L5', enableHiding: true }, { id: 'number', label: 'Station number', enableHiding: true }, { id: 'pollingStationDetails', label: 'Polling station details', enableHiding: true }, - { id: 'observerName', label: 'Observer', enableHiding: true }, + ]; export const quickReportsDefaultColumns: TableColumnVisibilityState = { @@ -181,6 +185,8 @@ export const quickReportsDefaultColumns: TableColumnVisibilityState description: true, numberOfAttachments: true, observerName: true, + email: false, + ngoName: false, level1: false, level2: false, level3: false, @@ -188,7 +194,6 @@ export const quickReportsDefaultColumns: TableColumnVisibilityState level5: false, number: false, pollingStationDetails: false, - email: false, followUpStatus: true, address: false, @@ -196,6 +201,8 @@ export const quickReportsDefaultColumns: TableColumnVisibilityState id: false, monitoringObserverId: false, pollingStationId: false, + isOwnObserver: false, + }; export const observerQuickReportsColumns: TableColumnVisibilityState = { @@ -220,8 +227,10 @@ export const observerQuickReportsColumns: TableColumnVisibilityState[] = [ @@ -245,6 +254,11 @@ export const citizenReportsDefaultColumns: TableColumnVisibilityState[] = [ { id: 'observerName', label: 'Observer name', enableHiding: false }, { id: 'phoneNumber', label: 'Observer contact', enableHiding: true }, + { id: 'ngoName', label: 'NGO', enableHiding: true }, { id: 'tags', label: 'Observer tags', enableHiding: true }, { id: 'numberOfIncidentsSubmitted', label: 'Number of submissions', enableHiding: false }, - { id: 'numberOfCompletedForms', label: 'Number completed forms', enableHiding: false }, { id: 'numberOfFlaggedAnswers', label: 'Flagged answers', enableHiding: true }, { id: 'followUpStatus', label: 'Follow-up status', enableHiding: true }, ]; @@ -305,7 +319,6 @@ export const observersFormSubmissionsColumnVisibilityOptions: ColumnOption = { observerName: true, phoneNumber: true, tags: true, - numberOfCompletedForms: true, + ngoName: true, numberOfFlaggedAnswers: true, followUpStatus: true, numberOfIncidentsSubmitted: true, @@ -396,7 +410,6 @@ export const observerIncidentReportsColumns: TableColumnVisibilityState = { diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index c1f520914..919c36eae 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -2,7 +2,7 @@ import { FormType, RatingScaleType, TranslatedString, UserPayload, ZFormType } f import { FormStatus } from '@/features/forms/models/form'; import i18n from '@/i18n'; import { redirect } from '@tanstack/react-router'; -import { type ClassValue, clsx } from 'clsx'; +import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { @@ -528,3 +528,12 @@ export const toBoolean = (value: string | undefined): boolean | undefined => { return undefined; }; + +export function getValueOrDefault(value: T | undefined | null, defaultValue: T): T { + return value !== undefined && value !== null ? value : defaultValue; +} + +export function omit(obj: T, key: K): Omit { + const { [key]: _, ...rest } = obj; + return rest; +} \ No newline at end of file diff --git a/web/src/locales/en.json b/web/src/locales/en.json index eeac91c49..18fe0c2bf 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -186,10 +186,6 @@ "cardTitle": "Citizen reports", "indicatorTitle": "citizen reports were signalled between {{interval}}" }, - "incidentReports": { - "cardTitle": "Incident reports", - "indicatorTitle": "incidents reports were signalled between {{interval}}" - }, "pollingStationsLevelsCards": { "onFieldObservers": "On field observers", "questionsAnswered": "Questions answered", @@ -197,8 +193,7 @@ "visitedPollingStations": "Visited polling stations", "pollingStationsCoverage": "Polling station coverage (%)", "timeSpentObserving": "Time spent observing (hours)", - "quickReports": "Quick reports", - "incidentReports": "Incident reports" + "quickReports": "Quick reports" } }, "electionEvent": { @@ -209,7 +204,13 @@ "englishTitle": "Election event English title", "country": "Country", "startDate": "Start date", - "status": "Status" + "status": "Status", + "coalition": { + "cardTitle": "Details on assigned organization(s)", + "coalitionName": "Coalition", + "leaderName": "Coalition coordinator", + "members": "Coalition members (NGOs)" + } }, "pollingStations": { "tabTitle": "Polling Stations", @@ -255,7 +256,8 @@ "language": "Language", "questions": "Questions", "status": "Status", - "updatedOn": "Updated on" + "updatedOn": "Updated on", + "sharedWith": "Shared with" }, "createDialogTitle": "Create form", "resetFilters": "Reset filters" @@ -352,5 +354,10 @@ "Some": "Some", "All": "All" } + }, + "responses": { + "title": "Responses", + "subtitle":"View all form answers and other reports submitted by your observers.", + "subtitleCoalitionLeader": "View all form answers and other reports submitted by coalition observers." } } \ No newline at end of file diff --git a/web/src/locales/ro.json b/web/src/locales/ro.json index 9f5e7b973..076d2f110 100644 --- a/web/src/locales/ro.json +++ b/web/src/locales/ro.json @@ -179,10 +179,6 @@ "indicatorTitle": "citizen reports were signalled between {{interval}}" }, - "incidentReports": { - "cardTitle": "Incident reports", - "indicatorTitle": "incidents reports were signalled between {{interval}}" - }, "pollingStationsLevelsCards": { "onFieldObservers": "On field observers", "questionsAnswered": "Questions answered", @@ -190,8 +186,7 @@ "visitedPollingStations": "Visited polling stations", "pollingStationsCoverage": "Polling station coverage (%)", "timeSpentObserving": "Time spent observing (hours)", - "quickReports": "Quick reports", - "incidentReports": "Incident reports" + "quickReports": "Quick reports" } }, "electionEvent": { @@ -202,7 +197,10 @@ "englishTitle": "Election event English title", "country": "Country", "startDate": "Start date", - "status": "Status" + "status": "Status", + "coalition": { + "cardTitle": "Details on assigned organization(s)" + } }, "pollingStations": { "tabTitle": "Polling Stations", @@ -249,7 +247,8 @@ "language": "Language", "questions": "Questions", "status": "Status", - "updatedOn": "Updated on" + "updatedOn": "Updated on", + "sharedWith": "Shared with" }, "createDialogTitle": "Create form", "resetFilters": "Reset filters" @@ -277,5 +276,9 @@ "goToNextPage": "Go to next page", "goToLastPage": "Go to last page" } + }, + "responses": { + "title": "", + "subtitle":"" } } diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index 56647f9e5..f26be3745 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -22,11 +22,11 @@ import { Route as ForgotPasswordIndexImport } from './routes/forgot-password/ind import { Route as ElectionRoundsIndexImport } from './routes/election-rounds/index' import { Route as ElectionEventIndexImport } from './routes/election-event/index' import { Route as AcceptInviteIndexImport } from './routes/accept-invite/index' -import { Route as ResponsesSubmissionIdImport } from './routes/responses/$submissionId' import { Route as ResetPasswordSuccessImport } from './routes/reset-password/success' import { Route as ObserversObserverIdImport } from './routes/observers/$observerId' import { Route as ObserverGuidesNewImport } from './routes/observer-guides/new' import { Route as NgosNgoIdImport } from './routes/ngos/$ngoId' +import { Route as MonitoringObserversImportImport } from './routes/monitoring-observers/import' import { Route as MonitoringObserversCreateNewMessageImport } from './routes/monitoring-observers/create-new-message' import { Route as MonitoringObserversTabImport } from './routes/monitoring-observers/$tab' import { Route as FormsFormIdImport } from './routes/forms/$formId' @@ -37,8 +37,8 @@ import { Route as CitizenGuidesNewImport } from './routes/citizen-guides/new' import { Route as AcceptInviteSuccessImport } from './routes/accept-invite/success' import { Route as ResponsesQuickReportsQuickReportIdImport } from './routes/responses/quick-reports/$quickReportId' import { Route as ResponsesIncidentReportsIncidentReportIdImport } from './routes/responses/incident-reports/$incidentReportId' +import { Route as ResponsesFormSubmissionsSubmissionIdImport } from './routes/responses/form-submissions/$submissionId' import { Route as ResponsesCitizenReportsCitizenReportIdImport } from './routes/responses/citizen-reports/$citizenReportId' -import { Route as ResponsesFormIdAggregatedImport } from './routes/responses/$formId.aggregated' import { Route as ObserversObserverIdEditImport } from './routes/observers_.$observerId.edit' import { Route as ObserverGuidesViewGuideIdImport } from './routes/observer-guides/view.$guideId' import { Route as ObserverGuidesEditGuideIdImport } from './routes/observer-guides/edit.$guideId' @@ -50,6 +50,7 @@ import { Route as CitizenNotificationsViewNotificationIdImport } from './routes/ import { Route as CitizenGuidesViewGuideIdImport } from './routes/citizen-guides/view.$guideId' import { Route as CitizenGuidesEditGuideIdImport } from './routes/citizen-guides/edit.$guideId' import { Route as ResponsesIncidentReportsFormIdAggregatedImport } from './routes/responses/incident-reports/$formId.aggregated' +import { Route as ResponsesFormSubmissionsFormIdAggregatedImport } from './routes/responses/form-submissions/$formId.aggregated' import { Route as ResponsesCitizenReportsFormIdAggregatedImport } from './routes/responses/citizen-reports/$formId.aggregated' import { Route as MonitoringObserversViewMonitoringObserverIdTabImport } from './routes/monitoring-observers/view/$monitoringObserverId.$tab' import { Route as MonitoringObserversPushMessagesIdViewImport } from './routes/monitoring-observers/push-messages.$id_.view' @@ -113,11 +114,6 @@ const AcceptInviteIndexRoute = AcceptInviteIndexImport.update({ getParentRoute: () => rootRoute, } as any) -const ResponsesSubmissionIdRoute = ResponsesSubmissionIdImport.update({ - path: '/responses/$submissionId', - getParentRoute: () => rootRoute, -} as any) - const ResetPasswordSuccessRoute = ResetPasswordSuccessImport.update({ path: '/reset-password/success', getParentRoute: () => rootRoute, @@ -138,6 +134,11 @@ const NgosNgoIdRoute = NgosNgoIdImport.update({ getParentRoute: () => rootRoute, } as any) +const MonitoringObserversImportRoute = MonitoringObserversImportImport.update({ + path: '/monitoring-observers/import', + getParentRoute: () => rootRoute, +} as any) + const MonitoringObserversCreateNewMessageRoute = MonitoringObserversCreateNewMessageImport.update({ path: '/monitoring-observers/create-new-message', @@ -192,17 +193,18 @@ const ResponsesIncidentReportsIncidentReportIdRoute = getParentRoute: () => rootRoute, } as any) +const ResponsesFormSubmissionsSubmissionIdRoute = + ResponsesFormSubmissionsSubmissionIdImport.update({ + path: '/responses/form-submissions/$submissionId', + getParentRoute: () => rootRoute, + } as any) + const ResponsesCitizenReportsCitizenReportIdRoute = ResponsesCitizenReportsCitizenReportIdImport.update({ path: '/responses/citizen-reports/$citizenReportId', getParentRoute: () => rootRoute, } as any) -const ResponsesFormIdAggregatedRoute = ResponsesFormIdAggregatedImport.update({ - path: '/responses/$formId/aggregated', - getParentRoute: () => rootRoute, -} as any) - const ObserversObserverIdEditRoute = ObserversObserverIdEditImport.update({ path: '/observers/$observerId/edit', getParentRoute: () => rootRoute, @@ -262,6 +264,12 @@ const ResponsesIncidentReportsFormIdAggregatedRoute = getParentRoute: () => rootRoute, } as any) +const ResponsesFormSubmissionsFormIdAggregatedRoute = + ResponsesFormSubmissionsFormIdAggregatedImport.update({ + path: '/responses/form-submissions/$formId/aggregated', + getParentRoute: () => rootRoute, + } as any) + const ResponsesCitizenReportsFormIdAggregatedRoute = ResponsesCitizenReportsFormIdAggregatedImport.update({ path: '/responses/citizen-reports/$formId/aggregated', @@ -334,6 +342,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MonitoringObserversCreateNewMessageImport parentRoute: typeof rootRoute } + '/monitoring-observers/import': { + preLoaderRoute: typeof MonitoringObserversImportImport + parentRoute: typeof rootRoute + } '/ngos/$ngoId': { preLoaderRoute: typeof NgosNgoIdImport parentRoute: typeof rootRoute @@ -350,10 +362,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResetPasswordSuccessImport parentRoute: typeof rootRoute } - '/responses/$submissionId': { - preLoaderRoute: typeof ResponsesSubmissionIdImport - parentRoute: typeof rootRoute - } '/accept-invite/': { preLoaderRoute: typeof AcceptInviteIndexImport parentRoute: typeof rootRoute @@ -434,14 +442,14 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ObserversObserverIdEditImport parentRoute: typeof rootRoute } - '/responses/$formId/aggregated': { - preLoaderRoute: typeof ResponsesFormIdAggregatedImport - parentRoute: typeof rootRoute - } '/responses/citizen-reports/$citizenReportId': { preLoaderRoute: typeof ResponsesCitizenReportsCitizenReportIdImport parentRoute: typeof rootRoute } + '/responses/form-submissions/$submissionId': { + preLoaderRoute: typeof ResponsesFormSubmissionsSubmissionIdImport + parentRoute: typeof rootRoute + } '/responses/incident-reports/$incidentReportId': { preLoaderRoute: typeof ResponsesIncidentReportsIncidentReportIdImport parentRoute: typeof rootRoute @@ -470,6 +478,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResponsesCitizenReportsFormIdAggregatedImport parentRoute: typeof rootRoute } + '/responses/form-submissions/$formId/aggregated': { + preLoaderRoute: typeof ResponsesFormSubmissionsFormIdAggregatedImport + parentRoute: typeof rootRoute + } '/responses/incident-reports/$formId/aggregated': { preLoaderRoute: typeof ResponsesIncidentReportsFormIdAggregatedImport parentRoute: typeof rootRoute @@ -489,11 +501,11 @@ export const routeTree = rootRoute.addChildren([ FormsFormIdRoute, MonitoringObserversTabRoute, MonitoringObserversCreateNewMessageRoute, + MonitoringObserversImportRoute, NgosNgoIdRoute, ObserverGuidesNewRoute, ObserversObserverIdRoute, ResetPasswordSuccessRoute, - ResponsesSubmissionIdRoute, AcceptInviteIndexRoute, ElectionEventIndexRoute, ElectionRoundsIndexRoute, @@ -514,8 +526,8 @@ export const routeTree = rootRoute.addChildren([ ObserverGuidesEditGuideIdRoute, ObserverGuidesViewGuideIdRoute, ObserversObserverIdEditRoute, - ResponsesFormIdAggregatedRoute, ResponsesCitizenReportsCitizenReportIdRoute, + ResponsesFormSubmissionsSubmissionIdRoute, ResponsesIncidentReportsIncidentReportIdRoute, ResponsesQuickReportsQuickReportIdRoute, CitizenReportAttachmentsElectionRoundIdCitizenReportIdAttachmentIdRoute, @@ -523,6 +535,7 @@ export const routeTree = rootRoute.addChildren([ MonitoringObserversPushMessagesIdViewRoute, MonitoringObserversViewMonitoringObserverIdTabRoute, ResponsesCitizenReportsFormIdAggregatedRoute, + ResponsesFormSubmissionsFormIdAggregatedRoute, ResponsesIncidentReportsFormIdAggregatedRoute, ]) diff --git a/web/src/routes/citizen-report-attachments/$electionRoundId.$citizenReportId.$attachmentId.tsx b/web/src/routes/citizen-report-attachments/$electionRoundId.$citizenReportId.$attachmentId.tsx index 1e9d70aab..1be170e0d 100644 --- a/web/src/routes/citizen-report-attachments/$electionRoundId.$citizenReportId.$attachmentId.tsx +++ b/web/src/routes/citizen-report-attachments/$electionRoundId.$citizenReportId.$attachmentId.tsx @@ -1,10 +1,10 @@ import { authApi } from '@/common/auth-api'; import { Attachment } from '@/features/responses/models/common'; -import { getFileCategory } from '@/lib/utils'; +import { getFileCategory, redirectIfNotAuth } from '@/lib/utils'; import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { useMemo } from 'react'; -import ReactPlayer from 'react-player'; +import ReactPlayer from 'react-player/lazy'; export const citizenReportAttachmentQueryOptions = ( electionRoundId: string, @@ -32,6 +32,9 @@ export const Route = createFileRoute('/citizen-report-attachments/$electionRound component: AttachmentDetails, loader: ({ context: { queryClient }, params: { electionRoundId, citizenReportId, attachmentId } }) => queryClient.ensureQueryData(citizenReportAttachmentQueryOptions(electionRoundId, citizenReportId, attachmentId)), + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function AttachmentDetails() { diff --git a/web/src/routes/election-rounds/$electionRoundId.tsx b/web/src/routes/election-rounds/$electionRoundId.tsx index f47652ce9..a9a89c2ee 100644 --- a/web/src/routes/election-rounds/$electionRoundId.tsx +++ b/web/src/routes/election-rounds/$electionRoundId.tsx @@ -1,6 +1,7 @@ import { authApi } from '@/common/auth-api'; import { ElectionRound } from '@/features/election-round/models/ElectionRound'; +import { redirectIfNotAuth } from '@/lib/utils'; import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; @@ -23,7 +24,10 @@ export const electionRoundQueryOptions = (electionRoundId: string) => { export const Route = createFileRoute('/election-rounds/$electionRoundId')({ component: ElectionRoundDetails, - loader: ({ context: { queryClient }, params: { electionRoundId } }) => queryClient.ensureQueryData(electionRoundQueryOptions(electionRoundId)) + loader: ({ context: { queryClient }, params: { electionRoundId } }) => queryClient.ensureQueryData(electionRoundQueryOptions(electionRoundId)), + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function ElectionRoundDetails() { diff --git a/web/src/routes/election-rounds/index.tsx b/web/src/routes/election-rounds/index.tsx index c579a602d..b09f8bdce 100644 --- a/web/src/routes/election-rounds/index.tsx +++ b/web/src/routes/election-rounds/index.tsx @@ -5,6 +5,7 @@ import Layout from '@/components/layout/Layout'; import { useCallback, type ReactElement } from 'react'; import CreateElectionRound from '@/features/election-round/components/CreateElectionRound'; import { useElectionRounds } from '@/features/election-round/queries'; +import { redirectIfNotAuth } from '@/lib/utils'; function ElectionRounds(): ReactElement { const navigate = useNavigate(); @@ -29,4 +30,7 @@ function ElectionRounds(): ReactElement { export const Route = createFileRoute('/election-rounds/')({ component: ElectionRounds, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); diff --git a/web/src/routes/forms/$formId.tsx b/web/src/routes/forms/$formId.tsx index ebb28beb7..9b03faf37 100644 --- a/web/src/routes/forms/$formId.tsx +++ b/web/src/routes/forms/$formId.tsx @@ -1,5 +1,6 @@ import type { FunctionComponent } from '@/common/types'; import { formDetailsQueryOptions } from '@/features/forms/queries'; +import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute, useLoaderData, useNavigate } from '@tanstack/react-router'; function Details(): FunctionComponent { @@ -26,5 +27,8 @@ export const Route = createFileRoute('/forms/$formId')({ const electionRoundId = currentElectionRoundContext.getState().currentElectionRoundId; return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId)) - } + }, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); diff --git a/web/src/routes/forms/$formId_.$languageCode.tsx b/web/src/routes/forms/$formId_.$languageCode.tsx index c35cffe36..6cbf8458c 100644 --- a/web/src/routes/forms/$formId_.$languageCode.tsx +++ b/web/src/routes/forms/$formId_.$languageCode.tsx @@ -1,5 +1,6 @@ import PreviewForm from '@/features/forms/components/PreviewForm/PreviewForm'; import { formDetailsQueryOptions } from '@/features/forms/queries'; +import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/forms/$formId/$languageCode')({ @@ -9,6 +10,9 @@ export const Route = createFileRoute('/forms/$formId/$languageCode')({ return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId)); }, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function Details() { diff --git a/web/src/routes/forms_.$formId.edit-translation.$languageCode.tsx b/web/src/routes/forms_.$formId.edit-translation.$languageCode.tsx index c4dfd9543..3614ed49f 100644 --- a/web/src/routes/forms_.$formId.edit-translation.$languageCode.tsx +++ b/web/src/routes/forms_.$formId.edit-translation.$languageCode.tsx @@ -1,13 +1,18 @@ import EditFormTranslation from '@/features/forms/components/EditFormTranslation/EditFormTranslation'; import { formDetailsQueryOptions } from '@/features/forms/queries'; +import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/forms/$formId/edit-translation/$languageCode')({ component: Edit, - loader: ({ context: { queryClient, currentElectionRoundContext }, params: { formId } }) =>{ + loader: ({ context: { queryClient, currentElectionRoundContext }, params: { formId } }) => { const electionRoundId = currentElectionRoundContext.getState().currentElectionRoundId; - return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId));} + return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId)); + }, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function Edit() { diff --git a/web/src/routes/forms_.$formId.edit.tsx b/web/src/routes/forms_.$formId.edit.tsx index 21e3e8230..3f1c21461 100644 --- a/web/src/routes/forms_.$formId.edit.tsx +++ b/web/src/routes/forms_.$formId.edit.tsx @@ -1,5 +1,6 @@ import EditForm from '@/features/forms/components/EditForm/EditForm'; import { formDetailsQueryOptions } from '@/features/forms/queries'; +import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/forms/$formId/edit')({ @@ -7,7 +8,10 @@ export const Route = createFileRoute('/forms/$formId/edit')({ loader: ({ context: { queryClient, currentElectionRoundContext }, params: { formId } }) => { const electionRoundId = currentElectionRoundContext.getState().currentElectionRoundId; - return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId)) + return queryClient.ensureQueryData(formDetailsQueryOptions(electionRoundId, formId)); + }, + beforeLoad: () => { + redirectIfNotAuth(); }, }); diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 7b9d105c5..7f36f9b0d 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -5,16 +5,22 @@ import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; import { useContext } from 'react'; -import type { FunctionComponent } from '../common/types'; -const Index = (): FunctionComponent => { +import { DataSources, type FunctionComponent } from '../common/types'; +import { z } from 'zod'; +const StatisticsDetails = (): FunctionComponent => { const { userRole } = useContext(AuthContext); - return userRole === 'PlatformAdmin' ? : + return userRole === 'PlatformAdmin' ? : ; }; +export const ZDataSourceSearchSchema = z.object({ + dataSource: z.nativeEnum(DataSources).catch(DataSources.Ngo).optional(), +}); + export const Route = createFileRoute('/')({ beforeLoad: () => { redirectIfNotAuth(); }, - component: Index + component: StatisticsDetails, + validateSearch: ZDataSourceSearchSchema, }); diff --git a/web/src/routes/monitoring-observers/edit.$monitoringObserverId.tsx b/web/src/routes/monitoring-observers/edit.$monitoringObserverId.tsx index 4d41c3325..4ef79975d 100644 --- a/web/src/routes/monitoring-observers/edit.$monitoringObserverId.tsx +++ b/web/src/routes/monitoring-observers/edit.$monitoringObserverId.tsx @@ -2,6 +2,7 @@ import { authApi } from '@/common/auth-api'; import EditMonitoringObserver from '@/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver'; import { monitoringObserversKeys } from '@/features/monitoring-observers/hooks/monitoring-observers-queries'; import { MonitoringObserver } from '@/features/monitoring-observers/models/monitoring-observer'; +import { redirectIfNotAuth } from '@/lib/utils'; import { queryOptions } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; @@ -30,6 +31,9 @@ export const Route = createFileRoute('/monitoring-observers/edit/$monitoringObse return queryClient.ensureQueryData(monitoringObserverDetailsQueryOptions(electionRoundId, monitoringObserverId)); }, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function Edit() { diff --git a/web/src/routes/monitoring-observers/import.tsx b/web/src/routes/monitoring-observers/import.tsx new file mode 100644 index 000000000..717dc7454 --- /dev/null +++ b/web/src/routes/monitoring-observers/import.tsx @@ -0,0 +1,10 @@ +import { MonitoringObserversImport } from '@/features/monitoring-observers/components/MonitoringObserversImport/MonitoringObserversImport'; +import { redirectIfNotAuth } from '@/lib/utils'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/monitoring-observers/import')({ + beforeLoad: () => { + redirectIfNotAuth(); + }, + component: MonitoringObserversImport +}); diff --git a/web/src/routes/monitoring-observers/push-messages.$id.tsx b/web/src/routes/monitoring-observers/push-messages.$id.tsx index b38ee2d44..2ef222e27 100644 --- a/web/src/routes/monitoring-observers/push-messages.$id.tsx +++ b/web/src/routes/monitoring-observers/push-messages.$id.tsx @@ -1,6 +1,7 @@ import type { FunctionComponent } from '@/common/types'; import { createFileRoute, useLoaderData, useNavigate } from '@tanstack/react-router'; import { pushMessageDetailsQueryOptions } from './push-messages.$id_.view'; +import { redirectIfNotAuth } from '@/lib/utils'; function Details(): FunctionComponent { const navigate = useNavigate({ from: '/monitoring-observers/push-messages/$id' }); @@ -23,5 +24,8 @@ export const Route = createFileRoute('/monitoring-observers/push-messages/$id')( const electionRoundId = currentElectionRoundContext.getState().currentElectionRoundId; return queryClient.ensureQueryData(pushMessageDetailsQueryOptions(electionRoundId, id)); - } + }, + beforeLoad: () => { + redirectIfNotAuth(); + }, }); diff --git a/web/src/routes/monitoring-observers/view/$monitoringObserverId.$tab.tsx b/web/src/routes/monitoring-observers/view/$monitoringObserverId.$tab.tsx index b0e9ab979..bef8a2084 100644 --- a/web/src/routes/monitoring-observers/view/$monitoringObserverId.$tab.tsx +++ b/web/src/routes/monitoring-observers/view/$monitoringObserverId.$tab.tsx @@ -29,7 +29,7 @@ const coerceTabSlug = (slug: string) => { if (slug?.toLowerCase()?.trim() === 'details') return 'details'; if (slug?.toLowerCase()?.trim() === 'responses') return 'responses'; if (slug?.toLowerCase()?.trim() === 'quick-reports') return 'quick-reports'; - if (slug?.toLowerCase()?.trim() === 'incident-reports') return 'incident-reports'; + // if (slug?.toLowerCase()?.trim() === 'incident-reports') return 'incident-reports'; return 'details'; }; diff --git a/web/src/routes/observers_.$observerId.edit.tsx b/web/src/routes/observers_.$observerId.edit.tsx index a3e15d08b..98210495b 100644 --- a/web/src/routes/observers_.$observerId.edit.tsx +++ b/web/src/routes/observers_.$observerId.edit.tsx @@ -1,11 +1,15 @@ import EditObserver from '@/features/observers/components/EditObserver/EditObserver'; import { createFileRoute } from '@tanstack/react-router'; import { observerDetailsQueryOptions } from './observers/$observerId'; +import { redirectIfNotAuth } from '@/lib/utils'; export const Route = createFileRoute('/observers/$observerId/edit')({ component: Edit, loader: ({ context: { queryClient }, params: { observerId } }) => queryClient.ensureQueryData(observerDetailsQueryOptions(observerId)), + beforeLoad: () => { + redirectIfNotAuth(); + }, }); function Edit() { diff --git a/web/src/routes/responses/$formId.aggregated.tsx b/web/src/routes/responses/form-submissions/$formId.aggregated.tsx similarity index 93% rename from web/src/routes/responses/$formId.aggregated.tsx rename to web/src/routes/responses/form-submissions/$formId.aggregated.tsx index e0296383d..f05891d80 100644 --- a/web/src/routes/responses/$formId.aggregated.tsx +++ b/web/src/routes/responses/form-submissions/$formId.aggregated.tsx @@ -8,6 +8,7 @@ import { buildURLSearchParams, redirectIfNotAuth } from '@/lib/utils'; import { queryOptions } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; +import { ZDataSourceSearchSchema } from '../..'; export function formAggregatedDetailsQueryOptions( electionRoundId: string, @@ -51,15 +52,15 @@ export const SubmissionsAggregatedByFormSchema = z.object({ questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), hasNotes: z.string().catch('').optional(), hasAttachments: z.string().catch('').optional(), - formIsCompleted: z.string().catch('').optional(), submissionsFromDate: z.coerce.date().optional(), submissionsToDate: z.coerce.date().optional(), -}); + coalitionMemberId: z.string().optional() +}).merge(ZDataSourceSearchSchema); export type SubmissionsAggregatedByFormParams = z.infer; -export const Route = createFileRoute('/responses/$formId/aggregated')({ +export const Route = createFileRoute('/responses/form-submissions/$formId/aggregated')({ beforeLoad: () => { redirectIfNotAuth(); }, diff --git a/web/src/routes/responses/$submissionId.tsx b/web/src/routes/responses/form-submissions/$submissionId.tsx similarity index 94% rename from web/src/routes/responses/$submissionId.tsx rename to web/src/routes/responses/form-submissions/$submissionId.tsx index 4d70438f3..05fa817c4 100644 --- a/web/src/routes/responses/$submissionId.tsx +++ b/web/src/routes/responses/form-submissions/$submissionId.tsx @@ -20,7 +20,7 @@ export function formSubmissionDetailsQueryOptions(electionRoundId: string, submi }); } -export const Route = createFileRoute('/responses/$submissionId')({ +export const Route = createFileRoute('/responses/form-submissions/$submissionId')({ beforeLoad: () => { redirectIfNotAuth(); }, diff --git a/web/src/routes/responses/index.tsx b/web/src/routes/responses/index.tsx index 7b410d9f8..45e8a8c05 100644 --- a/web/src/routes/responses/index.tsx +++ b/web/src/routes/responses/index.tsx @@ -1,8 +1,12 @@ import ResponsesDashboard from '@/features/responses/components/Dashboard/Dashboard'; import { FormSubmissionsSearchParamsSchema } from '@/features/responses/models/search-params'; +import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/responses/')({ component: () => , + beforeLoad: () => { + redirectIfNotAuth(); + }, validateSearch: FormSubmissionsSearchParamsSchema, }); diff --git a/web/src/styles/tailwind.css b/web/src/styles/tailwind.css index 26ff6ba37..f98f3aae7 100644 --- a/web/src/styles/tailwind.css +++ b/web/src/styles/tailwind.css @@ -69,7 +69,7 @@ } .election-text { - max-width: 160px; + max-width: 350px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;