Feat: Adiciona endpoints de agrupamento, listagem de clientes e filtros no LinesController
This commit is contained in:
parent
b4a5525578
commit
c830812af3
|
|
@ -1,8 +1,7 @@
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using line_gestao_api.Dtos;
|
using line_gestao_api.Dtos; // Certifique-se que ClientGroupDto está neste namespace
|
||||||
using line_gestao_api.Models;
|
using line_gestao_api.Models;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
@ -12,7 +11,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
//[Authorize]
|
//[Authorize] // Descomente se estiver usando autenticação
|
||||||
public class LinesController : ControllerBase
|
public class LinesController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
@ -22,15 +21,63 @@ namespace line_gestao_api.Controllers
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ DTO do form (pra Swagger entender multipart/form-data)
|
// 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')
|
||||||
|
// ==========================================================
|
||||||
|
[HttpGet("groups")]
|
||||||
|
public async Task<ActionResult<List<ClientGroupDto>>> GetClientGroups()
|
||||||
|
{
|
||||||
|
// Agrupa por nome do cliente e calcula os totais
|
||||||
|
var groups = await _db.MobileLines
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x.Cliente))
|
||||||
|
.GroupBy(x => x.Cliente)
|
||||||
|
.Select(g => new ClientGroupDto
|
||||||
|
{
|
||||||
|
Cliente = g.Key!,
|
||||||
|
TotalLinhas = g.Count(),
|
||||||
|
// Conta quantos contêm "ativo" (ignorando maiúsculas/minúsculas)
|
||||||
|
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%") || EF.Functions.ILike(x.Status ?? "", "%perda%") || EF.Functions.ILike(x.Status ?? "", "%roubo%"))
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.Cliente)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// ✅ 2. ENDPOINT: LISTAR NOMES DE CLIENTES (Para o Dropdown)
|
||||||
|
// ==========================================================
|
||||||
|
[HttpGet("clients")]
|
||||||
|
public async Task<ActionResult<List<string>>> GetClients()
|
||||||
|
{
|
||||||
|
var clients = await _db.MobileLines
|
||||||
|
.AsNoTracking()
|
||||||
|
.Select(x => x.Cliente)
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x))
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(x => x)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// ✅ 3. GET ALL (TABELA PRINCIPAL - Com todos os filtros)
|
||||||
|
// ==========================================================
|
||||||
[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? client, // Filtro Cliente Específico
|
||||||
[FromQuery] int page = 1,
|
[FromQuery] int page = 1,
|
||||||
[FromQuery] int pageSize = 20,
|
[FromQuery] int pageSize = 20,
|
||||||
[FromQuery] string? sortBy = "item",
|
[FromQuery] string? sortBy = "item",
|
||||||
|
|
@ -41,6 +88,21 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var q = _db.MobileLines.AsNoTracking();
|
var q = _db.MobileLines.AsNoTracking();
|
||||||
|
|
||||||
|
// 1. Filtro por SKIL (PF/PJ)
|
||||||
|
if (!string.IsNullOrWhiteSpace(skil))
|
||||||
|
{
|
||||||
|
var sSkil = skil.Trim();
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Filtro por Cliente Específico (usado no dropdown e no accordion)
|
||||||
|
if (!string.IsNullOrWhiteSpace(client))
|
||||||
|
{
|
||||||
|
var sClient = client.Trim();
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", sClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Busca Genérica (Barra de pesquisa)
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
{
|
{
|
||||||
var s = search.Trim();
|
var s = search.Trim();
|
||||||
|
|
@ -55,48 +117,26 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var total = await q.CountAsync();
|
var total = await q.CountAsync();
|
||||||
|
|
||||||
// ===== ORDENAÇÃO COMPLETA =====
|
// 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);
|
||||||
|
|
||||||
// sinônimos (pra não quebrar se vier diferente do front)
|
|
||||||
if (sb == "plano") sb = "planocontrato";
|
if (sb == "plano") sb = "planocontrato";
|
||||||
if (sb == "contrato") sb = "vencconta";
|
if (sb == "contrato") sb = "vencconta";
|
||||||
|
|
||||||
q = sb switch
|
q = sb switch
|
||||||
{
|
{
|
||||||
"conta" => desc ? q.OrderByDescending(x => x.Conta ?? "").ThenBy(x => x.Item)
|
"conta" => desc ? q.OrderByDescending(x => x.Conta ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Conta ?? "").ThenBy(x => x.Item),
|
||||||
: q.OrderBy(x => x.Conta ?? "").ThenBy(x => x.Item),
|
"linha" => desc ? q.OrderByDescending(x => x.Linha ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Linha ?? "").ThenBy(x => x.Item),
|
||||||
|
"chip" => desc ? q.OrderByDescending(x => x.Chip ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Chip ?? "").ThenBy(x => x.Item),
|
||||||
"linha" => desc ? q.OrderByDescending(x => x.Linha ?? "").ThenBy(x => x.Item)
|
"cliente" => desc ? q.OrderByDescending(x => x.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente ?? "").ThenBy(x => x.Item),
|
||||||
: q.OrderBy(x => x.Linha ?? "").ThenBy(x => x.Item),
|
"usuario" => desc ? q.OrderByDescending(x => x.Usuario ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Usuario ?? "").ThenBy(x => x.Item),
|
||||||
|
"planocontrato" => desc ? q.OrderByDescending(x => x.PlanoContrato ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.PlanoContrato ?? "").ThenBy(x => x.Item),
|
||||||
"chip" => desc ? q.OrderByDescending(x => x.Chip ?? "").ThenBy(x => x.Item)
|
"vencconta" => desc ? q.OrderByDescending(x => x.VencConta ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.VencConta ?? "").ThenBy(x => x.Item),
|
||||||
: q.OrderBy(x => x.Chip ?? "").ThenBy(x => x.Item),
|
"status" => desc ? q.OrderByDescending(x => x.Status ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Status ?? "").ThenBy(x => x.Item),
|
||||||
|
"skil" => desc ? q.OrderByDescending(x => x.Skil ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Skil ?? "").ThenBy(x => x.Item),
|
||||||
"cliente" => desc ? q.OrderByDescending(x => x.Cliente ?? "").ThenBy(x => x.Item)
|
"modalidade" => desc ? q.OrderByDescending(x => x.Modalidade ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Modalidade ?? "").ThenBy(x => x.Item),
|
||||||
: q.OrderBy(x => x.Cliente ?? "").ThenBy(x => x.Item),
|
_ => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item)
|
||||||
|
|
||||||
"usuario" => desc ? q.OrderByDescending(x => x.Usuario ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.Usuario ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
"planocontrato" => desc ? q.OrderByDescending(x => x.PlanoContrato ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.PlanoContrato ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
"vencconta" => desc ? q.OrderByDescending(x => x.VencConta ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.VencConta ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
"status" => desc ? q.OrderByDescending(x => x.Status ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.Status ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
"skil" => desc ? q.OrderByDescending(x => x.Skil ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.Skil ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
"modalidade" => desc ? q.OrderByDescending(x => x.Modalidade ?? "").ThenBy(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.Modalidade ?? "").ThenBy(x => x.Item),
|
|
||||||
|
|
||||||
_ => desc ? q.OrderByDescending(x => x.Item)
|
|
||||||
: q.OrderBy(x => x.Item)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var items = await q
|
var items = await q
|
||||||
|
|
@ -128,6 +168,9 @@ namespace line_gestao_api.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// OBTER DETALHES POR ID
|
||||||
|
// ==========================================================
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
||||||
{
|
{
|
||||||
|
|
@ -137,19 +180,43 @@ namespace line_gestao_api.Controllers
|
||||||
return Ok(ToDetailDto(x));
|
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 newChip = OnlyDigits(req.Chip);
|
||||||
|
|
||||||
|
// Verifica duplicidade de linha (se alterou a linha)
|
||||||
|
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 um registro com essa LINHA. Não é possível salvar duplicado.",
|
||||||
|
linha = newLinha
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualiza campos
|
||||||
x.Item = req.Item;
|
x.Item = req.Item;
|
||||||
x.Conta = req.Conta;
|
x.Conta = req.Conta?.Trim();
|
||||||
x.Linha = req.Linha;
|
x.Linha = string.IsNullOrWhiteSpace(newLinha) ? null : newLinha;
|
||||||
x.Chip = req.Chip;
|
x.Chip = string.IsNullOrWhiteSpace(newChip) ? null : newChip;
|
||||||
x.Cliente = req.Cliente;
|
x.Cliente = req.Cliente?.Trim();
|
||||||
x.Usuario = req.Usuario;
|
x.Usuario = req.Usuario?.Trim();
|
||||||
x.PlanoContrato = req.PlanoContrato;
|
x.PlanoContrato = req.PlanoContrato?.Trim();
|
||||||
|
|
||||||
x.FranquiaVivo = req.FranquiaVivo;
|
x.FranquiaVivo = req.FranquiaVivo;
|
||||||
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
||||||
|
|
@ -168,27 +235,40 @@ namespace line_gestao_api.Controllers
|
||||||
x.Desconto = req.Desconto;
|
x.Desconto = req.Desconto;
|
||||||
x.Lucro = req.Lucro;
|
x.Lucro = req.Lucro;
|
||||||
|
|
||||||
x.Status = req.Status;
|
x.Status = req.Status?.Trim();
|
||||||
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
||||||
|
|
||||||
x.Skil = req.Skil;
|
x.Skil = req.Skil?.Trim();
|
||||||
x.Modalidade = req.Modalidade;
|
x.Modalidade = req.Modalidade?.Trim();
|
||||||
x.Cedente = req.Cedente;
|
x.Cedente = req.Cedente?.Trim();
|
||||||
x.Solicitante = req.Solicitante;
|
x.Solicitante = req.Solicitante?.Trim();
|
||||||
|
|
||||||
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
||||||
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
||||||
x.VencConta = req.VencConta;
|
x.VencConta = req.VencConta?.Trim();
|
||||||
|
|
||||||
// regra RESERVA
|
|
||||||
ApplyReservaRule(x);
|
ApplyReservaRule(x);
|
||||||
|
|
||||||
x.UpdatedAt = DateTime.UtcNow;
|
x.UpdatedAt = DateTime.UtcNow;
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
return Conflict(new
|
||||||
|
{
|
||||||
|
message = "Conflito ao salvar. Verifique se a LINHA já existe em outro registro."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// DELETAR (DELETE)
|
||||||
|
// ==========================================================
|
||||||
[HttpDelete("{id:guid}")]
|
[HttpDelete("{id:guid}")]
|
||||||
public async Task<IActionResult> Delete(Guid id)
|
public async Task<IActionResult> Delete(Guid id)
|
||||||
{
|
{
|
||||||
|
|
@ -200,6 +280,9 @@ namespace line_gestao_api.Controllers
|
||||||
return NoContent();
|
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)]
|
||||||
|
|
@ -217,14 +300,12 @@ namespace line_gestao_api.Controllers
|
||||||
if (ws == null)
|
if (ws == null)
|
||||||
return BadRequest("Aba 'GERAL' não encontrada.");
|
return BadRequest("Aba 'GERAL' não encontrada.");
|
||||||
|
|
||||||
// acha a linha do cabeçalho (onde existe ITÉM)
|
|
||||||
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
||||||
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
||||||
|
|
||||||
if (headerRow == null)
|
if (headerRow == null)
|
||||||
return BadRequest("Cabeçalho da planilha (linha com 'ITÉM') não encontrado.");
|
return BadRequest("Cabeçalho da planilha (linha com 'ITÉM') não encontrado.");
|
||||||
|
|
||||||
// mapa header -> coluna
|
|
||||||
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())
|
||||||
{
|
{
|
||||||
|
|
@ -238,7 +319,7 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var startRow = headerRow.RowNumber() + 1;
|
var startRow = headerRow.RowNumber() + 1;
|
||||||
|
|
||||||
// REPLACE: apaga tudo e reimporta (pra espelhar 100% o Excel)
|
// Opção: Deletar tudo antes de importar (Cuidado em produção!)
|
||||||
await _db.MobileLines.ExecuteDeleteAsync();
|
await _db.MobileLines.ExecuteDeleteAsync();
|
||||||
|
|
||||||
var imported = 0;
|
var imported = 0;
|
||||||
|
|
@ -252,15 +333,12 @@ namespace line_gestao_api.Controllers
|
||||||
var entity = new MobileLine
|
var entity = new MobileLine
|
||||||
{
|
{
|
||||||
Item = TryInt(itemStr),
|
Item = TryInt(itemStr),
|
||||||
|
|
||||||
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
||||||
Linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA")),
|
Linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA")),
|
||||||
Chip = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP")),
|
Chip = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP")),
|
||||||
|
|
||||||
Cliente = GetCellByHeader(ws, r, map, "CLIENTE"),
|
Cliente = GetCellByHeader(ws, r, map, "CLIENTE"),
|
||||||
Usuario = GetCellByHeader(ws, r, map, "USUARIO"),
|
Usuario = GetCellByHeader(ws, r, map, "USUARIO"),
|
||||||
PlanoContrato = GetCellByHeader(ws, r, map, "PLANO CONTRATO"),
|
PlanoContrato = GetCellByHeader(ws, r, map, "PLANO CONTRATO"),
|
||||||
|
|
||||||
FranquiaVivo = TryDecimal(GetCellByHeader(ws, r, map, "FRAQUIA")),
|
FranquiaVivo = TryDecimal(GetCellByHeader(ws, r, map, "FRAQUIA")),
|
||||||
ValorPlanoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR DO PLANO R$")),
|
ValorPlanoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR DO PLANO R$")),
|
||||||
GestaoVozDados = TryDecimal(GetCellByHeader(ws, r, map, "GESTAO VOZ E DADOS R$")),
|
GestaoVozDados = TryDecimal(GetCellByHeader(ws, r, map, "GESTAO VOZ E DADOS R$")),
|
||||||
|
|
@ -269,30 +347,24 @@ namespace line_gestao_api.Controllers
|
||||||
VivoTravelMundo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO TRAVEL MUNDO")),
|
VivoTravelMundo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO TRAVEL MUNDO")),
|
||||||
VivoGestaoDispositivo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
VivoGestaoDispositivo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
||||||
ValorContratoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO VIVO")),
|
ValorContratoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO VIVO")),
|
||||||
|
|
||||||
FranquiaLine = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA LINE")),
|
FranquiaLine = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA LINE")),
|
||||||
FranquiaGestao = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA GESTAO")),
|
FranquiaGestao = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA GESTAO")),
|
||||||
LocacaoAp = TryDecimal(GetCellByHeader(ws, r, map, "LOCACAO AP.")),
|
LocacaoAp = TryDecimal(GetCellByHeader(ws, r, map, "LOCACAO AP.")),
|
||||||
ValorContratoLine = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO LINE")),
|
ValorContratoLine = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO LINE")),
|
||||||
|
|
||||||
Desconto = TryDecimal(GetCellByHeader(ws, r, map, "DESCONTO")),
|
Desconto = TryDecimal(GetCellByHeader(ws, r, map, "DESCONTO")),
|
||||||
Lucro = TryDecimal(GetCellByHeader(ws, r, map, "LUCRO")),
|
Lucro = TryDecimal(GetCellByHeader(ws, r, map, "LUCRO")),
|
||||||
|
|
||||||
Status = GetCellByHeader(ws, r, map, "STATUS"),
|
Status = GetCellByHeader(ws, r, map, "STATUS"),
|
||||||
DataBloqueio = TryDate(ws, r, map, "DATA DO BLOQUEIO"),
|
DataBloqueio = TryDate(ws, r, map, "DATA DO BLOQUEIO"),
|
||||||
|
|
||||||
Skil = GetCellByHeader(ws, r, map, "SKIL"),
|
Skil = GetCellByHeader(ws, r, map, "SKIL"),
|
||||||
Modalidade = GetCellByHeader(ws, r, map, "MODALIDADE"),
|
Modalidade = GetCellByHeader(ws, r, map, "MODALIDADE"),
|
||||||
Cedente = GetCellByHeader(ws, r, map, "CEDENTE"),
|
Cedente = GetCellByHeader(ws, r, map, "CEDENTE"),
|
||||||
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(entity);
|
ApplyReservaRule(entity);
|
||||||
|
|
||||||
buffer.Add(entity);
|
buffer.Add(entity);
|
||||||
imported++;
|
imported++;
|
||||||
|
|
||||||
|
|
@ -313,19 +385,19 @@ namespace line_gestao_api.Controllers
|
||||||
return Ok(new ImportResultDto { Imported = imported });
|
return Ok(new ImportResultDto { Imported = imported });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= helpers =================
|
// ==========================================================
|
||||||
|
// HELPERS
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
private static DateTime? ToUtc(DateTime? dt)
|
private static DateTime? ToUtc(DateTime? dt)
|
||||||
{
|
{
|
||||||
if (dt == null) return null;
|
if (dt == null) return null;
|
||||||
|
|
||||||
var v = dt.Value;
|
var v = dt.Value;
|
||||||
|
|
||||||
return v.Kind switch
|
return v.Kind switch
|
||||||
{
|
{
|
||||||
DateTimeKind.Utc => v,
|
DateTimeKind.Utc => v,
|
||||||
DateTimeKind.Local => v.ToUniversalTime(),
|
DateTimeKind.Local => v.ToUniversalTime(),
|
||||||
_ => DateTime.SpecifyKind(v, DateTimeKind.Utc) // Unspecified -> UTC (sem shift)
|
_ => DateTime.SpecifyKind(v, DateTimeKind.Utc)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,7 +411,6 @@ namespace line_gestao_api.Controllers
|
||||||
Cliente = x.Cliente,
|
Cliente = x.Cliente,
|
||||||
Usuario = x.Usuario,
|
Usuario = x.Usuario,
|
||||||
PlanoContrato = x.PlanoContrato,
|
PlanoContrato = x.PlanoContrato,
|
||||||
|
|
||||||
FranquiaVivo = x.FranquiaVivo,
|
FranquiaVivo = x.FranquiaVivo,
|
||||||
ValorPlanoVivo = x.ValorPlanoVivo,
|
ValorPlanoVivo = x.ValorPlanoVivo,
|
||||||
GestaoVozDados = x.GestaoVozDados,
|
GestaoVozDados = x.GestaoVozDados,
|
||||||
|
|
@ -348,15 +419,12 @@ namespace line_gestao_api.Controllers
|
||||||
VivoTravelMundo = x.VivoTravelMundo,
|
VivoTravelMundo = x.VivoTravelMundo,
|
||||||
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
||||||
ValorContratoVivo = x.ValorContratoVivo,
|
ValorContratoVivo = x.ValorContratoVivo,
|
||||||
|
|
||||||
FranquiaLine = x.FranquiaLine,
|
FranquiaLine = x.FranquiaLine,
|
||||||
FranquiaGestao = x.FranquiaGestao,
|
FranquiaGestao = x.FranquiaGestao,
|
||||||
LocacaoAp = x.LocacaoAp,
|
LocacaoAp = x.LocacaoAp,
|
||||||
ValorContratoLine = x.ValorContratoLine,
|
ValorContratoLine = x.ValorContratoLine,
|
||||||
|
|
||||||
Desconto = x.Desconto,
|
Desconto = x.Desconto,
|
||||||
Lucro = x.Lucro,
|
Lucro = x.Lucro,
|
||||||
|
|
||||||
Status = x.Status,
|
Status = x.Status,
|
||||||
DataBloqueio = x.DataBloqueio,
|
DataBloqueio = x.DataBloqueio,
|
||||||
Skil = x.Skil,
|
Skil = x.Skil,
|
||||||
|
|
@ -372,7 +440,6 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
var cliente = (x.Cliente ?? "").Trim();
|
var cliente = (x.Cliente ?? "").Trim();
|
||||||
var usuario = (x.Usuario ?? "").Trim();
|
var usuario = (x.Usuario ?? "").Trim();
|
||||||
|
|
||||||
if (cliente.Equals("RESERVA", StringComparison.OrdinalIgnoreCase) ||
|
if (cliente.Equals("RESERVA", StringComparison.OrdinalIgnoreCase) ||
|
||||||
usuario.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
usuario.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -382,8 +449,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetCol(Dictionary<string, int> map, string name)
|
private static int GetCol(Dictionary<string, int> map, string name) => map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;
|
||||||
=> map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;
|
|
||||||
|
|
||||||
private static string GetCellByHeader(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
private static string GetCellByHeader(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
||||||
{
|
{
|
||||||
|
|
@ -406,7 +472,6 @@ namespace line_gestao_api.Controllers
|
||||||
if (!map.TryGetValue(key, out var col)) return null;
|
if (!map.TryGetValue(key, out var col)) return null;
|
||||||
|
|
||||||
var cell = ws.Cell(row, col);
|
var cell = ws.Cell(row, col);
|
||||||
|
|
||||||
if (cell.DataType == XLDataType.DateTime)
|
if (cell.DataType == XLDataType.DateTime)
|
||||||
return ToUtc(cell.GetDateTime());
|
return ToUtc(cell.GetDateTime());
|
||||||
|
|
||||||
|
|
@ -425,28 +490,19 @@ namespace line_gestao_api.Controllers
|
||||||
private static decimal? TryDecimal(string? s)
|
private static decimal? TryDecimal(string? s)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(s)) return null;
|
if (string.IsNullOrWhiteSpace(s)) return null;
|
||||||
|
|
||||||
// remove "R$", espaços etc.
|
|
||||||
s = s.Replace("R$", "", StringComparison.OrdinalIgnoreCase).Trim();
|
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, new CultureInfo("pt-BR"), out var d))
|
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
||||||
return d;
|
|
||||||
|
|
||||||
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
|
|
||||||
return d;
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int TryInt(string s)
|
private static int TryInt(string s) => int.TryParse(OnlyDigits(s), out var n) ? n : 0;
|
||||||
=> int.TryParse(OnlyDigits(s), out var n) ? n : 0;
|
|
||||||
|
|
||||||
private static string OnlyDigits(string? s)
|
private static string OnlyDigits(string? s)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
if (string.IsNullOrWhiteSpace(s)) return "";
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
foreach (var ch in s)
|
foreach (var ch in s) if (char.IsDigit(ch)) sb.Append(ch);
|
||||||
if (char.IsDigit(ch)) sb.Append(ch);
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,8 +510,6 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(s)) return "";
|
if (string.IsNullOrWhiteSpace(s)) return "";
|
||||||
s = s.Trim().ToUpperInvariant();
|
s = s.Trim().ToUpperInvariant();
|
||||||
|
|
||||||
// remove acentos
|
|
||||||
var formD = s.Normalize(NormalizationForm.FormD);
|
var formD = s.Normalize(NormalizationForm.FormD);
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
foreach (var ch in formD)
|
foreach (var ch in formD)
|
||||||
|
|
@ -463,14 +517,11 @@ namespace line_gestao_api.Controllers
|
||||||
sb.Append(ch);
|
sb.Append(ch);
|
||||||
|
|
||||||
s = sb.ToString().Normalize(NormalizationForm.FormC);
|
s = sb.ToString().Normalize(NormalizationForm.FormC);
|
||||||
|
s = s.Replace("ITEM", "ITEM")
|
||||||
|
.Replace("USUARIO", "USUARIO")
|
||||||
|
.Replace("GESTAO", "GESTAO")
|
||||||
|
.Replace("LOCACAO", "LOCACAO");
|
||||||
|
|
||||||
// normalizações pra casar com a planilha
|
|
||||||
s = s.Replace("ITÉM", "ITEM")
|
|
||||||
.Replace("USUÁRIO", "USUARIO")
|
|
||||||
.Replace("GESTÃO", "GESTAO")
|
|
||||||
.Replace("LOCAÇÃO", "LOCACAO");
|
|
||||||
|
|
||||||
// remove espaços duplicados
|
|
||||||
s = string.Join(" ", s.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
s = string.Join(" ", s.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace line_gestao_api.Dtos
|
||||||
|
{
|
||||||
|
public class ClientGroupDto
|
||||||
|
{
|
||||||
|
public string Cliente { get; set; } = string.Empty;
|
||||||
|
public int TotalLinhas { get; set; }
|
||||||
|
public int Ativos { get; set; }
|
||||||
|
public int Bloqueados { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,9 +55,43 @@
|
||||||
public string? VencConta { get; set; }
|
public string? VencConta { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateMobileLineRequest : MobileLineDetailDto
|
// ✅ UPDATE REQUEST (SEM Id)
|
||||||
|
public class UpdateMobileLineRequest
|
||||||
{
|
{
|
||||||
// reaproveita os campos; Id vem na rota
|
public int Item { get; set; }
|
||||||
|
public string? Conta { get; set; }
|
||||||
|
public string? Linha { get; set; }
|
||||||
|
public string? Chip { get; set; }
|
||||||
|
public string? Cliente { get; set; }
|
||||||
|
public string? Usuario { get; set; }
|
||||||
|
public string? PlanoContrato { get; set; }
|
||||||
|
|
||||||
|
public decimal? FranquiaVivo { get; set; }
|
||||||
|
public decimal? ValorPlanoVivo { get; set; }
|
||||||
|
public decimal? GestaoVozDados { get; set; }
|
||||||
|
public decimal? Skeelo { get; set; }
|
||||||
|
public decimal? VivoNewsPlus { get; set; }
|
||||||
|
public decimal? VivoTravelMundo { get; set; }
|
||||||
|
public decimal? VivoGestaoDispositivo { get; set; }
|
||||||
|
public decimal? ValorContratoVivo { get; set; }
|
||||||
|
|
||||||
|
public decimal? FranquiaLine { get; set; }
|
||||||
|
public decimal? FranquiaGestao { get; set; }
|
||||||
|
public decimal? LocacaoAp { get; set; }
|
||||||
|
public decimal? ValorContratoLine { get; set; }
|
||||||
|
|
||||||
|
public decimal? Desconto { get; set; }
|
||||||
|
public decimal? Lucro { get; set; }
|
||||||
|
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? DataBloqueio { get; set; }
|
||||||
|
public string? Skil { get; set; }
|
||||||
|
public string? Modalidade { get; set; }
|
||||||
|
public string? Cedente { get; set; }
|
||||||
|
public string? Solicitante { get; set; }
|
||||||
|
public DateTime? DataEntregaOpera { get; set; }
|
||||||
|
public DateTime? DataEntregaCliente { get; set; }
|
||||||
|
public string? VencConta { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImportResultDto
|
public class ImportResultDto
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue