diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index f75f317..5c3c3df 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1184,8 +1184,15 @@ namespace line_gestao_api.Controllers { notificationsQuery = notificationsQuery.Where(n => n.TenantId == tenantId.Value); } - await notificationsQuery.ExecuteDeleteAsync(); - await _db.VigenciaLines.ExecuteDeleteAsync(); + await notificationsQuery.ExecuteUpdateAsync(setters => + setters.SetProperty(n => n.VigenciaLineId, n => null)); + + var vigenciaQuery = _db.VigenciaLines.IgnoreQueryFilters(); + if (tenantId.HasValue) + { + vigenciaQuery = vigenciaQuery.Where(v => v.TenantId == tenantId.Value); + } + await vigenciaQuery.ExecuteDeleteAsync(); var buffer = new List(600); @@ -1339,48 +1346,75 @@ namespace line_gestao_api.Controllers if (ws == null) return; - var headerRow = ws.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM")); - if (headerRow == null) return; - - var map = BuildHeaderMap(headerRow); - int colItem = GetCol(map, "ITEM"); - if (colItem == 0) return; - - var startRow = headerRow.RowNumber() + 1; - var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; + var headers = ws.RowsUsed() + .Where(IsChipsVirgensHeader) + .OrderBy(r => r.RowNumber()) + .ToList(); + if (headers.Count == 0) return; await _db.ChipVirgemLines.ExecuteDeleteAsync(); var buffer = new List(500); + var lastRow = ws.LastRowUsed()?.RowNumber() ?? 1; - for (int r = startRow; r <= lastRow; r++) + for (int i = 0; i < headers.Count; i++) { - var itemStr = GetCellString(ws, r, colItem); - if (string.IsNullOrWhiteSpace(itemStr)) break; + var headerRow = headers[i]; + var itemColumns = headerRow.CellsUsed() + .Where(c => NormalizeHeader(c.GetString()) == "ITEM") + .Select(c => c.Address.ColumnNumber) + .OrderBy(c => c) + .ToList(); + if (itemColumns.Count == 0) continue; - var numeroChip = NullIfEmptyDigits(GetCellByHeaderAny(ws, r, map, - "Nº DO CHIP", "N° DO CHIP", "NUMERO DO CHIP", "N DO CHIP", "NUM. DO CHIP")); - var observacoes = GetCellByHeaderAny(ws, r, map, "OBSERVAÇÕES", "OBSERVACOES", "OBS"); + var startRow = headerRow.RowNumber() + 1; + var endRow = i + 1 < headers.Count ? headers[i + 1].RowNumber() - 1 : lastRow; - var now = DateTime.UtcNow; - - var e = new ChipVirgemLine + for (int tableIndex = 0; tableIndex < itemColumns.Count; tableIndex++) { - Id = Guid.NewGuid(), - Item = TryInt(itemStr), - NumeroDoChip = numeroChip, - Observacoes = string.IsNullOrWhiteSpace(observacoes) ? null : observacoes.Trim(), - CreatedAt = now, - UpdatedAt = now - }; + var startCol = itemColumns[tableIndex]; + var endCol = tableIndex + 1 < itemColumns.Count + ? itemColumns[tableIndex + 1] - 1 + : headerRow.LastCellUsed()?.Address.ColumnNumber ?? startCol; - buffer.Add(e); + var map = BuildHeaderMapRange(headerRow, startCol, endCol); + int colItem = GetCol(map, "ITEM"); + if (colItem == 0) continue; - if (buffer.Count >= 500) - { - await _db.ChipVirgemLines.AddRangeAsync(buffer); - await _db.SaveChangesAsync(); - buffer.Clear(); + for (int r = startRow; r <= endRow; r++) + { + var itemStr = GetCellString(ws, r, colItem); + if (string.IsNullOrWhiteSpace(itemStr)) continue; + + var numeroChip = NullIfEmptyDigits(GetCellByHeaderAny(ws, r, map, + "Nº DO CHIP", "N° DO CHIP", "NUMERO DO CHIP", "N DO CHIP", "NUM. DO CHIP")); + var observacoes = GetCellByHeaderAny(ws, r, map, "OBSERVAÇÕES", "OBSERVACOES", "OBS"); + if (string.IsNullOrWhiteSpace(numeroChip) && string.IsNullOrWhiteSpace(observacoes)) + { + continue; + } + + var now = DateTime.UtcNow; + + var e = new ChipVirgemLine + { + Id = Guid.NewGuid(), + Item = TryInt(itemStr), + NumeroDoChip = numeroChip, + Observacoes = string.IsNullOrWhiteSpace(observacoes) ? null : observacoes.Trim(), + CreatedAt = now, + UpdatedAt = now + }; + + buffer.Add(e); + + if (buffer.Count >= 500) + { + await _db.ChipVirgemLines.AddRangeAsync(buffer); + await _db.SaveChangesAsync(); + buffer.Clear(); + } + } } } @@ -1398,10 +1432,19 @@ namespace line_gestao_api.Controllers { await _db.ControleRecebidoLines.ExecuteDeleteAsync(); + var years = new[] { 2022, 2023, 2024, 2025 }; + var importedYears = new HashSet(); + foreach (var info in GetControleRecebidosWorksheets(wb)) + { await ImportControleRecebidosSheet(info.Sheet, info.Year); + importedYears.Add(info.Year); + } + foreach (var year in years) { + if (importedYears.Contains(year)) continue; + var ws = FindControleRecebidosWorksheet(wb, year); if (ws == null) continue; @@ -1476,29 +1519,14 @@ namespace line_gestao_api.Controllers ConteudoDaNf = string.IsNullOrWhiteSpace(conteudo) ? null : conteudo.Trim(), NumeroDaLinha = numeroLinha, ValorUnit = valorUnit, - private sealed class ControleRecebidosWorksheetInfo - { - public ControleRecebidosWorksheetInfo(IXLWorksheet sheet, int year) - { - Sheet = sheet; - Year = year; - } - - public IXLWorksheet Sheet { get; } - public int Year { get; } - } - - private static IEnumerable GetControleRecebidosWorksheets(XLWorkbook wb) - yield return new ControleRecebidosWorksheetInfo(ws, year); + ValorDaNf = valorDaNf, + DataDaNf = dataDaNf, DataDoRecebimento = dataReceb, Quantidade = qtd, IsResumo = isResumo, CreatedAt = now, UpdatedAt = now - var isControleRecebidos = name.Contains("CONTROLE") && name.Contains("RECEBIDOS"); - var isRomaneio = name.Contains("ROMANEIO"); - - if (!isControleRecebidos && !isRomaneio) + }; buffer.Add(e); @@ -1520,6 +1548,37 @@ namespace line_gestao_api.Controllers } } + private sealed class ControleRecebidosWorksheetInfo + { + public ControleRecebidosWorksheetInfo(IXLWorksheet sheet, int year) + { + Sheet = sheet; + Year = year; + } + + public IXLWorksheet Sheet { get; } + public int Year { get; } + } + + private static IEnumerable GetControleRecebidosWorksheets(XLWorkbook wb) + { + var years = new[] { 2022, 2023, 2024, 2025 }; + + foreach (var ws in wb.Worksheets) + { + var name = NormalizeHeader(ws.Name); + var isControleRecebidos = name.Contains("CONTROLE") && name.Contains("RECEBIDOS"); + var isRomaneio = name.Contains("ROMANEIO"); + + if (!isControleRecebidos && !isRomaneio) continue; + + var year = years.FirstOrDefault(y => name.Contains(y.ToString())); + if (year == 0) continue; + + yield return new ControleRecebidosWorksheetInfo(ws, year); + } + } + private static IXLWorksheet? FindControleRecebidosWorksheet(XLWorkbook wb, int year) { var normalizedName = NormalizeHeader($"CONTROLE DE RECEBIDOS {year}"); @@ -1558,6 +1617,21 @@ namespace line_gestao_api.Controllers return false; } + private static bool IsChipsVirgensHeader(IXLRow row) + { + var hasItem = false; + var hasNumeroChip = false; + + foreach (var cell in row.CellsUsed()) + { + var k = NormalizeHeader(cell.GetString()); + if (k == "ITEM") hasItem = true; + if (k.Contains("CHIP")) hasNumeroChip = true; + } + + return hasItem && hasNumeroChip; + } + // ========================================================== // HELPERS (SEUS) // ========================================================== @@ -1573,6 +1647,21 @@ namespace line_gestao_api.Controllers return map; } + private static Dictionary BuildHeaderMapRange(IXLRow headerRow, int startCol, int endCol) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var cell in headerRow.CellsUsed()) + { + var col = cell.Address.ColumnNumber; + if (col < startCol || col > endCol) continue; + + var k = NormalizeHeader(cell.GetString()); + if (!string.IsNullOrWhiteSpace(k) && !map.ContainsKey(k)) + map[k] = col; + } + return map; + } + private static DateTime? ToUtc(DateTime? dt) { if (dt == null) return null; diff --git a/Services/VigenciaNotificationBackgroundService.cs b/Services/VigenciaNotificationBackgroundService.cs index 9b22f1d..d97ff91 100644 --- a/Services/VigenciaNotificationBackgroundService.cs +++ b/Services/VigenciaNotificationBackgroundService.cs @@ -190,13 +190,26 @@ public class VigenciaNotificationBackgroundService : BackgroundService return; } - var dedupKeys = candidates.Select(c => c.DedupKey).Distinct().ToList(); - var existingKeys = await db.Notifications.AsNoTracking() - .Where(n => dedupKeys.Contains(n.DedupKey)) - .Select(n => n.DedupKey) + var candidateTipos = candidates.Select(c => c.Tipo).Distinct().ToList(); + var candidateDates = candidates + .Where(c => c.ReferenciaData.HasValue) + .Select(c => c.ReferenciaData!.Value.Date) + .Distinct() + .ToList(); + var existingNotifications = await db.Notifications.AsNoTracking() + .Where(n => n.TenantId == tenantId) + .Where(n => candidateTipos.Contains(n.Tipo)) + .Where(n => n.ReferenciaData != null && candidateDates.Contains(n.ReferenciaData.Value.Date)) .ToListAsync(stoppingToken); - var existingSet = new HashSet(existingKeys); + var existingSet = new HashSet(existingNotifications.Select(n => + BuildDedupKey( + n.Tipo, + n.ReferenciaData!.Value, + n.DiasParaVencer ?? 0, + n.Usuario, + n.Cliente, + n.Linha))); var toInsert = candidates .Where(c => !existingSet.Contains(c.DedupKey)) .ToList(); @@ -232,7 +245,7 @@ public class VigenciaNotificationBackgroundService : BackgroundService ReferenciaData = referenciaData, DiasParaVencer = diasParaVencer, Lida = false, - DedupKey = BuildDedupKey(tipo, vigenciaLineId, referenciaData, diasParaVencer, usuario, cliente, linha), + DedupKey = BuildDedupKey(tipo, referenciaData, diasParaVencer, usuario, cliente, linha), UserId = userId, Usuario = usuario, Cliente = cliente, @@ -244,7 +257,6 @@ public class VigenciaNotificationBackgroundService : BackgroundService private static string BuildDedupKey( string tipo, - Guid vigenciaLineId, DateTime referenciaData, int diasParaVencer, string? usuario, @@ -254,7 +266,6 @@ public class VigenciaNotificationBackgroundService : BackgroundService var parts = new[] { tipo.Trim().ToLowerInvariant(), - vigenciaLineId.ToString(), referenciaData.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), diasParaVencer.ToString(CultureInfo.InvariantCulture), (usuario ?? string.Empty).Trim().ToLowerInvariant(),