Refactor: Ajuste na logica de busca por cliente, paginacao e layout do header
This commit is contained in:
parent
c830812af3
commit
3dc1eac097
|
|
@ -1,6 +1,6 @@
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using line_gestao_api.Dtos; // Certifique-se que ClientGroupDto está neste namespace
|
using line_gestao_api.Dtos;
|
||||||
using line_gestao_api.Models;
|
using line_gestao_api.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
@ -11,7 +11,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
//[Authorize] // Descomente se estiver usando autenticação
|
//[Authorize]
|
||||||
public class LinesController : ControllerBase
|
public class LinesController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
@ -21,40 +21,85 @@ namespace line_gestao_api.Controllers
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classe auxiliar apenas para o upload (não é DTO de banco)
|
|
||||||
public class ImportExcelForm
|
public class ImportExcelForm
|
||||||
{
|
{
|
||||||
public IFormFile File { get; set; } = default!;
|
public IFormFile File { get; set; } = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 1. NOVO ENDPOINT: AGRUPAR POR CLIENTE (Resumo para Aba 'Todos')
|
// ✅ 1. ENDPOINT: AGRUPAR POR CLIENTE (COM BUSCA E PAGINAÇÃO)
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
|
// Alterado para aceitar 'search', 'page' e retornar PagedResult
|
||||||
[HttpGet("groups")]
|
[HttpGet("groups")]
|
||||||
public async Task<ActionResult<List<ClientGroupDto>>> GetClientGroups()
|
public async Task<ActionResult<PagedResult<ClientGroupDto>>> GetClientGroups(
|
||||||
|
[FromQuery] string? skil,
|
||||||
|
[FromQuery] string? search, // 🔍 Busca por Nome do Cliente
|
||||||
|
[FromQuery] int page = 1, // 📄 Paginação
|
||||||
|
[FromQuery] int pageSize = 10)
|
||||||
{
|
{
|
||||||
// Agrupa por nome do cliente e calcula os totais
|
page = page < 1 ? 1 : page;
|
||||||
var groups = await _db.MobileLines
|
pageSize = pageSize < 1 ? 10 : pageSize;
|
||||||
.AsNoTracking()
|
|
||||||
.Where(x => !string.IsNullOrEmpty(x.Cliente))
|
var query = _db.MobileLines.AsNoTracking().Where(x => !string.IsNullOrEmpty(x.Cliente));
|
||||||
|
|
||||||
|
// 1. Filtro SKIL (PF, PJ ou RESERVA)
|
||||||
|
if (!string.IsNullOrWhiteSpace(skil))
|
||||||
|
{
|
||||||
|
var sSkil = skil.Trim();
|
||||||
|
|
||||||
|
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Filtro SEARCH (Busca pelo Nome do Cliente nos grupos)
|
||||||
|
// Aqui garantimos que se o usuário digitar "ADR", filtramos apenas os clientes que tem ADR no nome
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var s = search.Trim();
|
||||||
|
query = query.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Montagem do Agrupamento
|
||||||
|
var groupedQuery = query
|
||||||
.GroupBy(x => x.Cliente)
|
.GroupBy(x => x.Cliente)
|
||||||
.Select(g => new ClientGroupDto
|
.Select(g => new ClientGroupDto
|
||||||
{
|
{
|
||||||
Cliente = g.Key!,
|
Cliente = g.Key!,
|
||||||
TotalLinhas = g.Count(),
|
TotalLinhas = g.Count(),
|
||||||
// Conta quantos contêm "ativo" (ignorando maiúsculas/minúsculas)
|
|
||||||
Ativos = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%ativo%")),
|
Ativos = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%ativo%")),
|
||||||
// Conta quantos contêm "bloque" ou similar
|
Bloqueados = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%bloque%") ||
|
||||||
Bloqueados = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%bloque%") || EF.Functions.ILike(x.Status ?? "", "%perda%") || EF.Functions.ILike(x.Status ?? "", "%roubo%"))
|
EF.Functions.ILike(x.Status ?? "", "%perda%") ||
|
||||||
})
|
EF.Functions.ILike(x.Status ?? "", "%roubo%"))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contagem total para a paginação
|
||||||
|
var totalGroups = await groupedQuery.CountAsync();
|
||||||
|
|
||||||
|
// Aplicação da Paginação
|
||||||
|
var items = await groupedQuery
|
||||||
.OrderBy(x => x.Cliente)
|
.OrderBy(x => x.Cliente)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return Ok(groups);
|
// Retorna formato paginado
|
||||||
|
return Ok(new PagedResult<ClientGroupDto>
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
Total = totalGroups,
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 2. ENDPOINT: LISTAR NOMES DE CLIENTES (Para o Dropdown)
|
// ✅ 2. ENDPOINT: LISTAR NOMES DE CLIENTES
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpGet("clients")]
|
[HttpGet("clients")]
|
||||||
public async Task<ActionResult<List<string>>> GetClients()
|
public async Task<ActionResult<List<string>>> GetClients()
|
||||||
|
|
@ -71,13 +116,13 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 3. GET ALL (TABELA PRINCIPAL - Com todos os filtros)
|
// ✅ 3. GET ALL (TABELA / DETALHES DO GRUPO / BUSCA ESPECÍFICA)
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<PagedResult<MobileLineListDto>>> GetAll(
|
public async Task<ActionResult<PagedResult<MobileLineListDto>>> GetAll(
|
||||||
[FromQuery] string? search,
|
[FromQuery] string? search,
|
||||||
[FromQuery] string? skil, // Filtro PF/PJ
|
[FromQuery] string? skil,
|
||||||
[FromQuery] string? client, // Filtro Cliente Específico
|
[FromQuery] string? client,
|
||||||
[FromQuery] int page = 1,
|
[FromQuery] int page = 1,
|
||||||
[FromQuery] int pageSize = 20,
|
[FromQuery] int pageSize = 20,
|
||||||
[FromQuery] string? sortBy = "item",
|
[FromQuery] string? sortBy = "item",
|
||||||
|
|
@ -88,28 +133,31 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var q = _db.MobileLines.AsNoTracking();
|
var q = _db.MobileLines.AsNoTracking();
|
||||||
|
|
||||||
// 1. Filtro por SKIL (PF/PJ)
|
// Filtro SKIL
|
||||||
if (!string.IsNullOrWhiteSpace(skil))
|
if (!string.IsNullOrWhiteSpace(skil))
|
||||||
{
|
{
|
||||||
var sSkil = skil.Trim();
|
var sSkil = skil.Trim();
|
||||||
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
||||||
|
q = q.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%"));
|
||||||
|
else
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Filtro por Cliente Específico (usado no dropdown e no accordion)
|
// Filtro Cliente Específico (usado ao expandir o grupo)
|
||||||
if (!string.IsNullOrWhiteSpace(client))
|
if (!string.IsNullOrWhiteSpace(client))
|
||||||
{
|
{
|
||||||
var sClient = client.Trim();
|
var sClient = client.Trim();
|
||||||
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", sClient));
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", sClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Busca Genérica (Barra de pesquisa)
|
// Busca Genérica (usada quando o frontend detecta números ou busca específica)
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
{
|
{
|
||||||
var s = search.Trim();
|
var s = search.Trim();
|
||||||
q = q.Where(x =>
|
q = q.Where(x =>
|
||||||
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
|
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
|
||||||
EF.Functions.ILike(x.Chip ?? "", $"%{s}%") ||
|
EF.Functions.ILike(x.Chip ?? "", $"%{s}%") ||
|
||||||
EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") ||
|
EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") || // Busca cliente aqui também caso seja modo tabela
|
||||||
EF.Functions.ILike(x.Usuario ?? "", $"%{s}%") ||
|
EF.Functions.ILike(x.Usuario ?? "", $"%{s}%") ||
|
||||||
EF.Functions.ILike(x.Conta ?? "", $"%{s}%") ||
|
EF.Functions.ILike(x.Conta ?? "", $"%{s}%") ||
|
||||||
EF.Functions.ILike(x.Status ?? "", $"%{s}%"));
|
EF.Functions.ILike(x.Status ?? "", $"%{s}%"));
|
||||||
|
|
@ -117,7 +165,6 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var total = await q.CountAsync();
|
var total = await q.CountAsync();
|
||||||
|
|
||||||
// Ordenação
|
|
||||||
var sb = (sortBy ?? "item").Trim().ToLowerInvariant();
|
var sb = (sortBy ?? "item").Trim().ToLowerInvariant();
|
||||||
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|
@ -169,168 +216,54 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// OBTER DETALHES POR ID
|
// DEMAIS MÉTODOS (CRUD, IMPORT, HELPERS) - MANTIDOS
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")] public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id) { var x = await _db.MobileLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); return Ok(ToDetailDto(x)); }
|
||||||
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
|
||||||
{
|
|
||||||
var x = await _db.MobileLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
|
|
||||||
if (x == null) return NotFound();
|
|
||||||
|
|
||||||
return Ok(ToDetailDto(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================================
|
|
||||||
// ATUALIZAR (PUT)
|
|
||||||
// ==========================================================
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
||||||
{
|
{
|
||||||
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
if (x == null) return NotFound();
|
if (x == null) return NotFound();
|
||||||
|
|
||||||
var newLinha = OnlyDigits(req.Linha);
|
var newLinha = OnlyDigits(req.Linha);
|
||||||
var newChip = OnlyDigits(req.Chip);
|
if (!string.IsNullOrWhiteSpace(newLinha) && !string.Equals((x.Linha ?? ""), newLinha, StringComparison.Ordinal)) { var exists = await _db.MobileLines.AsNoTracking().AnyAsync(m => m.Linha == newLinha && m.Id != id); if (exists) return Conflict(new { message = "Já existe registro com essa LINHA.", linha = newLinha }); }
|
||||||
|
|
||||||
// Verifica duplicidade de linha (se alterou a linha)
|
x.Item = req.Item; x.Conta = req.Conta?.Trim(); x.Linha = newLinha; x.Chip = OnlyDigits(req.Chip); x.Cliente = req.Cliente?.Trim(); x.Usuario = req.Usuario?.Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(newLinha) &&
|
x.PlanoContrato = req.PlanoContrato?.Trim(); x.FranquiaVivo = req.FranquiaVivo; x.ValorPlanoVivo = req.ValorPlanoVivo; x.GestaoVozDados = req.GestaoVozDados;
|
||||||
!string.Equals((x.Linha ?? ""), newLinha, StringComparison.Ordinal))
|
x.Skeelo = req.Skeelo; x.VivoNewsPlus = req.VivoNewsPlus; x.VivoTravelMundo = req.VivoTravelMundo; x.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
|
||||||
{
|
x.ValorContratoVivo = req.ValorContratoVivo; x.FranquiaLine = req.FranquiaLine; x.FranquiaGestao = req.FranquiaGestao; x.LocacaoAp = req.LocacaoAp;
|
||||||
var exists = await _db.MobileLines.AsNoTracking()
|
x.ValorContratoLine = req.ValorContratoLine; x.Desconto = req.Desconto; x.Lucro = req.Lucro; x.Status = req.Status?.Trim();
|
||||||
.AnyAsync(m => m.Linha == newLinha && m.Id != id);
|
x.DataBloqueio = ToUtc(req.DataBloqueio); x.Skil = req.Skil?.Trim(); x.Modalidade = req.Modalidade?.Trim(); x.Cedente = req.Cedente?.Trim();
|
||||||
|
x.Solicitante = req.Solicitante?.Trim(); x.DataEntregaOpera = ToUtc(req.DataEntregaOpera); x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
||||||
if (exists)
|
x.VencConta = req.VencConta?.Trim(); ApplyReservaRule(x); x.UpdatedAt = DateTime.UtcNow;
|
||||||
{
|
|
||||||
return Conflict(new
|
|
||||||
{
|
|
||||||
message = "Já existe um registro com essa LINHA. Não é possível salvar duplicado.",
|
|
||||||
linha = newLinha
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atualiza campos
|
|
||||||
x.Item = req.Item;
|
|
||||||
x.Conta = req.Conta?.Trim();
|
|
||||||
x.Linha = string.IsNullOrWhiteSpace(newLinha) ? null : newLinha;
|
|
||||||
x.Chip = string.IsNullOrWhiteSpace(newChip) ? null : newChip;
|
|
||||||
x.Cliente = req.Cliente?.Trim();
|
|
||||||
x.Usuario = req.Usuario?.Trim();
|
|
||||||
x.PlanoContrato = req.PlanoContrato?.Trim();
|
|
||||||
|
|
||||||
x.FranquiaVivo = req.FranquiaVivo;
|
|
||||||
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
|
||||||
x.GestaoVozDados = req.GestaoVozDados;
|
|
||||||
x.Skeelo = req.Skeelo;
|
|
||||||
x.VivoNewsPlus = req.VivoNewsPlus;
|
|
||||||
x.VivoTravelMundo = req.VivoTravelMundo;
|
|
||||||
x.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
|
|
||||||
x.ValorContratoVivo = req.ValorContratoVivo;
|
|
||||||
|
|
||||||
x.FranquiaLine = req.FranquiaLine;
|
|
||||||
x.FranquiaGestao = req.FranquiaGestao;
|
|
||||||
x.LocacaoAp = req.LocacaoAp;
|
|
||||||
x.ValorContratoLine = req.ValorContratoLine;
|
|
||||||
|
|
||||||
x.Desconto = req.Desconto;
|
|
||||||
x.Lucro = req.Lucro;
|
|
||||||
|
|
||||||
x.Status = req.Status?.Trim();
|
|
||||||
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
|
||||||
|
|
||||||
x.Skil = req.Skil?.Trim();
|
|
||||||
x.Modalidade = req.Modalidade?.Trim();
|
|
||||||
x.Cedente = req.Cedente?.Trim();
|
|
||||||
x.Solicitante = req.Solicitante?.Trim();
|
|
||||||
|
|
||||||
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
|
||||||
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
|
||||||
x.VencConta = req.VencConta?.Trim();
|
|
||||||
|
|
||||||
ApplyReservaRule(x);
|
|
||||||
|
|
||||||
x.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
catch (DbUpdateException)
|
|
||||||
{
|
|
||||||
return Conflict(new
|
|
||||||
{
|
|
||||||
message = "Conflito ao salvar. Verifique se a LINHA já existe em outro registro."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try { await _db.SaveChangesAsync(); } catch (DbUpdateException) { return Conflict(new { message = "Conflito ao salvar." }); }
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
[HttpDelete("{id:guid}")] public async Task<IActionResult> Delete(Guid id) { var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); _db.MobileLines.Remove(x); await _db.SaveChangesAsync(); return NoContent(); }
|
||||||
// DELETAR (DELETE)
|
|
||||||
// ==========================================================
|
|
||||||
[HttpDelete("{id:guid}")]
|
|
||||||
public async Task<IActionResult> Delete(Guid id)
|
|
||||||
{
|
|
||||||
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
|
||||||
if (x == null) return NotFound();
|
|
||||||
|
|
||||||
_db.MobileLines.Remove(x);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================================
|
|
||||||
// IMPORTAR EXCEL
|
|
||||||
// ==========================================================
|
|
||||||
[HttpPost("import-excel")]
|
[HttpPost("import-excel")]
|
||||||
[Consumes("multipart/form-data")]
|
[Consumes("multipart/form-data")]
|
||||||
[RequestSizeLimit(50_000_000)]
|
[RequestSizeLimit(50_000_000)]
|
||||||
public async Task<ActionResult<ImportResultDto>> ImportExcel([FromForm] ImportExcelForm form)
|
public async Task<ActionResult<ImportResultDto>> ImportExcel([FromForm] ImportExcelForm form)
|
||||||
{
|
{
|
||||||
var file = form.File;
|
var file = form.File; if (file == null || file.Length == 0) return BadRequest("Arquivo inválido.");
|
||||||
|
using var stream = file.OpenReadStream(); using var wb = new XLWorkbook(stream);
|
||||||
if (file == null || file.Length == 0)
|
|
||||||
return BadRequest("Arquivo inválido.");
|
|
||||||
|
|
||||||
using var stream = file.OpenReadStream();
|
|
||||||
using var wb = new XLWorkbook(stream);
|
|
||||||
|
|
||||||
var ws = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("GERAL", StringComparison.OrdinalIgnoreCase));
|
var ws = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("GERAL", StringComparison.OrdinalIgnoreCase));
|
||||||
if (ws == null)
|
if (ws == null) return BadRequest("Aba 'GERAL' não encontrada.");
|
||||||
return BadRequest("Aba 'GERAL' não encontrada.");
|
var headerRow = ws.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
||||||
|
if (headerRow == null) return BadRequest("Cabeçalho 'ITEM' não encontrado.");
|
||||||
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
|
||||||
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
|
||||||
|
|
||||||
if (headerRow == null)
|
|
||||||
return BadRequest("Cabeçalho da planilha (linha com 'ITÉM') não encontrado.");
|
|
||||||
|
|
||||||
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var cell in headerRow.CellsUsed())
|
foreach (var cell in headerRow.CellsUsed()) { var k = NormalizeHeader(cell.GetString()); if (!string.IsNullOrWhiteSpace(k) && !map.ContainsKey(k)) map[k] = cell.Address.ColumnNumber; }
|
||||||
{
|
int colItem = GetCol(map, "ITEM"); if (colItem == 0) return BadRequest("Coluna 'ITEM' não encontrada.");
|
||||||
var key = NormalizeHeader(cell.GetString());
|
|
||||||
if (!string.IsNullOrWhiteSpace(key) && !map.ContainsKey(key))
|
|
||||||
map[key] = cell.Address.ColumnNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
int colItem = GetCol(map, "ITEM");
|
|
||||||
if (colItem == 0) return BadRequest("Coluna 'ITÉM' não encontrada.");
|
|
||||||
|
|
||||||
var startRow = headerRow.RowNumber() + 1;
|
var startRow = headerRow.RowNumber() + 1;
|
||||||
|
|
||||||
// Opção: Deletar tudo antes de importar (Cuidado em produção!)
|
|
||||||
await _db.MobileLines.ExecuteDeleteAsync();
|
await _db.MobileLines.ExecuteDeleteAsync();
|
||||||
|
var buffer = new List<MobileLine>(600); var imported = 0;
|
||||||
var imported = 0;
|
|
||||||
var buffer = new List<MobileLine>(600);
|
|
||||||
|
|
||||||
for (int r = startRow; r <= ws.LastRowUsed().RowNumber(); r++)
|
for (int r = startRow; r <= ws.LastRowUsed().RowNumber(); r++)
|
||||||
{
|
{
|
||||||
var itemStr = GetCellString(ws, r, colItem);
|
var itemStr = GetCellString(ws, r, colItem); if (string.IsNullOrWhiteSpace(itemStr)) break;
|
||||||
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
var e = new MobileLine
|
||||||
|
|
||||||
var entity = new MobileLine
|
|
||||||
{
|
{
|
||||||
Item = TryInt(itemStr),
|
Item = TryInt(itemStr),
|
||||||
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
||||||
|
|
@ -361,169 +294,26 @@ namespace line_gestao_api.Controllers
|
||||||
Solicitante = GetCellByHeader(ws, r, map, "SOLICITANTE"),
|
Solicitante = GetCellByHeader(ws, r, map, "SOLICITANTE"),
|
||||||
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
||||||
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
||||||
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA"),
|
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA")
|
||||||
};
|
};
|
||||||
|
ApplyReservaRule(e); buffer.Add(e); imported++;
|
||||||
ApplyReservaRule(entity);
|
if (buffer.Count >= 500) { await _db.MobileLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); buffer.Clear(); }
|
||||||
buffer.Add(entity);
|
|
||||||
imported++;
|
|
||||||
|
|
||||||
if (buffer.Count >= 500)
|
|
||||||
{
|
|
||||||
await _db.MobileLines.AddRangeAsync(buffer);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
buffer.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (buffer.Count > 0) { await _db.MobileLines.AddRangeAsync(buffer); await _db.SaveChangesAsync(); }
|
||||||
if (buffer.Count > 0)
|
|
||||||
{
|
|
||||||
await _db.MobileLines.AddRangeAsync(buffer);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(new ImportResultDto { Imported = imported });
|
return Ok(new ImportResultDto { Imported = imported });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// Helpers
|
||||||
// HELPERS
|
private static DateTime? ToUtc(DateTime? dt) { if (dt == null) return null; var v = dt.Value; return v.Kind == DateTimeKind.Utc ? v : (v.Kind == DateTimeKind.Local ? v.ToUniversalTime() : DateTime.SpecifyKind(v, DateTimeKind.Utc)); }
|
||||||
// ==========================================================
|
private static MobileLineDetailDto ToDetailDto(MobileLine x) => new() { Id = x.Id, Item = x.Item, Conta = x.Conta, Linha = x.Linha, Chip = x.Chip, Cliente = x.Cliente, Usuario = x.Usuario, PlanoContrato = x.PlanoContrato, FranquiaVivo = x.FranquiaVivo, ValorPlanoVivo = x.ValorPlanoVivo, GestaoVozDados = x.GestaoVozDados, Skeelo = x.Skeelo, VivoNewsPlus = x.VivoNewsPlus, VivoTravelMundo = x.VivoTravelMundo, VivoGestaoDispositivo = x.VivoGestaoDispositivo, ValorContratoVivo = x.ValorContratoVivo, FranquiaLine = x.FranquiaLine, FranquiaGestao = x.FranquiaGestao, LocacaoAp = x.LocacaoAp, ValorContratoLine = x.ValorContratoLine, Desconto = x.Desconto, Lucro = x.Lucro, Status = x.Status, DataBloqueio = x.DataBloqueio, Skil = x.Skil, Modalidade = x.Modalidade, Cedente = x.Cedente, Solicitante = x.Solicitante, DataEntregaOpera = x.DataEntregaOpera, DataEntregaCliente = x.DataEntregaCliente, VencConta = x.VencConta };
|
||||||
|
private static void ApplyReservaRule(MobileLine x) { if ((x.Cliente ?? "").Trim().ToUpper() == "RESERVA" || (x.Usuario ?? "").Trim().ToUpper() == "RESERVA") { x.Cliente = "RESERVA"; x.Usuario = "RESERVA"; x.Skil = "RESERVA"; } }
|
||||||
private static DateTime? ToUtc(DateTime? dt)
|
|
||||||
{
|
|
||||||
if (dt == null) return null;
|
|
||||||
var v = dt.Value;
|
|
||||||
return v.Kind switch
|
|
||||||
{
|
|
||||||
DateTimeKind.Utc => v,
|
|
||||||
DateTimeKind.Local => v.ToUniversalTime(),
|
|
||||||
_ => DateTime.SpecifyKind(v, DateTimeKind.Utc)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MobileLineDetailDto ToDetailDto(MobileLine x) => new()
|
|
||||||
{
|
|
||||||
Id = x.Id,
|
|
||||||
Item = x.Item,
|
|
||||||
Conta = x.Conta,
|
|
||||||
Linha = x.Linha,
|
|
||||||
Chip = x.Chip,
|
|
||||||
Cliente = x.Cliente,
|
|
||||||
Usuario = x.Usuario,
|
|
||||||
PlanoContrato = x.PlanoContrato,
|
|
||||||
FranquiaVivo = x.FranquiaVivo,
|
|
||||||
ValorPlanoVivo = x.ValorPlanoVivo,
|
|
||||||
GestaoVozDados = x.GestaoVozDados,
|
|
||||||
Skeelo = x.Skeelo,
|
|
||||||
VivoNewsPlus = x.VivoNewsPlus,
|
|
||||||
VivoTravelMundo = x.VivoTravelMundo,
|
|
||||||
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
|
||||||
ValorContratoVivo = x.ValorContratoVivo,
|
|
||||||
FranquiaLine = x.FranquiaLine,
|
|
||||||
FranquiaGestao = x.FranquiaGestao,
|
|
||||||
LocacaoAp = x.LocacaoAp,
|
|
||||||
ValorContratoLine = x.ValorContratoLine,
|
|
||||||
Desconto = x.Desconto,
|
|
||||||
Lucro = x.Lucro,
|
|
||||||
Status = x.Status,
|
|
||||||
DataBloqueio = x.DataBloqueio,
|
|
||||||
Skil = x.Skil,
|
|
||||||
Modalidade = x.Modalidade,
|
|
||||||
Cedente = x.Cedente,
|
|
||||||
Solicitante = x.Solicitante,
|
|
||||||
DataEntregaOpera = x.DataEntregaOpera,
|
|
||||||
DataEntregaCliente = x.DataEntregaCliente,
|
|
||||||
VencConta = x.VencConta
|
|
||||||
};
|
|
||||||
|
|
||||||
private static void ApplyReservaRule(MobileLine x)
|
|
||||||
{
|
|
||||||
var cliente = (x.Cliente ?? "").Trim();
|
|
||||||
var usuario = (x.Usuario ?? "").Trim();
|
|
||||||
if (cliente.Equals("RESERVA", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
usuario.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
x.Cliente = "RESERVA";
|
|
||||||
x.Usuario = "RESERVA";
|
|
||||||
x.Skil = "RESERVA";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetCol(Dictionary<string, int> map, string name) => map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;
|
private static int GetCol(Dictionary<string, int> map, string name) => map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;
|
||||||
|
private static string GetCellByHeader(IXLWorksheet ws, int row, Dictionary<string, int> map, string header) { var k = NormalizeHeader(header); return map.TryGetValue(k, out var c) ? GetCellString(ws, row, c) : ""; }
|
||||||
private static string GetCellByHeader(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
private static string GetCellString(IXLWorksheet ws, int row, int col) { var c = ws.Cell(row, col); return c == null ? "" : (c.GetValue<string>() ?? "").Trim(); }
|
||||||
{
|
private static DateTime? TryDate(IXLWorksheet ws, int row, Dictionary<string, int> map, string header) { var k = NormalizeHeader(header); if (!map.TryGetValue(k, out var c)) return null; var cell = ws.Cell(row, c); if (cell.DataType == XLDataType.DateTime) return ToUtc(cell.GetDateTime()); var s = cell.GetValue<string>()?.Trim(); if (string.IsNullOrWhiteSpace(s)) return null; if (DateTime.TryParse(s, new CultureInfo("pt-BR"), DateTimeStyles.None, out var d)) return ToUtc(d); return null; }
|
||||||
var key = NormalizeHeader(header);
|
private static decimal? TryDecimal(string? s) { if (string.IsNullOrWhiteSpace(s)) return null; s = s.Replace("R$", "").Trim(); if (decimal.TryParse(s, NumberStyles.Any, new CultureInfo("pt-BR"), out var d)) return d; if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d; return null; }
|
||||||
if (!map.TryGetValue(key, out var col)) return "";
|
|
||||||
return GetCellString(ws, row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetCellString(IXLWorksheet ws, int row, int col)
|
|
||||||
{
|
|
||||||
var cell = ws.Cell(row, col);
|
|
||||||
if (cell == null) return "";
|
|
||||||
var v = cell.GetValue<string>() ?? "";
|
|
||||||
return v.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DateTime? TryDate(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
|
||||||
{
|
|
||||||
var key = NormalizeHeader(header);
|
|
||||||
if (!map.TryGetValue(key, out var col)) return null;
|
|
||||||
|
|
||||||
var cell = ws.Cell(row, col);
|
|
||||||
if (cell.DataType == XLDataType.DateTime)
|
|
||||||
return ToUtc(cell.GetDateTime());
|
|
||||||
|
|
||||||
var s = cell.GetValue<string>()?.Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
||||||
|
|
||||||
if (DateTime.TryParse(s, new CultureInfo("pt-BR"), DateTimeStyles.None, out var d))
|
|
||||||
return ToUtc(d);
|
|
||||||
|
|
||||||
if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out d))
|
|
||||||
return ToUtc(d);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static decimal? TryDecimal(string? s)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
||||||
s = s.Replace("R$", "", StringComparison.OrdinalIgnoreCase).Trim();
|
|
||||||
if (decimal.TryParse(s, NumberStyles.Any, new CultureInfo("pt-BR"), out var d)) return d;
|
|
||||||
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int TryInt(string s) => int.TryParse(OnlyDigits(s), out var n) ? n : 0;
|
private static int TryInt(string s) => int.TryParse(OnlyDigits(s), out var n) ? n : 0;
|
||||||
|
private static string OnlyDigits(string? s) { if (string.IsNullOrWhiteSpace(s)) return ""; var sb = new StringBuilder(); foreach (var c in s) if (char.IsDigit(c)) sb.Append(c); return sb.ToString(); }
|
||||||
private static string OnlyDigits(string? s)
|
private static string NormalizeHeader(string? s) { if (string.IsNullOrWhiteSpace(s)) return ""; s = s.Trim().ToUpperInvariant().Normalize(NormalizationForm.FormD); var sb = new StringBuilder(); foreach (var c in s) if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) sb.Append(c); return sb.ToString().Normalize(NormalizationForm.FormC).Replace("ITEM", "ITEM").Replace("USUARIO", "USUARIO").Replace("GESTAO", "GESTAO").Replace("LOCACAO", "LOCACAO").Replace(" ", ""); }
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var ch in s) if (char.IsDigit(ch)) sb.Append(ch);
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeHeader(string? s)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
|
||||||
s = s.Trim().ToUpperInvariant();
|
|
||||||
var formD = s.Normalize(NormalizationForm.FormD);
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var ch in formD)
|
|
||||||
if (System.Globalization.CharUnicodeInfo.GetUnicodeCategory(ch) != System.Globalization.UnicodeCategory.NonSpacingMark)
|
|
||||||
sb.Append(ch);
|
|
||||||
|
|
||||||
s = sb.ToString().Normalize(NormalizationForm.FormC);
|
|
||||||
s = s.Replace("ITEM", "ITEM")
|
|
||||||
.Replace("USUARIO", "USUARIO")
|
|
||||||
.Replace("GESTAO", "GESTAO")
|
|
||||||
.Replace("LOCACAO", "LOCACAO");
|
|
||||||
|
|
||||||
s = string.Join(" ", s.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue