From b5b36276cba5e1a8b4edc75d7b87c3d2ada1d547 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes Date: Thu, 12 Mar 2026 23:55:58 -0300 Subject: [PATCH] perf: otimiza importacao e exportacao de linhas --- Controllers/LinesController.cs | 190 ++++++++++++++++++++++++++++----- Dtos/MobileLineDtos.cs | 7 ++ 2 files changed, 169 insertions(+), 28 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 6b870d4..b342101 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -144,7 +144,13 @@ namespace line_gestao_api.Controllers if (!string.IsNullOrWhiteSpace(search)) { var s = search.Trim(); - reservaRows = reservaRows.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%")); + reservaRows = reservaRows.Where(x => + EF.Functions.ILike(x.Linha ?? "", $"%{s}%") || + EF.Functions.ILike(x.Chip ?? "", $"%{s}%") || + EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") || + EF.Functions.ILike(x.Usuario ?? "", $"%{s}%") || + EF.Functions.ILike(x.Conta ?? "", $"%{s}%") || + EF.Functions.ILike(x.Status ?? "", $"%{s}%")); } groupedQuery = reservaRows @@ -166,7 +172,13 @@ namespace line_gestao_api.Controllers if (!string.IsNullOrWhiteSpace(search)) { var s = search.Trim(); - query = query.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%")); + query = query.Where(x => + EF.Functions.ILike(x.Linha ?? "", $"%{s}%") || + EF.Functions.ILike(x.Chip ?? "", $"%{s}%") || + EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") || + EF.Functions.ILike(x.Usuario ?? "", $"%{s}%") || + EF.Functions.ILike(x.Conta ?? "", $"%{s}%") || + EF.Functions.ILike(x.Status ?? "", $"%{s}%")); } groupedQuery = query.GroupBy(x => x.Cliente) @@ -622,6 +634,122 @@ namespace line_gestao_api.Controllers return Ok(ToDetailDto(x, vigencia)); } + [HttpPost("export-details")] + public async Task>> GetExportDetails([FromBody] MobileLineExportDetailsRequestDto? req) + { + var orderedIds = (req?.Ids ?? new List()) + .Where(id => id != Guid.Empty) + .Distinct() + .ToList(); + + if (orderedIds.Count == 0) + { + return Ok(new List()); + } + + var lines = await _db.MobileLines + .AsNoTracking() + .Include(x => x.Setor) + .Include(x => x.Aparelho) + .Where(x => orderedIds.Contains(x.Id)) + .ToListAsync(); + + if (lines.Count == 0) + { + return Ok(new List()); + } + + var vigenciaByLinha = new Dictionary(StringComparer.Ordinal); + var vigenciaByItem = new Dictionary(); + + var lineKeys = lines + .Select(x => NullIfEmptyDigits(x.Linha)) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.Ordinal) + .ToList(); + + var itemKeys = lines + .Where(x => x.Item > 0) + .Select(x => x.Item) + .Distinct() + .ToList(); + + if (lineKeys.Count > 0 || itemKeys.Count > 0) + { + IQueryable vigenciaQuery = _db.VigenciaLines.AsNoTracking(); + + if (lineKeys.Count > 0 && itemKeys.Count > 0) + { + vigenciaQuery = vigenciaQuery.Where(v => lineKeys.Contains(v.Linha!) || itemKeys.Contains(v.Item)); + } + else if (lineKeys.Count > 0) + { + vigenciaQuery = vigenciaQuery.Where(v => lineKeys.Contains(v.Linha!)); + } + else + { + vigenciaQuery = vigenciaQuery.Where(v => itemKeys.Contains(v.Item)); + } + + var vigencias = await vigenciaQuery.ToListAsync(); + + foreach (var vigencia in vigencias + .Where(v => !string.IsNullOrWhiteSpace(v.Linha)) + .GroupBy(v => NullIfEmptyDigits(v.Linha)!, StringComparer.Ordinal) + .Select(g => g + .OrderByDescending(x => x.UpdatedAt) + .ThenByDescending(x => x.CreatedAt) + .First())) + { + var key = NullIfEmptyDigits(vigencia.Linha); + if (!string.IsNullOrWhiteSpace(key)) + { + vigenciaByLinha[key] = vigencia; + } + } + + foreach (var vigencia in vigencias + .Where(v => string.IsNullOrWhiteSpace(v.Linha)) + .GroupBy(v => v.Item) + .Select(g => g + .OrderByDescending(x => x.UpdatedAt) + .ThenByDescending(x => x.CreatedAt) + .First())) + { + if (vigencia.Item > 0) + { + vigenciaByItem[vigencia.Item] = vigencia; + } + } + } + + var linesById = lines.ToDictionary(x => x.Id); + var result = new List(orderedIds.Count); + + foreach (var id in orderedIds) + { + if (!linesById.TryGetValue(id, out var line)) + { + continue; + } + + VigenciaLine? vigencia = null; + var lineKey = NullIfEmptyDigits(line.Linha); + if (!string.IsNullOrWhiteSpace(lineKey)) + { + vigenciaByLinha.TryGetValue(lineKey, out vigencia); + } + else if (line.Item > 0) + { + vigenciaByItem.TryGetValue(line.Item, out vigencia); + } + + result.Add(ToDetailDto(line, vigencia)); + } + + return Ok(result); + } + // ========================================================== // ✅ 5. CREATE // ========================================================== @@ -1738,7 +1866,7 @@ namespace line_gestao_api.Controllers await _db.MuregLines.ExecuteDeleteAsync(); await _db.MobileLines.ExecuteDeleteAsync(); - var buffer = new List(600); + var buffer = new List(1000); var imported = 0; var maxItemFromGeral = 0; @@ -1805,11 +1933,12 @@ namespace line_gestao_api.Controllers imported++; if (item > maxItemFromGeral) maxItemFromGeral = item; - if (buffer.Count >= 500) + if (buffer.Count >= 1000) { await _db.MobileLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); buffer.Clear(); + ClearImportTracking(); } } @@ -1817,6 +1946,8 @@ namespace line_gestao_api.Controllers { await _db.MobileLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); + buffer.Clear(); + ClearImportTracking(); } auditSession = _spreadsheetImportAuditService.StartRun( @@ -1828,40 +1959,48 @@ namespace line_gestao_api.Controllers // ✅ IMPORTA MUREG (ALTERADO: NÃO ESTOURA ERRO SE LINHANOVA JÁ EXISTIR) // ========================= await ImportMuregFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA FATURAMENTO PF/PJ // ========================= await ImportBillingFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA DADOS DOS USUÁRIOS (UserDatas) // ========================= var userDataImported = await ImportUserDatasFromWorkbook(wb); + ClearImportTracking(); if (userDataImported) { await RepairReservaClientAssignmentsAsync(); + ClearImportTracking(); } // ========================= // ✅ IMPORTA VIGÊNCIA // ========================= await ImportVigenciaFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA TROCA DE NÚMERO // ========================= await ImportTrocaNumeroFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA CHIPS VIRGENS // ========================= await ImportChipsVirgensFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA CONTROLE DE RECEBIDOS // ========================= await ImportControleRecebidosFromWorkbook(wb); + ClearImportTracking(); // ========================= // ✅ IMPORTA RESUMO @@ -1872,11 +2011,13 @@ namespace line_gestao_api.Controllers } await ImportResumoFromWorkbook(wb, auditSession); + ClearImportTracking(); // ========================= // ✅ IMPORTA PARCELAMENTOS // ========================= var parcelamentosSummary = await _parcelamentosImportService.ImportFromWorkbookAsync(wb, replaceAll: true); + ClearImportTracking(); if (auditSession != null) { await _spreadsheetImportAuditService.SaveRunAsync(auditSession); @@ -2122,17 +2263,15 @@ namespace line_gestao_api.Controllers // limpa MUREG antes (idempotente) await _db.MuregLines.ExecuteDeleteAsync(); - // ✅ dicionários para resolver MobileLineId por Linha/Chip - var mobilePairs = await _db.MobileLines - .AsNoTracking() - .Select(x => new { x.Id, x.Item, x.Linha, x.Chip }) - .ToListAsync(); + // Carrega uma vez para evitar consultas por linha durante a importacao da MUREG. + var mobileCache = await _db.MobileLines + .ToDictionaryAsync(x => x.Id); var mobileByLinha = new Dictionary(StringComparer.Ordinal); var mobileByChip = new Dictionary(StringComparer.Ordinal); var mobileByItem = new Dictionary(); - foreach (var m in mobilePairs) + foreach (var m in mobileCache.Values) { if (m.Item > 0 && !mobileByItem.ContainsKey(m.Item)) mobileByItem[m.Item] = m.Id; @@ -2152,10 +2291,7 @@ namespace line_gestao_api.Controllers } } - // ✅ cache de entidades tracked para atualizar a GERAL sem consultar toda hora - var mobileCache = new Dictionary(); - - var buffer = new List(600); + var buffer = new List(1000); var lastRow = wsM.LastRowUsed()?.RowNumber() ?? startRow; for (int r = startRow; r <= lastRow; r++) @@ -2204,12 +2340,7 @@ namespace line_gestao_api.Controllers string? linhaAntigaSnapshot = linhaAntiga; if (string.IsNullOrWhiteSpace(linhaAntigaSnapshot)) { - if (!mobileCache.TryGetValue(mobileLineId, out var mobTmp)) - { - mobTmp = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == mobileLineId); - if (mobTmp != null) mobileCache[mobileLineId] = mobTmp; - } - + mobileCache.TryGetValue(mobileLineId, out var mobTmp); linhaAntigaSnapshot = mobTmp?.Linha; } @@ -2241,12 +2372,7 @@ namespace line_gestao_api.Controllers } else { - // carrega entity tracked (cache) e atualiza - if (!mobileCache.TryGetValue(mobileLineId, out var mobile)) - { - mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == mobileLineId); - if (mobile != null) mobileCache[mobileLineId] = mobile; - } + mobileCache.TryGetValue(mobileLineId, out var mobile); if (mobile != null) { @@ -2277,7 +2403,7 @@ namespace line_gestao_api.Controllers } } - if (buffer.Count >= 500) + if (buffer.Count >= 1000) { await _db.MuregLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); @@ -2289,6 +2415,7 @@ namespace line_gestao_api.Controllers { await _db.MuregLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); + buffer.Clear(); } } @@ -5244,9 +5371,16 @@ namespace line_gestao_api.Controllers DtEfetivacaoServico = vigencia?.DtEfetivacaoServico, DtTerminoFidelizacao = vigencia?.DtTerminoFidelizacao, VencConta = x.VencConta, - TipoDeChip = x.TipoDeChip + TipoDeChip = x.TipoDeChip, + CreatedAt = x.CreatedAt, + UpdatedAt = x.UpdatedAt }; + private void ClearImportTracking() + { + _db.ChangeTracker.Clear(); + } + private static void ApplyReservaRule(MobileLine x) { if (IsReservaValue(x.Cliente)) x.Cliente = "RESERVA"; diff --git a/Dtos/MobileLineDtos.cs b/Dtos/MobileLineDtos.cs index ddd4227..ecb5773 100644 --- a/Dtos/MobileLineDtos.cs +++ b/Dtos/MobileLineDtos.cs @@ -84,6 +84,8 @@ public DateTime? DtTerminoFidelizacao { get; set; } public string? VencConta { get; set; } public string? TipoDeChip { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } } // ✅ UPDATE REQUEST (SEM Id) @@ -153,4 +155,9 @@ public string? Skil { get; set; } } + public class MobileLineExportDetailsRequestDto + { + public List Ids { get; set; } = new(); + } + }