From ae2145ac715d80b7c7df4ec6c7ba4b416d92280a Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:55:54 -0300 Subject: [PATCH 1/8] Fix controle recebidos import parsing --- Controllers/LinesController.cs | 61 ++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index f75f317..486f8da 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1398,10 +1398,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 +1485,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 +1514,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}"); From e73f181b336994dad30cf99ab1a353e1e9d224d4 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:13:24 -0300 Subject: [PATCH 2/8] Preserve vigencia notifications on reimport --- Controllers/LinesController.cs | 9 ------- .../VigenciaNotificationBackgroundService.cs | 27 +++++++++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 486f8da..f4926fb 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1176,15 +1176,6 @@ namespace line_gestao_api.Controllers var startRow = headerRow.RowNumber() + 1; var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; - var tenantId = GetTenantIdFromClaims(); - var notificationsQuery = _db.Notifications - .IgnoreQueryFilters() - .Where(n => n.VigenciaLineId != null); - if (tenantId.HasValue) - { - notificationsQuery = notificationsQuery.Where(n => n.TenantId == tenantId.Value); - } - await notificationsQuery.ExecuteDeleteAsync(); await _db.VigenciaLines.ExecuteDeleteAsync(); var buffer = new List(600); 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(), From 27ebbbab8536b4d03c64417d8eade0867d9b914c Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:17:57 -0300 Subject: [PATCH 3/8] Fix tenant-scoped vigencia delete --- Controllers/LinesController.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index f4926fb..cdfa4ea 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1176,7 +1176,13 @@ namespace line_gestao_api.Controllers var startRow = headerRow.RowNumber() + 1; var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; - await _db.VigenciaLines.ExecuteDeleteAsync(); + var tenantId = GetTenantIdFromClaims(); + var vigenciaQuery = _db.VigenciaLines.IgnoreQueryFilters(); + if (tenantId.HasValue) + { + vigenciaQuery = vigenciaQuery.Where(v => v.TenantId == tenantId.Value); + } + await vigenciaQuery.ExecuteDeleteAsync(); var buffer = new List(600); From c9d8bdfda6ba4645c303127f5f0971444f27a5c1 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:22:20 -0300 Subject: [PATCH 4/8] Null vigencia notification links before delete --- Controllers/LinesController.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index cdfa4ea..028206f 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1177,6 +1177,16 @@ namespace line_gestao_api.Controllers var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; var tenantId = GetTenantIdFromClaims(); + var notificationsQuery = _db.Notifications + .IgnoreQueryFilters() + .Where(n => n.VigenciaLineId != null); + if (tenantId.HasValue) + { + notificationsQuery = notificationsQuery.Where(n => n.TenantId == tenantId.Value); + } + await notificationsQuery.ExecuteUpdateAsync(setters => + setters.SetProperty(n => n.VigenciaLineId, n => null)); + var vigenciaQuery = _db.VigenciaLines.IgnoreQueryFilters(); if (tenantId.HasValue) { From cd1066f20386ee31f686f35d104626b7c65792f2 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:32:05 -0300 Subject: [PATCH 5/8] Import multiple chip virgens tables --- Controllers/LinesController.cs | 88 ++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 028206f..464f1a9 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1346,48 +1346,56 @@ 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 map = BuildHeaderMap(headerRow); + int colItem = GetCol(map, "ITEM"); + if (colItem == 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 r = startRow; r <= endRow; r++) { - Id = Guid.NewGuid(), - Item = TryInt(itemStr), - NumeroDoChip = numeroChip, - Observacoes = string.IsNullOrWhiteSpace(observacoes) ? null : observacoes.Trim(), - CreatedAt = now, - UpdatedAt = now - }; + var itemStr = GetCellString(ws, r, colItem); + if (string.IsNullOrWhiteSpace(itemStr)) continue; - buffer.Add(e); + 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 (buffer.Count >= 500) - { - await _db.ChipVirgemLines.AddRangeAsync(buffer); - await _db.SaveChangesAsync(); - buffer.Clear(); + 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(); + } } } @@ -1590,6 +1598,24 @@ 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 == "NODOCHIP" || k == "NUMERODOCHIP" || k == "NDOCHIP" || k == "NUMDOCHIP") + { + hasNumeroChip = true; + } + } + + return hasItem && hasNumeroChip; + } + // ========================================================== // HELPERS (SEUS) // ========================================================== From f0ec95363d13527893ed0a720ff023088a87eaa1 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:49:45 -0300 Subject: [PATCH 6/8] Detect all chip virgens headers --- Controllers/LinesController.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 464f1a9..8328fcb 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1602,18 +1602,17 @@ namespace line_gestao_api.Controllers { var hasItem = false; var hasNumeroChip = false; + var hasObservacoes = false; foreach (var cell in row.CellsUsed()) { var k = NormalizeHeader(cell.GetString()); - if (k == "ITEM") hasItem = true; - if (k == "NODOCHIP" || k == "NUMERODOCHIP" || k == "NDOCHIP" || k == "NUMDOCHIP") - { - hasNumeroChip = true; - } + if (k.Contains("ITEM")) hasItem = true; + if (k.Contains("CHIP")) hasNumeroChip = true; + if (k.Contains("OBSERV")) hasObservacoes = true; } - return hasItem && hasNumeroChip; + return hasItem && hasNumeroChip && hasObservacoes; } // ========================================================== From b07b1d2cd7b4d5989e79530130ac97f580b64130 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:57:08 -0300 Subject: [PATCH 7/8] Import both chip virgens tables --- Controllers/LinesController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 8328fcb..7010399 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1373,8 +1373,12 @@ namespace line_gestao_api.Controllers 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")); + "Nº DO CHIP", "N° DO CHIP", "NUMERO DO CHIP", "N DO CHIP", "NUM. DO CHIP", "N° DO CHIP")); var observacoes = GetCellByHeaderAny(ws, r, map, "OBSERVAÇÕES", "OBSERVACOES", "OBS"); + if (string.IsNullOrWhiteSpace(numeroChip) && string.IsNullOrWhiteSpace(observacoes)) + { + continue; + } var now = DateTime.UtcNow; @@ -1602,17 +1606,15 @@ namespace line_gestao_api.Controllers { var hasItem = false; var hasNumeroChip = false; - var hasObservacoes = false; foreach (var cell in row.CellsUsed()) { var k = NormalizeHeader(cell.GetString()); - if (k.Contains("ITEM")) hasItem = true; + if (k == "ITEM") hasItem = true; if (k.Contains("CHIP")) hasNumeroChip = true; - if (k.Contains("OBSERV")) hasObservacoes = true; } - return hasItem && hasNumeroChip && hasObservacoes; + return hasItem && hasNumeroChip; } // ========================================================== From f14059dd2cb75f9f724be3d4446c6bbc7dc44935 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:05:44 -0300 Subject: [PATCH 8/8] Import chip virgens tables side by side --- Controllers/LinesController.cs | 86 +++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index 7010399..5c3c3df 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -1360,45 +1360,60 @@ namespace line_gestao_api.Controllers for (int i = 0; i < headers.Count; i++) { var headerRow = headers[i]; - var map = BuildHeaderMap(headerRow); - int colItem = GetCol(map, "ITEM"); - if (colItem == 0) continue; + 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 startRow = headerRow.RowNumber() + 1; var endRow = i + 1 < headers.Count ? headers[i + 1].RowNumber() - 1 : lastRow; - for (int r = startRow; r <= endRow; r++) + for (int tableIndex = 0; tableIndex < itemColumns.Count; tableIndex++) { - var itemStr = GetCellString(ws, r, colItem); - if (string.IsNullOrWhiteSpace(itemStr)) continue; + var startCol = itemColumns[tableIndex]; + var endCol = tableIndex + 1 < itemColumns.Count + ? itemColumns[tableIndex + 1] - 1 + : headerRow.LastCellUsed()?.Address.ColumnNumber ?? startCol; - var numeroChip = NullIfEmptyDigits(GetCellByHeaderAny(ws, r, map, - "Nº DO CHIP", "N° DO CHIP", "NUMERO DO CHIP", "N DO CHIP", "NUM. DO CHIP", "N° DO CHIP")); - var observacoes = GetCellByHeaderAny(ws, r, map, "OBSERVAÇÕES", "OBSERVACOES", "OBS"); - if (string.IsNullOrWhiteSpace(numeroChip) && string.IsNullOrWhiteSpace(observacoes)) + var map = BuildHeaderMapRange(headerRow, startCol, endCol); + int colItem = GetCol(map, "ITEM"); + if (colItem == 0) continue; + + for (int r = startRow; r <= endRow; r++) { - continue; - } + var itemStr = GetCellString(ws, r, colItem); + if (string.IsNullOrWhiteSpace(itemStr)) continue; - var now = DateTime.UtcNow; + 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 e = new ChipVirgemLine - { - Id = Guid.NewGuid(), - Item = TryInt(itemStr), - NumeroDoChip = numeroChip, - Observacoes = string.IsNullOrWhiteSpace(observacoes) ? null : observacoes.Trim(), - CreatedAt = now, - UpdatedAt = now - }; + var now = DateTime.UtcNow; - buffer.Add(e); + var e = new ChipVirgemLine + { + Id = Guid.NewGuid(), + Item = TryInt(itemStr), + NumeroDoChip = numeroChip, + Observacoes = string.IsNullOrWhiteSpace(observacoes) ? null : observacoes.Trim(), + CreatedAt = now, + UpdatedAt = now + }; - if (buffer.Count >= 500) - { - await _db.ChipVirgemLines.AddRangeAsync(buffer); - await _db.SaveChangesAsync(); - buffer.Clear(); + buffer.Add(e); + + if (buffer.Count >= 500) + { + await _db.ChipVirgemLines.AddRangeAsync(buffer); + await _db.SaveChangesAsync(); + buffer.Clear(); + } } } } @@ -1632,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;