diff --git a/Antifraude.Net/ApiDenuncias/Configuration/GestionaOptions.cs b/Antifraude.Net/ApiDenuncias/Configuration/GestionaOptions.cs index 5539954..d208ae4 100644 --- a/Antifraude.Net/ApiDenuncias/Configuration/GestionaOptions.cs +++ b/Antifraude.Net/ApiDenuncias/Configuration/GestionaOptions.cs @@ -13,6 +13,8 @@ namespace ApiDenuncias.Configuration public string? CircuitUpdateTemplateName { get; set; } public string? CircuitUpdateSajTemplateName { get; set; } public string? CircuitUpdateSdiTemplateName { get; set; } + public string? CircuitCommunicationSajTemplateName { get; set; } + public string? CircuitCommunicationSdiTemplateName { get; set; } public string? CircuitSignerStampTitle { get; set; } public string? CircuitVersion { get; set; } public string? DocumentMetadataLanguage { get; set; } diff --git a/Antifraude.Net/ApiDenuncias/Controllers/ConfigurationController.cs b/Antifraude.Net/ApiDenuncias/Controllers/ConfigurationController.cs index 4e98c79..194900a 100644 --- a/Antifraude.Net/ApiDenuncias/Controllers/ConfigurationController.cs +++ b/Antifraude.Net/ApiDenuncias/Controllers/ConfigurationController.cs @@ -61,6 +61,16 @@ public sealed class ConfigurationController : ControllerBase return Ok(await _workGroupService.GetAsync(cancellationToken)); } + [HttpGet("work-groups/current")] + public async Task> GetCurrentUserWorkGroups( + CancellationToken cancellationToken) + { + var username = User.Identity?.Name ?? + throw new InvalidOperationException("No hay usuario autenticado."); + + return Ok(await _workGroupService.GetUserGroupsAsync(username, cancellationToken)); + } + [HttpPut("work-groups/users/{username}")] [Authorize(Policy = "ConfigurationAdministrators")] public async Task> SetUserWorkGroups( diff --git a/Antifraude.Net/ApiDenuncias/Controllers/GestionaController.cs b/Antifraude.Net/ApiDenuncias/Controllers/GestionaController.cs index 182ee79..bea6e3c 100644 --- a/Antifraude.Net/ApiDenuncias/Controllers/GestionaController.cs +++ b/Antifraude.Net/ApiDenuncias/Controllers/GestionaController.cs @@ -194,7 +194,8 @@ public sealed class GestionaController : ControllerBase request.DocumentUrl, request.AssignedGroupCode, request.ComplaintId, - request.IsUpdate); + request.IsUpdate, + request.UpdateSource); return Ok(new { ok = true }); } diff --git a/Antifraude.Net/ApiDenuncias/Controllers/InboxController.cs b/Antifraude.Net/ApiDenuncias/Controllers/InboxController.cs index b3cb4f0..2e28a6b 100644 --- a/Antifraude.Net/ApiDenuncias/Controllers/InboxController.cs +++ b/Antifraude.Net/ApiDenuncias/Controllers/InboxController.cs @@ -316,7 +316,12 @@ public sealed class InboxController : ControllerBase json = null; } - var result = await _inboxService.ImportFromGlobalLeaksAsync(reportPackage, json, reportDetail, cancellationToken); + var result = await _inboxService.ImportFromGlobalLeaksAsync( + reportPackage, + json, + reportDetail, + report, + cancellationToken); if (result.ImportedCount > 0) { await _trackingService.MarkReportImportedAsync( diff --git a/Antifraude.Net/ApiDenuncias/Scripts/gestiondenuncias_schema.sql b/Antifraude.Net/ApiDenuncias/Scripts/gestiondenuncias_schema.sql index 86e16bb..6309bf3 100644 --- a/Antifraude.Net/ApiDenuncias/Scripts/gestiondenuncias_schema.sql +++ b/Antifraude.Net/ApiDenuncias/Scripts/gestiondenuncias_schema.sql @@ -66,6 +66,7 @@ CREATE TABLE IF NOT EXISTS complaints ( gestiona_uploaded_at_utc DATETIME(6) NULL, gestiona_last_upload_type TEXT NOT NULL, gestiona_assigned_group TEXT NOT NULL, + pending_update_source VARCHAR(256) NOT NULL DEFAULT '', is_in_gestiona TINYINT(1) NOT NULL DEFAULT 0, is_rejected TINYINT(1) NOT NULL DEFAULT 0, created_at_utc DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), diff --git a/Antifraude.Net/ApiDenuncias/Services/DenunciaInboxService.cs b/Antifraude.Net/ApiDenuncias/Services/DenunciaInboxService.cs index 5cacfb9..6dadb70 100644 --- a/Antifraude.Net/ApiDenuncias/Services/DenunciaInboxService.cs +++ b/Antifraude.Net/ApiDenuncias/Services/DenunciaInboxService.cs @@ -84,6 +84,7 @@ public sealed class DenunciaInboxService FileDownloadResult reportDownload, FileDownloadResult? jsonDownload, ReportDetailDto? reportDetail, + ReportDto inboxReport, CancellationToken cancellationToken = default) { await EnsureStorageReadyAsync(cancellationToken); @@ -102,7 +103,13 @@ public sealed class DenunciaInboxService try { - var result = await ProcessGlobalLeaksPackageAsync(reportDownload.Content, sourceName, json, reportDetail, cancellationToken); + var result = await ProcessGlobalLeaksPackageAsync( + reportDownload.Content, + sourceName, + json, + reportDetail, + inboxReport, + cancellationToken); return new ImportSummary( 1, result.ImportedCount, @@ -164,6 +171,7 @@ public sealed class DenunciaInboxService string sourceName, string? globalLeaksJson, ReportDetailDto? reportDetail, + ReportDto inboxReport, CancellationToken cancellationToken) { using var packageStream = new MemoryStream(packageBytes, writable: false); @@ -214,6 +222,12 @@ public sealed class DenunciaInboxService $"No se ha podido determinar el identificador de la denuncia en {sourceName}."); } + denuncia.PendingUpdateSource = inboxReport.CitizenHasNewActivity + ? ComplaintUpdateSources.Citizen + : inboxReport.ReceiverHasNewActivity + ? ComplaintUpdateSources.Receiver + : string.Empty; + if (reportIsPdf) { reportText = BuildSyntheticReportText(denuncia); @@ -246,14 +260,17 @@ public sealed class DenunciaInboxService var storedComplaint = await _denunciaStore.GetDenunciaByIdAsync(denuncia.Id_Denuncia, cancellationToken); if (IsAlreadyUploadedToGestiona(storedComplaint)) { - var storedFiles = await GetFicherosMetadataByDenunciaForImportAsync(denuncia.Id_Denuncia, cancellationToken); + var storedFiles = await GetFicherosMetadataByDenunciaForImportAsync( + denuncia.Id_Denuncia, + cancellationToken); if (!HasPendingFilesForGestiona(storedFiles)) { storedComplaint!.EsActualizacion = false; storedComplaint.EnGestiona = true; + storedComplaint.PendingUpdateSource = string.Empty; await _denunciaStore.UpsertDenunciaAsync(storedComplaint, cancellationToken); - warnings.Add($"La denuncia #{denuncia.Id_Denuncia} ya esta en Gestiona y no tiene documentos nuevos pendientes de subir."); + warnings.Add(BuildNoCitizenUpdateWarning(inboxReport)); return new ProcessPackageResult(denuncia.Id_Denuncia, 0, warnings); } } @@ -379,6 +396,13 @@ public sealed class DenunciaInboxService } } + private static string BuildNoCitizenUpdateWarning(ReportDto inboxReport) + { + return inboxReport.ReceiverHasNewActivity && !inboxReport.CitizenHasNewActivity + ? "La actividad detectada procede de la OAAF, pero el report descargado no contiene cambios pendientes de subir a Gestiona." + : "La denuncia ya esta en Gestiona y el report no contiene documentos ni comentarios nuevos del ciudadano pendientes de subir."; + } + private static bool HasPendingFilesForGestiona(IReadOnlyList files) { var plannedHashes = files @@ -591,6 +615,7 @@ public sealed class DenunciaInboxService target.Pais = source.Pais; target.CamposFormularioJson = source.CamposFormularioJson; target.TextoOriginalReport = source.TextoOriginalReport; + target.PendingUpdateSource = source.PendingUpdateSource; } private static bool IsSupportedAttachmentEntry(ZipArchiveEntry entry) diff --git a/Antifraude.Net/ApiDenuncias/Services/EncryptedDenunciaStore.cs b/Antifraude.Net/ApiDenuncias/Services/EncryptedDenunciaStore.cs index f8d8e6e..a9ccbee 100644 --- a/Antifraude.Net/ApiDenuncias/Services/EncryptedDenunciaStore.cs +++ b/Antifraude.Net/ApiDenuncias/Services/EncryptedDenunciaStore.cs @@ -280,6 +280,7 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore, IFilteredDenunciaSt FechaSubidaAGestiona = source.FechaSubidaAGestiona, UltimaSubidaGestionaTipo = source.UltimaSubidaGestionaTipo, UltimoGrupoAsignadoGestiona = source.UltimoGrupoAsignadoGestiona, + PendingUpdateSource = source.PendingUpdateSource, EnGestiona = source.EnGestiona, EnRechazada = source.EnRechazada, @@ -361,6 +362,7 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore, IFilteredDenunciaSt target.FechaSubidaAGestiona = stored.FechaSubidaAGestiona; target.UltimaSubidaGestionaTipo = stored.UltimaSubidaGestionaTipo; target.UltimoGrupoAsignadoGestiona = stored.UltimoGrupoAsignadoGestiona; + target.PendingUpdateSource = stored.PendingUpdateSource; target.EnGestiona = stored.EnGestiona; target.EnRechazada = stored.EnRechazada; } diff --git a/Antifraude.Net/ApiDenuncias/Services/GestionaDocumentWorkflowService.cs b/Antifraude.Net/ApiDenuncias/Services/GestionaDocumentWorkflowService.cs index 4b727c1..0dede3a 100644 --- a/Antifraude.Net/ApiDenuncias/Services/GestionaDocumentWorkflowService.cs +++ b/Antifraude.Net/ApiDenuncias/Services/GestionaDocumentWorkflowService.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Globalization; using System.Text; using System.Text.Json; +using GestionaDenuncias.Shared.Models; using Microsoft.Extensions.Logging; namespace ApiDenuncias.Services; @@ -124,11 +125,20 @@ public sealed class GestionaDocumentWorkflowService string documentUrl, string assignedGroupCode, int? complaintId = null, - bool isUpdate = false) + bool isUpdate = false, + string? updateSource = null) { var docUrlAbs = EnsureAbsoluteGestionaUrl(documentUrl, GestionaApiBase); - var operationLabel = isUpdate ? $"actualizacion grupo {NormalizeGroupCode(assignedGroupCode)}" : "nueva denuncia"; - var (templateHref, payload) = await ResolveCircuitTemplatePayloadAsync(docUrlAbs, isUpdate, assignedGroupCode); + var operationLabel = ComplaintUpdateSources.IsReceiver(updateSource) + ? $"comunicacion OAAF grupo {NormalizeGroupCode(assignedGroupCode)}" + : isUpdate + ? $"actualizacion grupo {NormalizeGroupCode(assignedGroupCode)}" + : "nueva denuncia"; + var (templateHref, payload) = await ResolveCircuitTemplatePayloadAsync( + docUrlAbs, + isUpdate, + assignedGroupCode, + updateSource); var (success, statusCode, body) = await TryPostCircuitAsync(docUrlAbs, payload); if (success) { @@ -155,7 +165,8 @@ public sealed class GestionaDocumentWorkflowService private async Task<(string TemplateHref, string Payload)> ResolveCircuitTemplatePayloadAsync( string documentUrl, bool isUpdate, - string? assignedGroupCode) + string? assignedGroupCode, + string? updateSource) { var templatesUrl = $"{documentUrl.TrimEnd('/')}/circuit/templates"; var templates = await GetCircuitTemplatesAsync(templatesUrl); @@ -164,7 +175,7 @@ public sealed class GestionaDocumentWorkflowService throw new InvalidOperationException("Gestiona no ha devuelto plantillas de circuito para el documento."); } - var selection = GetOperationCircuitTemplateSelection(isUpdate, assignedGroupCode); + var selection = GetOperationCircuitTemplateSelection(isUpdate, assignedGroupCode, updateSource); if (!string.IsNullOrWhiteSpace(selection.TemplateName)) { var resolved = await TryResolveCircuitTemplatePayloadByNameAsync(templates, selection.TemplateName); @@ -211,7 +222,10 @@ public sealed class GestionaDocumentWorkflowService "No se puede seleccionar de forma inequivoca la plantilla de circuito. Configura Gestiona:CircuitTemplateName o Gestiona:CircuitSignerStampTitle."); } - private CircuitTemplateSelection GetOperationCircuitTemplateSelection(bool isUpdate, string? assignedGroupCode) + private CircuitTemplateSelection GetOperationCircuitTemplateSelection( + bool isUpdate, + string? assignedGroupCode, + string? updateSource) { if (!isUpdate) { @@ -221,6 +235,25 @@ public sealed class GestionaDocumentWorkflowService OperationLabel: "nueva denuncia"); } + if (ComplaintUpdateSources.IsReceiver(updateSource)) + { + return NormalizeGroupCode(assignedGroupCode) switch + { + "510" => new CircuitTemplateSelection( + _configuration["Gestiona:CircuitCommunicationSdiTemplateName"], + Required: true, + OperationLabel: "comunicacion SDI a denunciante"), + + "600" => new CircuitTemplateSelection( + _configuration["Gestiona:CircuitCommunicationSajTemplateName"], + Required: true, + OperationLabel: "comunicacion SAJ a denunciante"), + + _ => throw new InvalidOperationException( + "Las comunicaciones de la OAAF solo pueden tramitarse con los grupos 510 o 600.") + }; + } + var defaultUpdateTemplateName = FirstConfigured( _configuration["Gestiona:CircuitTemplateName"], _configuration["Gestiona:CircuitUpdateTemplateName"]); diff --git a/Antifraude.Net/ApiDenuncias/Services/GlobalLeaksClient.cs b/Antifraude.Net/ApiDenuncias/Services/GlobalLeaksClient.cs index bc0cb13..3de5c48 100644 --- a/Antifraude.Net/ApiDenuncias/Services/GlobalLeaksClient.cs +++ b/Antifraude.Net/ApiDenuncias/Services/GlobalLeaksClient.cs @@ -950,6 +950,15 @@ public sealed class GlobalLeaksClient private static DateTimeOffset? GetActivityReference(ReportDto report, DateTimeOffset? fallbackReference) { + if (report.AlreadyInGestiona) + { + var lastGestionaUpload = ParseDate(report.LastGestionaUploadAt); + if (lastGestionaUpload is not null) + { + return lastGestionaUpload; + } + } + return ParseDate(report.LastDownloadedAt) ?? fallbackReference ?? ParseDate(report.LastAccess) ?? diff --git a/Antifraude.Net/ApiDenuncias/Services/InboxTrackingService.cs b/Antifraude.Net/ApiDenuncias/Services/InboxTrackingService.cs index 6d294d6..5441176 100644 --- a/Antifraude.Net/ApiDenuncias/Services/InboxTrackingService.cs +++ b/Antifraude.Net/ApiDenuncias/Services/InboxTrackingService.cs @@ -102,6 +102,7 @@ public sealed class InboxTrackingService : IInboxTrackingService DownloadedByAnotherUser = meta?.DownloadedByAnotherUser ?? false, LastDownloadedByUsername = meta?.LastDownloadedByUsername, LastDownloadedAt = meta?.LastDownloadedAtUtc?.ToString("O", CultureInfo.InvariantCulture), + LastGestionaUploadAt = meta?.LastGestionaUploadAtUtc?.ToString("O", CultureInfo.InvariantCulture), AlreadyImported = meta?.AlreadyImported ?? false, AlreadyInGestiona = meta?.AlreadyInGestiona ?? false, OwnerUsername = meta?.OwnerUsername, @@ -535,6 +536,15 @@ public sealed class InboxTrackingService : IInboxTrackingService owner.username AS owner_username, ir.imported_to_store_at_utc, COALESCE(c.is_in_gestiona, 0) AS already_in_gestiona, + COALESCE( + ( + SELECT MAX(history.uploaded_at_utc) + FROM gestiona_upload_history history + WHERE history.external_report_id = + COALESCE(ir.imported_complaint_report_id, ir.progressive_id) + ), + c.gestiona_uploaded_at_utc + ) AS last_gestiona_upload_at_utc, CASE WHEN uir.last_downloaded_at_utc IS NULL THEN 0 ELSE 1 END AS downloaded_by_current_user, CASE WHEN ir.owner_user_id = @userId THEN 1 ELSE 0 END AS owned_by_current_user, CASE @@ -599,6 +609,7 @@ public sealed class InboxTrackingService : IInboxTrackingService DownloadedByAnotherUser = downloadedByAnotherUser, LastDownloadedByUsername = lastDownloadedByUsername, LastDownloadedAtUtc = GetDateTimeOffset(reader, "last_downloaded_at_utc"), + LastGestionaUploadAtUtc = GetDateTimeOffset(reader, "last_gestiona_upload_at_utc"), AlreadyImported = !reader.IsDBNull(reader.GetOrdinal("imported_to_store_at_utc")), AlreadyInGestiona = reader.GetInt32(reader.GetOrdinal("already_in_gestiona")) == 1, OwnerUsername = ownerUsername, @@ -748,6 +759,7 @@ public sealed class InboxTrackingService : IInboxTrackingService public bool DownloadedByAnotherUser { get; init; } public string? LastDownloadedByUsername { get; init; } public DateTimeOffset? LastDownloadedAtUtc { get; init; } + public DateTimeOffset? LastGestionaUploadAtUtc { get; init; } public bool AlreadyImported { get; init; } public bool AlreadyInGestiona { get; init; } public string? OwnerUsername { get; init; } diff --git a/Antifraude.Net/ApiDenuncias/Services/MySqlDenunciaStore.cs b/Antifraude.Net/ApiDenuncias/Services/MySqlDenunciaStore.cs index aef4034..a9f959b 100644 --- a/Antifraude.Net/ApiDenuncias/Services/MySqlDenunciaStore.cs +++ b/Antifraude.Net/ApiDenuncias/Services/MySqlDenunciaStore.cs @@ -116,6 +116,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore gestiona_uploaded_at_utc, gestiona_last_upload_type, gestiona_assigned_group, + pending_update_source, is_in_gestiona, is_rejected, key_date, @@ -184,6 +185,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore ("complaints", "encrypted_at_utc", "`encrypted_at_utc` DATETIME(6) NULL"), ("complaints", "gestiona_last_upload_type", "`gestiona_last_upload_type` VARCHAR(64) NOT NULL DEFAULT ''"), ("complaints", "gestiona_assigned_group", "`gestiona_assigned_group` VARCHAR(255) NOT NULL DEFAULT ''"), + ("complaints", "pending_update_source", "`pending_update_source` VARCHAR(256) NOT NULL DEFAULT ''"), ("inbox_reports", "owner_user_id", "`owner_user_id` BIGINT NULL"), ("complaint_attachments", "content_sha256", "`content_sha256` CHAR(64) NOT NULL DEFAULT ''"), ("complaint_attachments", "key_date", "`key_date` DATE NULL"), @@ -711,6 +713,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore gestiona_uploaded_at_utc, gestiona_last_upload_type, gestiona_assigned_group, + pending_update_source, is_in_gestiona, is_rejected, key_date, @@ -783,6 +786,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore @gestionaUploadedAtUtc, @gestionaLastUploadType, @gestionaAssignedGroup, + @pendingUpdateSource, @isInGestiona, @isRejected, @keyDate, @@ -855,6 +859,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore gestiona_uploaded_at_utc = VALUES(gestiona_uploaded_at_utc), gestiona_last_upload_type = VALUES(gestiona_last_upload_type), gestiona_assigned_group = VALUES(gestiona_assigned_group), + pending_update_source = VALUES(pending_update_source), is_in_gestiona = VALUES(is_in_gestiona), is_rejected = VALUES(is_rejected), key_date = VALUES(key_date), @@ -932,6 +937,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore command.Parameters.AddWithValue("@gestionaUploadedAtUtc", ToDbDate(denuncia.FechaSubidaAGestiona)); command.Parameters.AddWithValue("@gestionaLastUploadType", denuncia.UltimaSubidaGestionaTipo ?? string.Empty); command.Parameters.AddWithValue("@gestionaAssignedGroup", denuncia.UltimoGrupoAsignadoGestiona ?? string.Empty); + command.Parameters.AddWithValue("@pendingUpdateSource", denuncia.PendingUpdateSource ?? string.Empty); command.Parameters.AddWithValue("@isInGestiona", denuncia.EnGestiona); command.Parameters.AddWithValue("@isRejected", denuncia.EnRechazada); command.Parameters.AddWithValue("@keyDate", ToDbDate(denuncia.KeyDate)); @@ -996,9 +1002,6 @@ public sealed class MySqlDenunciaStore : IDenunciaStore description = @description, attachment_date_utc = @attachmentDateUtc, notes = @notes, - content = @content, - content_mime_type = @contentMimeType, - content_sha256 = @contentSha256, uploaded_to_gestiona = CASE WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_to_gestiona ELSE @uploadedToGestiona @@ -1007,6 +1010,9 @@ public sealed class MySqlDenunciaStore : IDenunciaStore WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_at_utc ELSE @uploadedAtUtc END, + content = @content, + content_mime_type = @contentMimeType, + content_sha256 = @contentSha256, key_date = @keyDate, encryption_scheme = @encryptionScheme, encrypted_at_utc = @encryptedAtUtc, @@ -1721,6 +1727,7 @@ public sealed class MySqlDenunciaStore : IDenunciaStore FechaSubidaAGestiona = GetDateTime(record, "gestiona_uploaded_at_utc"), UltimaSubidaGestionaTipo = GetString(record, "gestiona_last_upload_type"), UltimoGrupoAsignadoGestiona = GetString(record, "gestiona_assigned_group"), + PendingUpdateSource = GetString(record, "pending_update_source"), EnGestiona = GetBoolean(record, "is_in_gestiona"), EnRechazada = GetBoolean(record, "is_rejected"), KeyDate = GetNullableDateOnly(record, "key_date"), diff --git a/Antifraude.Net/ApiDenuncias/Services/WorkGroupAdministrationService.cs b/Antifraude.Net/ApiDenuncias/Services/WorkGroupAdministrationService.cs index d663e72..362f8b5 100644 --- a/Antifraude.Net/ApiDenuncias/Services/WorkGroupAdministrationService.cs +++ b/Antifraude.Net/ApiDenuncias/Services/WorkGroupAdministrationService.cs @@ -69,6 +69,42 @@ public sealed class WorkGroupAdministrationService .ToArray()); } + public async Task GetUserGroupsAsync( + string username, + CancellationToken cancellationToken = default) + { + await _denunciaStore.EnsureSchemaAsync(cancellationToken); + + var normalizedUsername = username?.Trim(); + if (string.IsNullOrWhiteSpace(normalizedUsername)) + { + throw new InvalidOperationException("No hay usuario autenticado."); + } + + await using var connection = await OpenConnectionAsync(cancellationToken); + const string sql = """ + SELECT wg.code + FROM app_users au + INNER JOIN app_user_groups aug ON aug.app_user_id = au.id + INNER JOIN work_groups wg + ON wg.id = aug.work_group_id + AND wg.is_active = 1 + WHERE LOWER(au.username) = LOWER(@username) + ORDER BY wg.code; + """; + + var groupCodes = new List(); + await using var command = new MySqlCommand(sql, connection); + command.Parameters.AddWithValue("@username", normalizedUsername); + await using var reader = await command.ExecuteReaderAsync(cancellationToken); + while (await reader.ReadAsync(cancellationToken)) + { + groupCodes.Add(reader.GetString(reader.GetOrdinal("code"))); + } + + return new CurrentUserWorkGroupsDto(normalizedUsername, groupCodes); + } + public async Task UpdateUserGroupsAsync( string username, IEnumerable groupCodes, diff --git a/Antifraude.Net/ApiDenuncias/appsettings.json b/Antifraude.Net/ApiDenuncias/appsettings.json index 1fe56c5..6f24d06 100644 --- a/Antifraude.Net/ApiDenuncias/appsettings.json +++ b/Antifraude.Net/ApiDenuncias/appsettings.json @@ -44,6 +44,8 @@ "CircuitNewComplaintTemplateName": "CT-Nueva denuncia", "CircuitUpdateSajTemplateName": "CT-Actualización denuncia SAJ", "CircuitUpdateSdiTemplateName": "CT-Actualización denuncia SDI", + "CircuitCommunicationSajTemplateName": "CT-Comunicación SAJ a denunciante", + "CircuitCommunicationSdiTemplateName": "CT-Comunicación SDI a denunciante", "CircuitSignerStampTitle": "oaaf-complaints-tramit", "CircuitVersion": "2", "DocumentMetadataLanguage": "es", diff --git a/Antifraude.Net/GestionaDenuncias.Shared/Models/ApiDenunciasDtos.cs b/Antifraude.Net/GestionaDenuncias.Shared/Models/ApiDenunciasDtos.cs index 2b24c17..507fbc4 100644 --- a/Antifraude.Net/GestionaDenuncias.Shared/Models/ApiDenunciasDtos.cs +++ b/Antifraude.Net/GestionaDenuncias.Shared/Models/ApiDenunciasDtos.cs @@ -95,7 +95,8 @@ public sealed record GestionaTramitarDocumentoRequest( string DocumentUrl, string AssignedGroupCode, int? ComplaintId, - bool IsUpdate = false); + bool IsUpdate = false, + string? UpdateSource = null); public sealed record ManualPurgeRequest(string Date); @@ -125,6 +126,10 @@ public sealed record WorkGroupAdministrationDto( IReadOnlyList Groups, IReadOnlyList Users); +public sealed record CurrentUserWorkGroupsDto( + string Username, + IReadOnlyList GroupCodes); + public sealed record UpdateUserWorkGroupsRequest( IReadOnlyList GroupCodes); diff --git a/Antifraude.Net/GestionaDenuncias.Shared/Models/ComplaintUpdateSources.cs b/Antifraude.Net/GestionaDenuncias.Shared/Models/ComplaintUpdateSources.cs new file mode 100644 index 0000000..424d7c5 --- /dev/null +++ b/Antifraude.Net/GestionaDenuncias.Shared/Models/ComplaintUpdateSources.cs @@ -0,0 +1,13 @@ +namespace GestionaDenuncias.Shared.Models; + +public static class ComplaintUpdateSources +{ + public const string Citizen = "citizen"; + public const string Receiver = "receiver"; + + public static bool IsCitizen(string? value) + => string.Equals(value, Citizen, StringComparison.OrdinalIgnoreCase); + + public static bool IsReceiver(string? value) + => string.Equals(value, Receiver, StringComparison.OrdinalIgnoreCase); +} diff --git a/Antifraude.Net/GestionaDenuncias.Shared/Models/DenunciasGestiona.cs b/Antifraude.Net/GestionaDenuncias.Shared/Models/DenunciasGestiona.cs index 55cc9a3..60f2af3 100644 --- a/Antifraude.Net/GestionaDenuncias.Shared/Models/DenunciasGestiona.cs +++ b/Antifraude.Net/GestionaDenuncias.Shared/Models/DenunciasGestiona.cs @@ -79,6 +79,7 @@ public class DenunciasGestiona public DateTime FechaSubidaAGestiona { get; set; } = DateTime.MinValue; public string UltimaSubidaGestionaTipo { get; set; } = string.Empty; public string UltimoGrupoAsignadoGestiona { get; set; } = string.Empty; + public string PendingUpdateSource { get; set; } = string.Empty; public bool EnGestiona { get; set; } public bool EnRechazada { get; set; } diff --git a/Antifraude.Net/GestionaDenuncias.Shared/Models/ReportDto.cs b/Antifraude.Net/GestionaDenuncias.Shared/Models/ReportDto.cs index 852a08e..1af4325 100644 --- a/Antifraude.Net/GestionaDenuncias.Shared/Models/ReportDto.cs +++ b/Antifraude.Net/GestionaDenuncias.Shared/Models/ReportDto.cs @@ -30,6 +30,7 @@ public sealed record ReportDto public bool DownloadedByAnotherUser { get; init; } public string? LastDownloadedByUsername { get; init; } public string? LastDownloadedAt { get; init; } + public string? LastGestionaUploadAt { get; init; } public bool AlreadyImported { get; init; } public bool AlreadyInGestiona { get; init; } public string? OwnerUsername { get; init; } diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor index eb0d20f..df36bd8 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor @@ -734,7 +734,7 @@ else nuevoAsunto = string.IsNullOrWhiteSpace(d.NombreDenuncia) ? $"Denuncia-{d.Id_Denuncia}-CD" : d.NombreDenuncia; nombreDocumentos = ""; selectedThirdParty = ThirdPartyIdentityData.FromComplaint(d); - selectedGroup = NormalizeUpdateGroup(selectedGroup); + selectedGroup = NormalizeUpdateGroup(d.UltimoGrupoAsignadoGestiona); operationError = string.Empty; operationNotice = string.Empty; @@ -844,12 +844,19 @@ else return; } + if (!await ConfirmSelectedGroupAsync()) + { + return; + } + try { isUploading = true; operationError = string.Empty; operationNotice = string.Empty; selectedGroup = NormalizeUpdateGroup(selectedGroup); + var pendingUpdateSource = selectedDenuncias.PendingUpdateSource; + var uploadType = GetUpdateUploadType(pendingUpdateSource, selectedGroup); using var busy = Busy.Show( "Enviando actualizacion", "Preparando expediente, carpeta de actualizacion y documentos."); @@ -1035,7 +1042,8 @@ else documentoParaTramitar, selectedGroup, selectedDenuncias.Id_Denuncia, - isUpdate: true); + isUpdate: true, + updateSource: pendingUpdateSource); } foreach (var orig in nombresOriginalesSubidos) @@ -1057,13 +1065,14 @@ else selectedDenuncias.EsActualizacion = false; selectedDenuncias.NombreDenuncia = nuevoAsunto; selectedDenuncias.FechaSubidaAGestiona = ahoraUtc; - selectedDenuncias.UltimaSubidaGestionaTipo = "Actualización"; + selectedDenuncias.UltimaSubidaGestionaTipo = uploadType; selectedDenuncias.UltimoGrupoAsignadoGestiona = GetGestionaGroupDisplay(selectedGroup); + selectedDenuncias.PendingUpdateSource = string.Empty; await SincronizarExpedienteGestionaAsync(selectedDenuncias, fileUrl); await ActualizarDenunciaAsync(selectedDenuncias); var historialAviso = await RegistrarHistorialGestionaAsync( selectedDenuncias, - "Actualización", + uploadType, selectedGroup, ahoraUtc, string.Join("; ", nombresFinalesSubidos)); @@ -1358,13 +1367,52 @@ else } private static string NormalizeUpdateGroup(string? groupCode) - => groupCode == "510" ? "510" : "600"; + => groupCode?.Trim().StartsWith("510", StringComparison.Ordinal) == true + ? "510" + : "600"; private static string GetGestionaGroupDisplay(string? groupCode) { return string.IsNullOrWhiteSpace(groupCode) ? string.Empty : groupCode.Trim(); } + private static string GetUpdateUploadType(string? updateSource, string groupCode) + { + if (!ComplaintUpdateSources.IsReceiver(updateSource)) + { + return "Actualización"; + } + + return NormalizeUpdateGroup(groupCode) == "510" + ? "Comunic. SDI a denunciante" + : "Comunic. SAJ a denunciante"; + } + + private async Task ConfirmSelectedGroupAsync() + { + try + { + var currentGroups = await ApiDenuncias.GetCurrentUserWorkGroupsAsync(); + if (currentGroups.GroupCodes.Contains(selectedGroup, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + + var configuredGroups = currentGroups.GroupCodes.Count == 0 + ? "ningún grupo" + : string.Join(", ", currentGroups.GroupCodes); + return await JSRuntime.InvokeAsync( + "confirm", + $"El grupo de destino {selectedGroup} no pertenece a tus grupos configurados ({configuredGroups}). " + + "Si continúas, la denuncia cambiará su asignación en Gestiona a un grupo distinto de los tuyos. ¿Deseas continuar?"); + } + catch (Exception ex) + { + operationError = $"No se han podido comprobar tus grupos antes de la subida: {ex.Message}"; + return false; + } + } + private async Task RegistrarHistorialGestionaAsync( DenunciasGestiona denuncia, string tipoSubida, diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor index 3099b64..8cd7cd0 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor @@ -314,12 +314,12 @@ # Canal Presentacion - Actividad ciudadano - Actividad OAAF - Estado - Acceso - Seguimiento - Detalle + Actividad ciudadano + Actividad OAAF + Estado + Acceso + Gestiona + Detalle @@ -361,17 +361,17 @@ @FormatCitizenActivity(report) @FormatReceiverActivity(report) - + @GetStatusLabel(report) - + @GetAccessLabel(report) - - @GetTrackingLabel(report) + + @GetTrackingLabel(report)