From 96d18ebd1a32ab765fbf4d1cb345d3bfad68e9f5 Mon Sep 17 00:00:00 2001 From: "ion.dormenco" Date: Wed, 3 Jun 2026 13:07:47 +0300 Subject: [PATCH] optimize detailed submissions response --- Requests/form-submission.http | 11 ++- .../DetailedSubmissionEntry.cs | 41 ++++++++ .../ListEntriesDetailed/Endpoint.cs | 97 ++++++++++--------- 3 files changed, 99 insertions(+), 50 deletions(-) create mode 100644 api/src/Feature.Form.Submissions/ListEntriesDetailed/DetailedSubmissionEntry.cs diff --git a/Requests/form-submission.http b/Requests/form-submission.http index 07a2fe898..e4047194d 100644 --- a/Requests/form-submission.http +++ b/Requests/form-submission.http @@ -1,12 +1,19 @@ -@electionRoundId=ec618650-b83d-4969-ae18-f638edeedae4 +@electionRoundId=6bdf707f-e4cf-49a1-9bd7-27f68bc7a12b @pollingStationId=5d00b66f-d874-4936-9eca-5eac0d4baf7c # get election round forms -GET {{$dotenv HOST }}/api/election-rounds/{{electionRoundId}}/forms:fetchAll +GET {{$dotenv HOST }}/api/election-rounds/{{electionRoundId}}/forms?formStatusFilter=Published Authorization: Bearer {{$dotenv JWT_TOKEN}} ### +# get election round forms +GET {{$dotenv HOST }}/api/election-rounds/{{electionRoundId}}/form-submissions:byEntryDetailed +Authorization: Bearer {{$dotenv JWT_TOKEN}} + +### + + ### # upsert a form submission POST {{$dotenv HOST }}/api/election-rounds/{{electionRoundId}}/form-submissions diff --git a/api/src/Feature.Form.Submissions/ListEntriesDetailed/DetailedSubmissionEntry.cs b/api/src/Feature.Form.Submissions/ListEntriesDetailed/DetailedSubmissionEntry.cs new file mode 100644 index 000000000..daf4e8426 --- /dev/null +++ b/api/src/Feature.Form.Submissions/ListEntriesDetailed/DetailedSubmissionEntry.cs @@ -0,0 +1,41 @@ +using Module.Answers.Models; +using Module.Forms.Models; +using Vote.Monitor.Core.Models; + +namespace Feature.Form.Submissions.ListEntriesDetailed; + +public record DetailedSubmissionEntry +{ + public Guid SubmissionId { get; init; } + public DateTime CreatedAt { get; init; } + public DateTime? LastUpdatedAt { get; init; } + public Guid FormId { get; init; } + + public SubmissionFollowUpStatus FollowUpStatus { get; init; } = null!; + + public Guid PollingStationId { get; init; } + public string Level1 { get; init; } = null!; + public string Level2 { get; init; } = null!; + public string Level3 { get; init; } = null!; + public string Level4 { get; init; } = null!; + public string Level5 { get; init; } = null!; + public string Number { get; init; } = null!; + public Guid MonitoringObserverId { get; init; } + public bool IsOwnObserver { get; init; } + public string ObserverName { get; init; } = null!; + public string Email { get; init; } = null!; + public string? PhoneNumber { get; init; } = null!; + public string[] Tags { get; init; } = []; + public string NgoName { get; init; } = null!; + public int NumberOfFlaggedAnswers { get; init; } + public int NumberOfQuestionsAnswered { get; init; } + + public BaseAnswerModel[] Answers { get; init; } = []; + public NoteModel[] Notes { get; init; } = []; + public AttachmentModel[] Attachments { get; init; } = []; + + public DateTime? ArrivalTime { get; init; } + public DateTime? DepartureTime { get; init; } + public ObservationBreakModel[] Breaks { get; init; } = []; + public bool IsCompleted { get; init; } +} diff --git a/api/src/Feature.Form.Submissions/ListEntriesDetailed/Endpoint.cs b/api/src/Feature.Form.Submissions/ListEntriesDetailed/Endpoint.cs index 7982aeeb8..384531b51 100644 --- a/api/src/Feature.Form.Submissions/ListEntriesDetailed/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListEntriesDetailed/Endpoint.cs @@ -1,4 +1,3 @@ -using Module.Answers.Models; using Vote.Monitor.Core.Models; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Domain.Specifications; @@ -9,7 +8,7 @@ public class Endpoint( IAuthorizationService authorizationService, INpgsqlConnectionFactory dbConnectionFactory, IFileStorageService fileStorageService) - : Endpoint>, NotFound>> + : Endpoint>, NotFound>> { public override void Configure() { @@ -20,7 +19,7 @@ public override void Configure() Summary(x => { x.Summary = "Lists form submissions by entry detailed"; }); } - public override async Task>, NotFound>> ExecuteAsync(Request req, + public override async Task>, NotFound>> ExecuteAsync(Request req, CancellationToken ct) { var authorizationResult = @@ -148,7 +147,6 @@ WITH polling_station_submissions AS (SELECT psi."Id" AS psi."Answers", '[]'::jsonb AS "Attachments", '[]'::jsonb AS "Notes", - psif."Questions", psi."ArrivalTime", psi."DepartureTime", psi."Breaks", @@ -156,10 +154,9 @@ WITH polling_station_submissions AS (SELECT psi."Id" AS psi."NumberOfFlaggedAnswers", 0 AS "MediaFilesCount", 0 AS "NotesCount", - psi."LastUpdatedAt" AS "TimeSubmitted", + psi."LastUpdatedAt" AS "CreatedAt", + psi."LastUpdatedAt" AS "LastUpdatedAt", psi."FollowUpStatus", - psif."DefaultLanguage", - psif."Name" as "FormName", psi."IsCompleted" FROM "PollingStationInformation" psi INNER JOIN "PollingStationInformationForms" psif @@ -213,7 +210,6 @@ form_submissions AS (SELECT fs."Id" OR N."SubmissionId" = FS."Id" ) AND n."MonitoringObserverId" = fs."MonitoringObserverId"), '[]'::JSONB) AS "Notes", - f."Questions", NULL::timestamp AS "ArrivalTime", NULL::timestamp AS "DepartureTime", '[]'::jsonb AS "Breaks", @@ -236,10 +232,9 @@ form_submissions AS (SELECT fs."Id" OR N."SubmissionId" = FS."Id" ) AND N."MonitoringObserverId" = fs."MonitoringObserverId") AS "NotesCount", - fs."LastUpdatedAt" AS "TimeSubmitted", + fs."LastUpdatedAt" AS "CreatedAt", + fs."LastUpdatedAt" AS "LastUpdatedAt", fs."FollowUpStatus", - f."DefaultLanguage", - f."Name" as "FormName", fs."IsCompleted" FROM "FormSubmissions" fs INNER JOIN "Forms" f ON f."Id" = fs."FormId" @@ -265,12 +260,11 @@ OR mo."PhoneNumber" ILIKE @searchText f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") OR (@questionsAnswered = 'None' AND fs."NumberOfQuestionsAnswered" = 0))) SELECT s."SubmissionId", - s."TimeSubmitted", + s."CreatedAt", + s."LastUpdatedAt", s."FormId", s."FormCode", s."FormType", - s."DefaultLanguage", - s."FormName", ps."Id" AS "PollingStationId", ps."Level1", ps."Level2", @@ -290,7 +284,6 @@ OR mo."PhoneNumber" ILIKE @searchText s."NumberOfFlaggedAnswers", s."MediaFilesCount", s."NotesCount", - s."Questions", s."Answers", s."Notes", s."Attachments", @@ -325,8 +318,10 @@ UNION ALL 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, + ORDER BY CASE WHEN @sortExpression = 'LastUpdatedAt ASC' THEN s."LastUpdatedAt" END ASC, + CASE WHEN @sortExpression = 'LastUpdatedAt DESC' THEN s."LastUpdatedAt" END DESC, + CASE WHEN @sortExpression = 'CreatedAt ASC' THEN s."CreatedAt" END ASC, + CASE WHEN @sortExpression = 'CreatedAt DESC' THEN s."CreatedAt" 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, @@ -389,13 +384,13 @@ UNION ALL }; int totalRowCount; - List entries; + 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(); + entries = multi.Read().ToList(); } entries = (await Task.WhenAll( @@ -415,14 +410,14 @@ UNION ALL )).ToList(); return TypedResults.Ok( - new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); + new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); } private static string GetSortExpression(string? sortColumnName, bool isAscendingSorting) { if (string.IsNullOrWhiteSpace(sortColumnName)) { - return $"{nameof(FormSubmissionViewV2.TimeSubmitted)} DESC"; + return $"{nameof(DetailedSubmissionEntry.CreatedAt)} DESC"; } var sortOrder = isAscendingSorting ? "ASC" : "DESC"; @@ -439,76 +434,76 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"FormType {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Level1), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Level1), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Level1)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Level1)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Level2), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Level2), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Level2)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Level2)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Level3), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Level3), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Level3)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Level3)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Level4), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Level4), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Level4)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Level4)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Level5), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Level5), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Level5)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Level5)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Number), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Number), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Number)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Number)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.ObserverName), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.ObserverName), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.ObserverName)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.ObserverName)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Email), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Email), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Email)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Email)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.PhoneNumber), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.PhoneNumber), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.PhoneNumber)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.PhoneNumber)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.Tags), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.Tags), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.Tags)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.Tags)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.NumberOfQuestionsAnswered), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.NumberOfQuestionsAnswered), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.NumberOfQuestionsAnswered)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.NumberOfQuestionsAnswered)} {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.NumberOfFlaggedAnswers), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.NumberOfFlaggedAnswers), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.NumberOfFlaggedAnswers)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.NumberOfFlaggedAnswers)} {sortOrder}"; } if (string.Equals(sortColumnName, "MediaFilesCount", @@ -523,10 +518,16 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"NotesCount {sortOrder}"; } - if (string.Equals(sortColumnName, nameof(FormSubmissionViewV2.TimeSubmitted), + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.CreatedAt), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(FormSubmissionViewV2.TimeSubmitted)} {sortOrder}"; + return $"{nameof(DetailedSubmissionEntry.CreatedAt)} {sortOrder}"; + } + + if (string.Equals(sortColumnName, nameof(DetailedSubmissionEntry.LastUpdatedAt), + StringComparison.InvariantCultureIgnoreCase)) + { + return $"{nameof(DetailedSubmissionEntry.LastUpdatedAt)} {sortOrder}"; } if (string.Equals(sortColumnName, "MonitoringObserverStatus", @@ -535,6 +536,6 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"MonitoringObserverStatus {sortOrder}"; } - return $"{nameof(FormSubmissionViewV2.TimeSubmitted)} DESC"; + return $"{nameof(DetailedSubmissionEntry.CreatedAt)} DESC"; } }