Merge pull request #3 from eduardolopesx03/feature/melhoria-busca-agrupamento
Feature/melhoria busca agrupamento
This commit is contained in:
commit
c81a7d1ed9
|
|
@ -0,0 +1,319 @@
|
||||||
|
using ClosedXML.Excel;
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
using line_gestao_api.Dtos;
|
||||||
|
using line_gestao_api.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
//[Authorize]
|
||||||
|
public class LinesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _db;
|
||||||
|
|
||||||
|
public LinesController(AppDbContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportExcelForm
|
||||||
|
{
|
||||||
|
public IFormFile File { get; set; } = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// ✅ 1. ENDPOINT: AGRUPAR POR CLIENTE (COM BUSCA E PAGINAÇÃO)
|
||||||
|
// ==========================================================
|
||||||
|
// Alterado para aceitar 'search', 'page' e retornar PagedResult
|
||||||
|
[HttpGet("groups")]
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
page = page < 1 ? 1 : page;
|
||||||
|
pageSize = pageSize < 1 ? 10 : pageSize;
|
||||||
|
|
||||||
|
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)
|
||||||
|
.Select(g => new ClientGroupDto
|
||||||
|
{
|
||||||
|
Cliente = g.Key!,
|
||||||
|
TotalLinhas = g.Count(),
|
||||||
|
Ativos = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%ativo%")),
|
||||||
|
Bloqueados = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%bloque%") ||
|
||||||
|
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)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Retorna formato paginado
|
||||||
|
return Ok(new PagedResult<ClientGroupDto>
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
Total = totalGroups,
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// ✅ 2. ENDPOINT: LISTAR NOMES DE CLIENTES
|
||||||
|
// ==========================================================
|
||||||
|
[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 / DETALHES DO GRUPO / BUSCA ESPECÍFICA)
|
||||||
|
// ==========================================================
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<PagedResult<MobileLineListDto>>> GetAll(
|
||||||
|
[FromQuery] string? search,
|
||||||
|
[FromQuery] string? skil,
|
||||||
|
[FromQuery] string? client,
|
||||||
|
[FromQuery] int page = 1,
|
||||||
|
[FromQuery] int pageSize = 20,
|
||||||
|
[FromQuery] string? sortBy = "item",
|
||||||
|
[FromQuery] string? sortDir = "asc")
|
||||||
|
{
|
||||||
|
page = page < 1 ? 1 : page;
|
||||||
|
pageSize = pageSize < 1 ? 20 : pageSize;
|
||||||
|
|
||||||
|
var q = _db.MobileLines.AsNoTracking();
|
||||||
|
|
||||||
|
// Filtro SKIL
|
||||||
|
if (!string.IsNullOrWhiteSpace(skil))
|
||||||
|
{
|
||||||
|
var sSkil = skil.Trim();
|
||||||
|
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}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro Cliente Específico (usado ao expandir o grupo)
|
||||||
|
if (!string.IsNullOrWhiteSpace(client))
|
||||||
|
{
|
||||||
|
var sClient = client.Trim();
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", sClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busca Genérica (usada quando o frontend detecta números ou busca específica)
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var s = search.Trim();
|
||||||
|
q = q.Where(x =>
|
||||||
|
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
|
||||||
|
EF.Functions.ILike(x.Chip ?? "", $"%{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.Conta ?? "", $"%{s}%") ||
|
||||||
|
EF.Functions.ILike(x.Status ?? "", $"%{s}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = await q.CountAsync();
|
||||||
|
|
||||||
|
var sb = (sortBy ?? "item").Trim().ToLowerInvariant();
|
||||||
|
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (sb == "plano") sb = "planocontrato";
|
||||||
|
if (sb == "contrato") sb = "vencconta";
|
||||||
|
|
||||||
|
q = sb switch
|
||||||
|
{
|
||||||
|
"conta" => desc ? q.OrderByDescending(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),
|
||||||
|
"cliente" => desc ? q.OrderByDescending(x => x.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente ?? "").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),
|
||||||
|
"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
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(x => new MobileLineListDto
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
Item = x.Item,
|
||||||
|
Conta = x.Conta,
|
||||||
|
Linha = x.Linha,
|
||||||
|
Chip = x.Chip,
|
||||||
|
Cliente = x.Cliente,
|
||||||
|
Usuario = x.Usuario,
|
||||||
|
PlanoContrato = x.PlanoContrato,
|
||||||
|
Status = x.Status,
|
||||||
|
Skil = x.Skil,
|
||||||
|
Modalidade = x.Modalidade,
|
||||||
|
VencConta = x.VencConta
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(new PagedResult<MobileLineListDto>
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
Total = total,
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// DEMAIS MÉTODOS (CRUD, IMPORT, HELPERS) - MANTIDOS
|
||||||
|
// ==========================================================
|
||||||
|
[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)); }
|
||||||
|
|
||||||
|
[HttpPut("{id:guid}")]
|
||||||
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
||||||
|
{
|
||||||
|
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
if (x == null) return NotFound();
|
||||||
|
var newLinha = OnlyDigits(req.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 registro com essa LINHA.", linha = newLinha }); }
|
||||||
|
|
||||||
|
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();
|
||||||
|
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." }); }
|
||||||
|
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(); }
|
||||||
|
|
||||||
|
[HttpPost("import-excel")]
|
||||||
|
[Consumes("multipart/form-data")]
|
||||||
|
[RequestSizeLimit(50_000_000)]
|
||||||
|
public async Task<ActionResult<ImportResultDto>> ImportExcel([FromForm] ImportExcelForm form)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
var ws = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("GERAL", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (ws == null) 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 map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
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 startRow = headerRow.RowNumber() + 1;
|
||||||
|
await _db.MobileLines.ExecuteDeleteAsync();
|
||||||
|
var buffer = new List<MobileLine>(600); var imported = 0;
|
||||||
|
for (int r = startRow; r <= ws.LastRowUsed().RowNumber(); r++)
|
||||||
|
{
|
||||||
|
var itemStr = GetCellString(ws, r, colItem); if (string.IsNullOrWhiteSpace(itemStr)) break;
|
||||||
|
var e = new MobileLine
|
||||||
|
{
|
||||||
|
Item = TryInt(itemStr),
|
||||||
|
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
||||||
|
Linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA")),
|
||||||
|
Chip = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP")),
|
||||||
|
Cliente = GetCellByHeader(ws, r, map, "CLIENTE"),
|
||||||
|
Usuario = GetCellByHeader(ws, r, map, "USUARIO"),
|
||||||
|
PlanoContrato = GetCellByHeader(ws, r, map, "PLANO CONTRATO"),
|
||||||
|
FranquiaVivo = TryDecimal(GetCellByHeader(ws, r, map, "FRAQUIA")),
|
||||||
|
ValorPlanoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR DO PLANO R$")),
|
||||||
|
GestaoVozDados = TryDecimal(GetCellByHeader(ws, r, map, "GESTAO VOZ E DADOS R$")),
|
||||||
|
Skeelo = TryDecimal(GetCellByHeader(ws, r, map, "SKEELO")),
|
||||||
|
VivoNewsPlus = TryDecimal(GetCellByHeader(ws, r, map, "VIVO NEWS PLUS")),
|
||||||
|
VivoTravelMundo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO TRAVEL MUNDO")),
|
||||||
|
VivoGestaoDispositivo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
||||||
|
ValorContratoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO VIVO")),
|
||||||
|
FranquiaLine = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA LINE")),
|
||||||
|
FranquiaGestao = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA GESTAO")),
|
||||||
|
LocacaoAp = TryDecimal(GetCellByHeader(ws, r, map, "LOCACAO AP.")),
|
||||||
|
ValorContratoLine = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO LINE")),
|
||||||
|
Desconto = TryDecimal(GetCellByHeader(ws, r, map, "DESCONTO")),
|
||||||
|
Lucro = TryDecimal(GetCellByHeader(ws, r, map, "LUCRO")),
|
||||||
|
Status = GetCellByHeader(ws, r, map, "STATUS"),
|
||||||
|
DataBloqueio = TryDate(ws, r, map, "DATA DO BLOQUEIO"),
|
||||||
|
Skil = GetCellByHeader(ws, r, map, "SKIL"),
|
||||||
|
Modalidade = GetCellByHeader(ws, r, map, "MODALIDADE"),
|
||||||
|
Cedente = GetCellByHeader(ws, r, map, "CEDENTE"),
|
||||||
|
Solicitante = GetCellByHeader(ws, r, map, "SOLICITANTE"),
|
||||||
|
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
||||||
|
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
||||||
|
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA")
|
||||||
|
};
|
||||||
|
ApplyReservaRule(e); buffer.Add(e); 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(); }
|
||||||
|
return Ok(new ImportResultDto { Imported = imported });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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 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; }
|
||||||
|
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; }
|
||||||
|
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 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(" ", ""); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,12 +9,21 @@ public class AppDbContext : DbContext
|
||||||
|
|
||||||
public DbSet<User> Users => Set<User>();
|
public DbSet<User> Users => Set<User>();
|
||||||
|
|
||||||
|
// ✅ NOVO: tabela para espelhar a planilha (GERAL)
|
||||||
|
public DbSet<MobileLine> MobileLines => Set<MobileLine>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
// ✅ MANTIDO: índice único do User (não mexi em nada aqui)
|
||||||
modelBuilder.Entity<User>()
|
modelBuilder.Entity<User>()
|
||||||
.HasIndex(u => u.Email)
|
.HasIndex(u => u.Email)
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
// ✅ NOVO: índice único para evitar duplicar a mesma linha (telefone)
|
||||||
|
modelBuilder.Entity<MobileLine>()
|
||||||
|
.HasIndex(x => x.Linha)
|
||||||
|
.IsUnique();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Dtos
|
||||||
|
{
|
||||||
|
public class ImportExcelForm
|
||||||
|
{
|
||||||
|
public IFormFile File { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
namespace line_gestao_api.Dtos
|
||||||
|
{
|
||||||
|
public class MobileLineListDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
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 string? Status { get; set; }
|
||||||
|
public string? Skil { get; set; }
|
||||||
|
public string? Modalidade { get; set; }
|
||||||
|
public string? VencConta { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MobileLineDetailDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ UPDATE REQUEST (SEM Id)
|
||||||
|
public class UpdateMobileLineRequest
|
||||||
|
{
|
||||||
|
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 int Imported { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace line_gestao_api.Dtos
|
||||||
|
{
|
||||||
|
public class PagedResult<T>
|
||||||
|
{
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public int Total { get; set; }
|
||||||
|
public List<T> Items { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace line_gestao_api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20251217170445_AddMobileLines")]
|
||||||
|
partial class AddMobileLines
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.1")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Cedente")
|
||||||
|
.HasMaxLength(150)
|
||||||
|
.HasColumnType("character varying(150)");
|
||||||
|
|
||||||
|
b.Property<string>("Chip")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)");
|
||||||
|
|
||||||
|
b.Property<string>("Cliente")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Conta")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataBloqueio")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataEntregaCliente")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataEntregaOpera")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Desconto")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaGestao")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaLine")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("GestaoVozDados")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<int>("Item")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Linha")
|
||||||
|
.HasMaxLength(30)
|
||||||
|
.HasColumnType("character varying(30)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("LocacaoAp")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Lucro")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("Modalidade")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<string>("PlanoContrato")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Skeelo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("Skil")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<string>("Solicitante")
|
||||||
|
.HasMaxLength(150)
|
||||||
|
.HasColumnType("character varying(150)");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Usuario")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorContratoLine")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorContratoVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorPlanoVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("VencConta")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoGestaoDispositivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoNewsPlus")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoTravelMundo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Linha")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("MobileLines");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("character varying(120)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("character varying(120)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Email")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace line_gestao_api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMobileLines : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "MobileLines",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Item = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Conta = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
|
||||||
|
Linha = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: true),
|
||||||
|
Chip = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true),
|
||||||
|
Cliente = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||||
|
Usuario = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||||
|
PlanoContrato = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||||
|
FranquiaVivo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
ValorPlanoVivo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
GestaoVozDados = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
Skeelo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
VivoNewsPlus = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
VivoTravelMundo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
VivoGestaoDispositivo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
ValorContratoVivo = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
FranquiaLine = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
FranquiaGestao = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
LocacaoAp = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
ValorContratoLine = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
Desconto = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
Lucro = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
Status = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
|
||||||
|
DataBloqueio = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Skil = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
|
||||||
|
Modalidade = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
|
||||||
|
Cedente = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: true),
|
||||||
|
Solicitante = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: true),
|
||||||
|
DataEntregaOpera = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
DataEntregaCliente = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
VencConta = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_MobileLines", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MobileLines_Linha",
|
||||||
|
table: "MobileLines",
|
||||||
|
column: "Linha",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "MobileLines");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,128 @@ namespace line_gestao_api.Migrations
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Cedente")
|
||||||
|
.HasMaxLength(150)
|
||||||
|
.HasColumnType("character varying(150)");
|
||||||
|
|
||||||
|
b.Property<string>("Chip")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)");
|
||||||
|
|
||||||
|
b.Property<string>("Cliente")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Conta")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataBloqueio")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataEntregaCliente")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DataEntregaOpera")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Desconto")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaGestao")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaLine")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("FranquiaVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("GestaoVozDados")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<int>("Item")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Linha")
|
||||||
|
.HasMaxLength(30)
|
||||||
|
.HasColumnType("character varying(30)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("LocacaoAp")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Lucro")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("Modalidade")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<string>("PlanoContrato")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Skeelo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("Skil")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<string>("Solicitante")
|
||||||
|
.HasMaxLength(150)
|
||||||
|
.HasColumnType("character varying(150)");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Usuario")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorContratoLine")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorContratoVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ValorPlanoVivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("VencConta")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoGestaoDispositivo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoNewsPlus")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<decimal?>("VivoTravelMundo")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Linha")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("MobileLines");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("line_gestao_api.Models.User", b =>
|
modelBuilder.Entity("line_gestao_api.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Models
|
||||||
|
{
|
||||||
|
public class MobileLine
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
// ===== Planilha (GERAL) =====
|
||||||
|
public int Item { get; set; } // ITÉM
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Conta { get; set; } // CONTA
|
||||||
|
[MaxLength(30)]
|
||||||
|
public string? Linha { get; set; } // LINHA (telefone)
|
||||||
|
[MaxLength(40)]
|
||||||
|
public string? Chip { get; set; } // CHIP
|
||||||
|
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? Cliente { get; set; } // CLIENTE
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? Usuario { get; set; } // USUÁRIO
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? PlanoContrato { get; set; } // PLANO CONTRATO
|
||||||
|
|
||||||
|
// ===== Valores Vivo (ROXO no modal do front) =====
|
||||||
|
public decimal? FranquiaVivo { get; set; } // FRAQUIA
|
||||||
|
public decimal? ValorPlanoVivo { get; set; } // VALOR DO PLANO R$
|
||||||
|
public decimal? GestaoVozDados { get; set; } // GESTÃO VOZ E DADOS R$
|
||||||
|
public decimal? Skeelo { get; set; } // SKEELO
|
||||||
|
public decimal? VivoNewsPlus { get; set; } // VIVO NEWS PLUS
|
||||||
|
public decimal? VivoTravelMundo { get; set; } // VIVO TRAVEL MUNDO
|
||||||
|
public decimal? VivoGestaoDispositivo { get; set; } // VIVO GESTÃO DISPOSITIVO
|
||||||
|
public decimal? ValorContratoVivo { get; set; } // VALOR CONTRATO VIVO
|
||||||
|
|
||||||
|
// ===== Valores Line Móvel (paleta do sistema no modal) =====
|
||||||
|
public decimal? FranquiaLine { get; set; } // FRANQUIA LINE
|
||||||
|
public decimal? FranquiaGestao { get; set; } // FRANQUIA GESTÃO
|
||||||
|
public decimal? LocacaoAp { get; set; } // LOCAÇÃO AP.
|
||||||
|
public decimal? ValorContratoLine { get; set; } // VALOR CONTRATO LINE
|
||||||
|
|
||||||
|
public decimal? Desconto { get; set; } // DESCONTO
|
||||||
|
public decimal? Lucro { get; set; } // LUCRO
|
||||||
|
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Status { get; set; } // STATUS
|
||||||
|
public DateTime? DataBloqueio { get; set; } // DATA DO BLOQUEIO
|
||||||
|
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Skil { get; set; } // SKIL
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Modalidade { get; set; } // MODALIDADE
|
||||||
|
|
||||||
|
[MaxLength(150)]
|
||||||
|
public string? Cedente { get; set; } // CEDENTE
|
||||||
|
[MaxLength(150)]
|
||||||
|
public string? Solicitante { get; set; } // SOLICITANTE
|
||||||
|
|
||||||
|
public DateTime? DataEntregaOpera { get; set; } // DATA DA ENTREGA OPERA.
|
||||||
|
public DateTime? DataEntregaCliente { get; set; } // DATA DA ENTREGA CLIENTE
|
||||||
|
|
||||||
|
[MaxLength(50)]
|
||||||
|
public string? VencConta { get; set; } // VENC. DA CONTA
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Program.cs
19
Program.cs
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
|
@ -8,7 +9,13 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
// ? CORS (Angular)
|
// ✅ Upload (Excel / multipart) - seguro e não quebra nada
|
||||||
|
builder.Services.Configure<FormOptions>(o =>
|
||||||
|
{
|
||||||
|
o.MultipartBodyLengthLimit = 50_000_000; // 50MB (mesmo do seu endpoint)
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ CORS (Angular)
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("Front", p =>
|
options.AddPolicy("Front", p =>
|
||||||
|
|
@ -18,16 +25,16 @@ builder.Services.AddCors(options =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// EF Core (PostgreSQL)
|
// ✅ EF Core (PostgreSQL)
|
||||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
|
options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Swagger
|
// ✅ Swagger
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
// JWT
|
// ✅ JWT
|
||||||
var jwtKey = builder.Configuration["Jwt:Key"]!;
|
var jwtKey = builder.Configuration["Jwt:Key"]!;
|
||||||
var issuer = builder.Configuration["Jwt:Issuer"];
|
var issuer = builder.Configuration["Jwt:Issuer"];
|
||||||
var audience = builder.Configuration["Jwt:Audience"];
|
var audience = builder.Configuration["Jwt:Audience"];
|
||||||
|
|
@ -60,7 +67,7 @@ if (app.Environment.IsDevelopment())
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
// ? CORS precisa vir antes de Auth/Authorization
|
// ✅ CORS precisa vir antes de Auth/Authorization
|
||||||
app.UseCors("Front");
|
app.UseCors("Front");
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue