Salvando alterações do back-end com banco tabelas sem relacao

This commit is contained in:
Eduardo 2026-01-13 11:21:42 -03:00
parent 19a3dc3a59
commit 6a2d7689b5
16 changed files with 1853 additions and 865 deletions

View File

@ -5,8 +5,12 @@ using line_gestao_api.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace line_gestao_api.Controllers namespace line_gestao_api.Controllers
{ {
@ -107,7 +111,7 @@ namespace line_gestao_api.Controllers
var clients = await query var clients = await query
.Where(x => !string.IsNullOrEmpty(x.Cliente)) .Where(x => !string.IsNullOrEmpty(x.Cliente))
.Select(x => x.Cliente) .Select(x => x.Cliente!)
.Distinct() .Distinct()
.OrderBy(x => x) .OrderBy(x => x)
.ToListAsync(); .ToListAsync();
@ -159,10 +163,8 @@ namespace line_gestao_api.Controllers
"franquialine" => desc ? q.OrderByDescending(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente), "franquialine" => desc ? q.OrderByDescending(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente),
"valorcontratoline" => desc ? q.OrderByDescending(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente), "valorcontratoline" => desc ? q.OrderByDescending(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente),
"lucro" => desc ? q.OrderByDescending(x => x.Lucro ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.Lucro ?? 0).ThenBy(x => x.Cliente), "lucro" => desc ? q.OrderByDescending(x => x.Lucro ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.Lucro ?? 0).ThenBy(x => x.Cliente),
"aparelho" => desc ? q.OrderByDescending(x => x.Aparelho ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.Aparelho ?? "").ThenBy(x => x.Cliente), "aparelho" => desc ? q.OrderByDescending(x => x.Aparelho ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.Aparelho ?? "").ThenBy(x => x.Cliente),
"formapagamento" => desc ? q.OrderByDescending(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente), "formapagamento" => desc ? q.OrderByDescending(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente),
_ => desc ? q.OrderByDescending(x => x.Cliente).ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente).ThenBy(x => x.Item), _ => desc ? q.OrderByDescending(x => x.Cliente).ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente).ThenBy(x => x.Item),
}; };
@ -193,7 +195,7 @@ namespace line_gestao_api.Controllers
var clients = await q var clients = await q
.Where(x => !string.IsNullOrEmpty(x.Cliente)) .Where(x => !string.IsNullOrEmpty(x.Cliente))
.Select(x => x.Cliente) .Select(x => x.Cliente!)
.Distinct() .Distinct()
.OrderBy(x => x) .OrderBy(x => x)
.ToListAsync(); .ToListAsync();
@ -331,13 +333,15 @@ namespace line_gestao_api.Controllers
var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0; var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
var nextItem = maxItem + 1; var nextItem = maxItem + 1;
var now = DateTime.UtcNow;
var newLine = new MobileLine var newLine = new MobileLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = nextItem, Item = nextItem,
Cliente = req.Cliente.Trim().ToUpper(), Cliente = req.Cliente.Trim().ToUpper(),
Linha = linhaLimpa, Linha = linhaLimpa,
Chip = chipLimpo, Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
Usuario = req.Usuario?.Trim(), Usuario = req.Usuario?.Trim(),
Status = req.Status?.Trim(), Status = req.Status?.Trim(),
Skil = req.Skil?.Trim(), Skil = req.Skil?.Trim(),
@ -368,8 +372,8 @@ namespace line_gestao_api.Controllers
Desconto = req.Desconto, Desconto = req.Desconto,
Lucro = req.Lucro, Lucro = req.Lucro,
CreatedAt = DateTime.UtcNow, CreatedAt = now,
UpdatedAt = DateTime.UtcNow UpdatedAt = now
}; };
ApplyReservaRule(newLine); ApplyReservaRule(newLine);
@ -405,8 +409,11 @@ namespace line_gestao_api.Controllers
} }
x.Conta = req.Conta?.Trim(); x.Conta = req.Conta?.Trim();
x.Linha = newLinha; x.Linha = string.IsNullOrWhiteSpace(newLinha) ? null : newLinha;
x.Chip = OnlyDigits(req.Chip);
var newChip = OnlyDigits(req.Chip);
x.Chip = string.IsNullOrWhiteSpace(newChip) ? null : newChip;
x.Cliente = req.Cliente?.Trim(); x.Cliente = req.Cliente?.Trim();
x.Usuario = req.Usuario?.Trim(); x.Usuario = req.Usuario?.Trim();
x.PlanoContrato = req.PlanoContrato?.Trim(); x.PlanoContrato = req.PlanoContrato?.Trim();
@ -457,8 +464,10 @@ namespace line_gestao_api.Controllers
} }
// ========================================================== // ==========================================================
// ✅ 8. IMPORT EXCEL (GERAL + MUREG + FATURAMENTO + DADOS USUÁRIOS + VIGÊNCIA + TROCA DE NÚMERO + PARCELAMENTO) // ✅ 8. IMPORT EXCEL (GERAL + MUREG + FATURAMENTO + DADOS USUÁRIOS + VIGÊNCIA + TROCA DE NÚMERO)
// ✅ IMPORTAÇÃO CONTINUA NO LINESCONTROLLER //
// ✅ CORREÇÕES IMPORTANTES PARA NÃO ESTOURAR ERRO 500:
// - LINHA/CHIP vazios viram NULL (evita violar índice UNIQUE da LINHA com várias strings vazias)
// ========================================================== // ==========================================================
[HttpPost("import-excel")] [HttpPost("import-excel")]
[Consumes("multipart/form-data")] [Consumes("multipart/form-data")]
@ -468,6 +477,10 @@ namespace line_gestao_api.Controllers
var file = form.File; var file = form.File;
if (file == null || file.Length == 0) return BadRequest("Arquivo inválido."); if (file == null || file.Length == 0) return BadRequest("Arquivo inválido.");
await using var tx = await _db.Database.BeginTransactionAsync();
try
{
using var stream = file.OpenReadStream(); using var stream = file.OpenReadStream();
using var wb = new XLWorkbook(stream); using var wb = new XLWorkbook(stream);
@ -478,15 +491,16 @@ namespace line_gestao_api.Controllers
if (ws == null) return BadRequest("Aba 'GERAL' não encontrada."); if (ws == null) return BadRequest("Aba 'GERAL' não encontrada.");
var headerRow = ws.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM")); 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."); if (headerRow == null) return BadRequest("Cabeçalho 'ITEM' não encontrado na aba GERAL.");
var map = BuildHeaderMap(headerRow); var map = BuildHeaderMap(headerRow);
int colItem = GetCol(map, "ITEM"); int colItem = GetCol(map, "ITEM");
if (colItem == 0) return BadRequest("Coluna 'ITEM' não encontrada."); if (colItem == 0) return BadRequest("Coluna 'ITEM' não encontrada na aba GERAL.");
var startRow = headerRow.RowNumber() + 1; var startRow = headerRow.RowNumber() + 1;
// limpa tudo antes (idempotente)
await _db.MobileLines.ExecuteDeleteAsync(); await _db.MobileLines.ExecuteDeleteAsync();
var buffer = new List<MobileLine>(600); var buffer = new List<MobileLine>(600);
@ -499,30 +513,40 @@ namespace line_gestao_api.Controllers
var itemStr = GetCellString(ws, r, colItem); var itemStr = GetCellString(ws, r, colItem);
if (string.IsNullOrWhiteSpace(itemStr)) break; if (string.IsNullOrWhiteSpace(itemStr)) break;
var linhaDigits = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA"));
var chipDigits = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP"));
// ✅ se vier vazio, vira null (evita duplicidade de "")
var linhaVal = string.IsNullOrWhiteSpace(linhaDigits) ? null : linhaDigits;
var chipVal = string.IsNullOrWhiteSpace(chipDigits) ? null : chipDigits;
var now = DateTime.UtcNow;
var e = new MobileLine var e = new MobileLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
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 = linhaVal,
Chip = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP")), Chip = chipVal,
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")),
ValorPlanoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR DO PLANO R$")), FranquiaVivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "FRAQUIA", "FRANQUIA", "FRANQUIA VIVO", "FRAQUIA VIVO")),
GestaoVozDados = TryDecimal(GetCellByHeader(ws, r, map, "GESTAO VOZ E DADOS R$")), ValorPlanoVivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VALOR DO PLANO R$", "VALOR DO PLANO", "VALORPLANO")),
Skeelo = TryDecimal(GetCellByHeader(ws, r, map, "SKEELO")), GestaoVozDados = TryDecimal(GetCellByHeaderAny(ws, r, map, "GESTAO VOZ E DADOS R$", "GESTAO VOZ E DADOS", "GESTAOVOZEDADOS")),
VivoNewsPlus = TryDecimal(GetCellByHeader(ws, r, map, "VIVO NEWS PLUS")), Skeelo = TryDecimal(GetCellByHeaderAny(ws, r, map, "SKEELO")),
VivoTravelMundo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO TRAVEL MUNDO")), VivoNewsPlus = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO NEWS PLUS")),
VivoGestaoDispositivo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO GESTAO DISPOSITIVO")), VivoTravelMundo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO TRAVEL MUNDO")),
ValorContratoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO VIVO")), VivoGestaoDispositivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
FranquiaLine = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA LINE")), ValorContratoVivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO")),
FranquiaGestao = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA GESTAO")), FranquiaLine = TryDecimal(GetCellByHeaderAny(ws, r, map, "FRANQUIA LINE", "FRAQUIA LINE")),
LocacaoAp = TryDecimal(GetCellByHeader(ws, r, map, "LOCACAO AP.")), FranquiaGestao = TryDecimal(GetCellByHeaderAny(ws, r, map, "FRANQUIA GESTAO", "FRAQUIA GESTAO")),
ValorContratoLine = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO LINE")), LocacaoAp = TryDecimal(GetCellByHeaderAny(ws, r, map, "LOCACAO AP.", "LOCACAO AP", "LOCACAOAP")),
Desconto = TryDecimal(GetCellByHeader(ws, r, map, "DESCONTO")), ValorContratoLine = TryDecimal(GetCellByHeaderAny(ws, r, map, "VALOR CONTRATO LINE", "VALOR DO CONTRATO LINE")),
Lucro = TryDecimal(GetCellByHeader(ws, r, map, "LUCRO")), Desconto = TryDecimal(GetCellByHeaderAny(ws, r, map, "DESCONTO")),
Lucro = TryDecimal(GetCellByHeaderAny(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"),
@ -532,8 +556,8 @@ namespace line_gestao_api.Controllers
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"),
CreatedAt = DateTime.UtcNow, CreatedAt = now,
UpdatedAt = DateTime.UtcNow UpdatedAt = now
}; };
ApplyReservaRule(e); ApplyReservaRule(e);
@ -580,13 +604,15 @@ namespace line_gestao_api.Controllers
// ========================= // =========================
await ImportTrocaNumeroFromWorkbook(wb); await ImportTrocaNumeroFromWorkbook(wb);
// ========================= await tx.CommitAsync();
// ✅ IMPORTA PARCELAMENTO (NOVO)
// =========================
await ImportParcelamentoFromWorkbook(wb);
return Ok(new ImportResultDto { Imported = imported }); return Ok(new ImportResultDto { Imported = imported });
} }
catch (Exception ex)
{
await tx.RollbackAsync();
return StatusCode(500, new { message = "Erro ao importar Excel.", detail = ex.Message });
}
}
// ========================================================== // ==========================================================
// ✅ IMPORTAÇÃO DA ABA MUREG // ✅ IMPORTAÇÃO DA ABA MUREG
@ -611,8 +637,8 @@ namespace line_gestao_api.Controllers
await _db.MuregLines.ExecuteDeleteAsync(); await _db.MuregLines.ExecuteDeleteAsync();
var buffer = new List<MuregLine>(600); var buffer = new List<MuregLine>(600);
var lastRow = wsM.LastRowUsed()?.RowNumber() ?? startRow; var lastRow = wsM.LastRowUsed()?.RowNumber() ?? startRow;
for (int r = startRow; r <= lastRow; r++) for (int r = startRow; r <= lastRow; r++)
{ {
var itemStr = GetCellString(wsM, r, colItem); var itemStr = GetCellString(wsM, r, colItem);
@ -622,9 +648,9 @@ namespace line_gestao_api.Controllers
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = TryInt(itemStr), Item = TryInt(itemStr),
LinhaAntiga = OnlyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA")), LinhaAntiga = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA")),
LinhaNova = OnlyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA")), LinhaNova = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA")),
ICCID = OnlyDigits(GetCellByHeader(wsM, r, map, "ICCID")), ICCID = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "ICCID")),
DataDaMureg = TryDate(wsM, r, map, "DATA DA MUREG"), DataDaMureg = TryDate(wsM, r, map, "DATA DA MUREG"),
Cliente = GetCellByHeader(wsM, r, map, "CLIENTE"), Cliente = GetCellByHeader(wsM, r, map, "CLIENTE"),
}; };
@ -668,7 +694,6 @@ namespace line_gestao_api.Controllers
private async Task ImportBillingSheet(IXLWorksheet ws, string tipo) private async Task ImportBillingSheet(IXLWorksheet ws, string tipo)
{ {
// ✅ acha linha do header pelo "CLIENTE"
var headerRow = var headerRow =
ws.RowsUsed().FirstOrDefault(r => ws.RowsUsed().FirstOrDefault(r =>
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "CLIENTE")); r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "CLIENTE"));
@ -677,13 +702,12 @@ namespace line_gestao_api.Controllers
var headerRowIndex = headerRow.RowNumber(); var headerRowIndex = headerRow.RowNumber();
// ✅ na PJ (e às vezes PF), existe uma linha acima com os grupos: "VIVO" e "LINE MÓVEL" // linha acima (grupos VIVO / LINE)
var groupRowIndex = Math.Max(1, headerRowIndex - 1); var groupRowIndex = Math.Max(1, headerRowIndex - 1);
var groupRow = ws.Row(groupRowIndex); var groupRow = ws.Row(groupRowIndex);
var lastCol = GetLastUsedColumn(ws, headerRowIndex); var lastCol = GetLastUsedColumn(ws, headerRowIndex);
// colunas base (sempre pela linha de header)
var colItem = FindColByAny(headerRow, lastCol, "ITEM"); var colItem = FindColByAny(headerRow, lastCol, "ITEM");
var colCliente = FindColByAny(headerRow, lastCol, "CLIENTE"); var colCliente = FindColByAny(headerRow, lastCol, "CLIENTE");
if (colCliente == 0) return; if (colCliente == 0) return;
@ -693,13 +717,6 @@ namespace line_gestao_api.Controllers
var colAparelho = FindColByAny(headerRow, lastCol, "APARELHO"); var colAparelho = FindColByAny(headerRow, lastCol, "APARELHO");
var colForma = FindColByAny(headerRow, lastCol, "FORMA DE PAGAMENTO", "FORMA PAGAMENTO", "FORMAPAGAMENTO"); var colForma = FindColByAny(headerRow, lastCol, "FORMA DE PAGAMENTO", "FORMA PAGAMENTO", "FORMAPAGAMENTO");
// ----------------------------------------------------------
// ✅ Principal correção:
// No PJ, o valor da Vivo costuma estar em uma coluna com header vazio (ou "R$")
// e o grupo "VIVO" costuma estar MESCLADO (merge), deixando células vazias.
// ----------------------------------------------------------
// tenta detectar se existe "VIVO" e "LINE" na linha de grupos (se não tiver, cai em fallback)
var hasAnyGroup = RowHasAnyText(groupRow); var hasAnyGroup = RowHasAnyText(groupRow);
int colFranquiaVivo = 0; int colFranquiaVivo = 0;
@ -709,15 +726,13 @@ namespace line_gestao_api.Controllers
if (hasAnyGroup) if (hasAnyGroup)
{ {
// VIVO
colFranquiaVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO", colFranquiaVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO",
"FRANQUIA", "FRAQUIA", "FRANQUIAVIVO", "FRAQUIAVIVO"); "FRANQUIA", "FRAQUIA", "FRANQUIAVIVO", "FRAQUIAVIVO");
colValorVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO", colValorVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO",
"VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO", "VALOR VIVO", "VALOR", "VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO", "VALOR VIVO", "VALOR",
"R$", "RS", ""); // "" = aceita header vazio "R$", "RS", "");
// LINE
colFranquiaLine = FindColInGroup(groupRow, headerRow, lastCol, "LINE", colFranquiaLine = FindColInGroup(groupRow, headerRow, lastCol, "LINE",
"FRANQUIA LINE", "FRAQUIA LINE", "FRANQUIA", "FRAQUIA", "FRANQUIALINE", "FRAQUIALINE"); "FRANQUIA LINE", "FRAQUIA LINE", "FRANQUIA", "FRAQUIA", "FRANQUIALINE", "FRAQUIALINE");
@ -725,8 +740,6 @@ namespace line_gestao_api.Controllers
"VALOR CONTRATO LINE", "VALOR DO CONTRATO LINE", "VALOR LINE", "VALOR", "VALOR CONTRATO LINE", "VALOR DO CONTRATO LINE", "VALOR LINE", "VALOR",
"R$", "RS", ""); "R$", "RS", "");
// ✅ Fallback extra: se o valor não foi encontrado mas a Franquia foi,
// pega a coluna imediatamente à direita (comum quando header do valor é vazio)
if (colValorVivo == 0 && colFranquiaVivo > 0) if (colValorVivo == 0 && colFranquiaVivo > 0)
{ {
var cand = colFranquiaVivo + 1; var cand = colFranquiaVivo + 1;
@ -750,7 +763,6 @@ namespace line_gestao_api.Controllers
} }
} }
// ✅ fallback (caso a planilha não tenha linha de grupos)
if (colFranquiaVivo == 0 || colValorVivo == 0 || colFranquiaLine == 0 || colValorLine == 0) if (colFranquiaVivo == 0 || colValorVivo == 0 || colFranquiaLine == 0 || colValorLine == 0)
{ {
var map = BuildHeaderMap(headerRow); var map = BuildHeaderMap(headerRow);
@ -863,7 +875,6 @@ namespace line_gestao_api.Controllers
// ========================================================== // ==========================================================
private async Task ImportUserDatasFromWorkbook(XLWorkbook wb) private async Task ImportUserDatasFromWorkbook(XLWorkbook wb)
{ {
// procura aba com nome exato ou aproximado
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUÁRIOS")) var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUÁRIOS"))
?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUARIOS")) ?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUARIOS"))
?? wb.Worksheets.FirstOrDefault(w => ?? wb.Worksheets.FirstOrDefault(w =>
@ -872,7 +883,6 @@ namespace line_gestao_api.Controllers
if (ws == null) return; if (ws == null) return;
// header com ITEM ou CLIENTE (mais seguro)
var headerRow = ws.RowsUsed().FirstOrDefault(r => var headerRow = ws.RowsUsed().FirstOrDefault(r =>
r.CellsUsed().Any(c => r.CellsUsed().Any(c =>
NormalizeHeader(c.GetString()) == "ITEM" || NormalizeHeader(c.GetString()) == "ITEM" ||
@ -882,15 +892,12 @@ namespace line_gestao_api.Controllers
var map = BuildHeaderMap(headerRow); var map = BuildHeaderMap(headerRow);
// colunas principais
var colItem = GetCol(map, "ITEM"); var colItem = GetCol(map, "ITEM");
var colCliente = GetCol(map, "CLIENTE"); var colCliente = GetCol(map, "CLIENTE");
var colLinha = GetCol(map, "LINHA"); var colLinha = GetCol(map, "LINHA");
// se não tiver cliente, não importa
if (colCliente == 0) return; if (colCliente == 0) return;
// limpar tabela (espelho da planilha)
await _db.UserDatas.ExecuteDeleteAsync(); await _db.UserDatas.ExecuteDeleteAsync();
var startRow = headerRow.RowNumber() + 1; var startRow = headerRow.RowNumber() + 1;
@ -899,7 +906,6 @@ namespace line_gestao_api.Controllers
var buffer = new List<UserData>(500); var buffer = new List<UserData>(500);
var seq = 0; var seq = 0;
// colunas opcionais (várias variações de header)
var colCpf = GetColAny(map, "CPF"); var colCpf = GetColAny(map, "CPF");
var colRg = GetColAny(map, "RG"); var colRg = GetColAny(map, "RG");
var colEmail = GetColAny(map, "EMAIL", "E-MAIL"); var colEmail = GetColAny(map, "EMAIL", "E-MAIL");
@ -930,10 +936,10 @@ namespace line_gestao_api.Controllers
} }
else item = seq; else item = seq;
var linha = colLinha > 0 ? OnlyDigits(GetCellString(ws, r, colLinha)) : ""; var linha = colLinha > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colLinha)) : null;
var cpf = colCpf > 0 ? OnlyDigits(GetCellString(ws, r, colCpf)) : ""; var cpf = colCpf > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colCpf)) : null;
var rg = colRg > 0 ? OnlyDigits(GetCellString(ws, r, colRg)) : ""; var rg = colRg > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colRg)) : null;
DateTime? dataNascimento = null; DateTime? dataNascimento = null;
if (colDataNasc > 0) if (colDataNasc > 0)
@ -942,8 +948,8 @@ namespace line_gestao_api.Controllers
var email = colEmail > 0 ? GetCellString(ws, r, colEmail) : ""; var email = colEmail > 0 ? GetCellString(ws, r, colEmail) : "";
var endereco = colEndereco > 0 ? GetCellString(ws, r, colEndereco) : ""; var endereco = colEndereco > 0 ? GetCellString(ws, r, colEndereco) : "";
var celular = colCelular > 0 ? OnlyDigits(GetCellString(ws, r, colCelular)) : ""; var celular = colCelular > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colCelular)) : null;
var fixo = colFixo > 0 ? OnlyDigits(GetCellString(ws, r, colFixo)) : ""; var fixo = colFixo > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colFixo)) : null;
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -951,18 +957,18 @@ namespace line_gestao_api.Controllers
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = item, Item = item,
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha, Linha = linha,
Cliente = cliente.Trim(), Cliente = cliente.Trim(),
Cpf = string.IsNullOrWhiteSpace(cpf) ? null : cpf, Cpf = cpf,
Rg = string.IsNullOrWhiteSpace(rg) ? null : rg, Rg = rg,
DataNascimento = ToUtc(dataNascimento), DataNascimento = ToUtc(dataNascimento),
Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(), Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(),
Endereco = string.IsNullOrWhiteSpace(endereco) ? null : endereco.Trim(), Endereco = string.IsNullOrWhiteSpace(endereco) ? null : endereco.Trim(),
Celular = string.IsNullOrWhiteSpace(celular) ? null : celular, Celular = celular,
TelefoneFixo = string.IsNullOrWhiteSpace(fixo) ? null : fixo, TelefoneFixo = fixo,
CreatedAt = now, CreatedAt = now,
UpdatedAt = now UpdatedAt = now
@ -1023,7 +1029,7 @@ namespace line_gestao_api.Controllers
if (string.IsNullOrWhiteSpace(itemStr)) break; if (string.IsNullOrWhiteSpace(itemStr)) break;
var conta = GetCellByHeader(ws, r, map, "CONTA"); var conta = GetCellByHeader(ws, r, map, "CONTA");
var linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA")); var linha = NullIfEmptyDigits(GetCellByHeader(ws, r, map, "LINHA"));
var cliente = GetCellByHeader(ws, r, map, "CLIENTE"); var cliente = GetCellByHeader(ws, r, map, "CLIENTE");
var usuario = GetCellByHeader(ws, r, map, "USUÁRIO"); var usuario = GetCellByHeader(ws, r, map, "USUÁRIO");
if (string.IsNullOrWhiteSpace(usuario)) if (string.IsNullOrWhiteSpace(usuario))
@ -1045,7 +1051,7 @@ namespace line_gestao_api.Controllers
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = TryInt(itemStr), Item = TryInt(itemStr),
Conta = string.IsNullOrWhiteSpace(conta) ? null : conta.Trim(), Conta = string.IsNullOrWhiteSpace(conta) ? null : conta.Trim(),
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha, Linha = linha,
Cliente = string.IsNullOrWhiteSpace(cliente) ? null : cliente.Trim(), Cliente = string.IsNullOrWhiteSpace(cliente) ? null : cliente.Trim(),
Usuario = string.IsNullOrWhiteSpace(usuario) ? null : usuario.Trim(), Usuario = string.IsNullOrWhiteSpace(usuario) ? null : usuario.Trim(),
PlanoContrato = string.IsNullOrWhiteSpace(plano) ? null : plano.Trim(), PlanoContrato = string.IsNullOrWhiteSpace(plano) ? null : plano.Trim(),
@ -1108,9 +1114,9 @@ namespace line_gestao_api.Controllers
var itemStr = GetCellString(ws, r, colItem); var itemStr = GetCellString(ws, r, colItem);
if (string.IsNullOrWhiteSpace(itemStr)) break; if (string.IsNullOrWhiteSpace(itemStr)) break;
var linhaAntiga = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA ANTIGA")); var linhaAntiga = NullIfEmptyDigits(GetCellByHeader(ws, r, map, "LINHA ANTIGA"));
var linhaNova = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA NOVA")); var linhaNova = NullIfEmptyDigits(GetCellByHeader(ws, r, map, "LINHA NOVA"));
var iccid = OnlyDigits(GetCellByHeader(ws, r, map, "ICCID")); var iccid = NullIfEmptyDigits(GetCellByHeader(ws, r, map, "ICCID"));
var dataTroca = TryDate(ws, r, map, "DATA TROCA"); var dataTroca = TryDate(ws, r, map, "DATA TROCA");
if (dataTroca == null) dataTroca = TryDate(ws, r, map, "DATA DA TROCA"); if (dataTroca == null) dataTroca = TryDate(ws, r, map, "DATA DA TROCA");
@ -1125,9 +1131,9 @@ namespace line_gestao_api.Controllers
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = TryInt(itemStr), Item = TryInt(itemStr),
LinhaAntiga = string.IsNullOrWhiteSpace(linhaAntiga) ? null : linhaAntiga, LinhaAntiga = linhaAntiga,
LinhaNova = string.IsNullOrWhiteSpace(linhaNova) ? null : linhaNova, LinhaNova = linhaNova,
ICCID = string.IsNullOrWhiteSpace(iccid) ? null : iccid, ICCID = iccid,
DataTroca = dataTroca, DataTroca = dataTroca,
Motivo = string.IsNullOrWhiteSpace(motivo) ? null : motivo.Trim(), Motivo = string.IsNullOrWhiteSpace(motivo) ? null : motivo.Trim(),
Observacao = string.IsNullOrWhiteSpace(obs) ? null : obs.Trim(), Observacao = string.IsNullOrWhiteSpace(obs) ? null : obs.Trim(),
@ -1153,283 +1159,7 @@ namespace line_gestao_api.Controllers
} }
// ========================================================== // ==========================================================
// ✅ IMPORTAÇÃO: PARCELAMENTO (NOVO) // HELPERS (SEUS - + AJUSTES)
// - aba real costuma ser "PARCELAMENTOS DE APARELHOS"
// - mas buscamos qualquer aba que contenha "PARCEL"
// ==========================================================
private async Task ImportParcelamentoFromWorkbook(XLWorkbook wb)
{
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name).Contains("PARCEL"));
if (ws == null) return;
// acha linha de cabeçalho (onde existem LINHA e CLIENTE)
var headerRowIndex = FindParcelamentoHeaderRow(ws);
if (headerRowIndex <= 0) return;
// localizar colunas principais
int colLinha = FindColExact(ws, headerRowIndex, "LINHA");
int colCliente = FindColExact(ws, headerRowIndex, "CLIENTE");
int colQtParcelas = FindColContains(ws, headerRowIndex, "QTPARCELAS");
int colValorCheio = FindColContains(ws, headerRowIndex, "VALORCHEIO");
int colDesconto = FindColContains(ws, headerRowIndex, "DESCONTO");
int colValorComDesc = FindColContains(ws, headerRowIndex, "VALORCDESCONTO");
if (colLinha == 0 || colCliente == 0 || colValorComDesc == 0) return;
// ano e item ficam antes de LINHA (ex: ... 2025 | 1 | 7199... | CONSEF ...)
int colAno = Math.Max(1, colLinha - 2);
int colItem = Math.Max(1, colLinha - 1);
// meses ficam após VALOR C\ DESCONTO
var monthCols = FindMonthColumns(ws, headerRowIndex, colValorComDesc + 1);
if (monthCols.Count == 0) return;
// linha acima do header tem os anos (ex: 2005, 2026, 2027) em células mescladas
var yearRowIndex = Math.Max(1, headerRowIndex - 1);
// minAno real (pra corrigir 2005 -> 2025)
var minAnoRef = FindMinAnoRef(ws, headerRowIndex + 2, colAno, colLinha);
// map coluna -> competencia
var colToCompetencia = BuildCompetenciaMap(ws, headerRowIndex, yearRowIndex, monthCols, minAnoRef);
// limpa tabela do parcelamento (cascade resolve meses)
await _db.ParcelamentoMonthValues.ExecuteDeleteAsync();
await _db.ParcelamentoLines.ExecuteDeleteAsync();
var bufferLines = new List<ParcelamentoLine>(300);
var bufferMonths = new List<ParcelamentoMonthValue>(1200);
// dados começam em header + 2 (porque tem uma linha de total logo abaixo do header)
var startRow = headerRowIndex + 2;
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
for (int r = startRow; r <= lastRow; r++)
{
var linhaRaw = GetCellString(ws, r, colLinha);
if (string.IsNullOrWhiteSpace(linhaRaw)) break;
var linha = OnlyDigits(linhaRaw);
var cliente = GetCellString(ws, r, colCliente);
var anoStr = GetCellString(ws, r, colAno);
var itemStr = GetCellString(ws, r, colItem);
var e = new ParcelamentoLine
{
Id = Guid.NewGuid(),
AnoRef = TryNullableInt(anoStr),
Item = TryNullableInt(itemStr),
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha,
Cliente = string.IsNullOrWhiteSpace(cliente) ? null : cliente.Trim(),
QtParcelas = colQtParcelas > 0 ? GetCellString(ws, r, colQtParcelas) : null,
ValorCheio = colValorCheio > 0 ? TryDecimal(GetCellString(ws, r, colValorCheio)) : null,
Desconto = colDesconto > 0 ? TryDecimal(GetCellString(ws, r, colDesconto)) : null,
ValorComDesconto = TryDecimal(GetCellString(ws, r, colValorComDesc)),
CreatedAt = DateTime.UtcNow
};
bufferLines.Add(e);
foreach (var col in monthCols)
{
var v = ParseDecimalCell(ws.Cell(r, col));
if (v == null) continue;
bufferMonths.Add(new ParcelamentoMonthValue
{
Id = Guid.NewGuid(),
ParcelamentoLineId = e.Id,
Competencia = colToCompetencia[col],
Valor = v.Value
});
}
if (bufferLines.Count >= 250)
{
await _db.ParcelamentoLines.AddRangeAsync(bufferLines);
await _db.ParcelamentoMonthValues.AddRangeAsync(bufferMonths);
await _db.SaveChangesAsync();
bufferLines.Clear();
bufferMonths.Clear();
}
}
if (bufferLines.Count > 0)
{
await _db.ParcelamentoLines.AddRangeAsync(bufferLines);
await _db.ParcelamentoMonthValues.AddRangeAsync(bufferMonths);
await _db.SaveChangesAsync();
bufferLines.Clear();
bufferMonths.Clear();
}
}
// ============================
// PARCELAMENTO HELPERS (NOVO)
// ============================
private static int FindParcelamentoHeaderRow(IXLWorksheet ws)
{
// busca nas primeiras 60 linhas uma linha que contenha LINHA e CLIENTE
for (int r = 1; r <= 60; r++)
{
bool hasLinha = false;
bool hasCliente = false;
foreach (var c in ws.Row(r).CellsUsed())
{
var k = NormalizeHeader(c.GetString());
if (k == "LINHA") hasLinha = true;
if (k == "CLIENTE") hasCliente = true;
if (hasLinha && hasCliente) return r;
}
}
return 0;
}
private static int FindColExact(IXLWorksheet ws, int row, string header)
{
var target = NormalizeHeader(header);
foreach (var c in ws.Row(row).CellsUsed())
{
var k = NormalizeHeader(c.GetString());
if (k == target) return c.Address.ColumnNumber;
}
return 0;
}
private static int FindColContains(IXLWorksheet ws, int row, string headerKeyNoSpaces)
{
var target = NormalizeHeader(headerKeyNoSpaces);
foreach (var c in ws.Row(row).CellsUsed())
{
var k = NormalizeHeader(c.GetString());
if (!string.IsNullOrWhiteSpace(k) && k.Contains(target))
return c.Address.ColumnNumber;
}
return 0;
}
private static List<int> FindMonthColumns(IXLWorksheet ws, int headerRow, int startCol)
{
var cols = new List<int>();
var months = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"JAN","FEV","MAR","ABR","MAI","JUN","JUL","AGO","SET","OUT","NOV","DEZ"
};
int emptyStreak = 0;
for (int col = startCol; col <= 220; col++)
{
var v = NormalizeHeader(ws.Cell(headerRow, col).GetString());
if (string.IsNullOrWhiteSpace(v))
{
emptyStreak++;
if (emptyStreak >= 6) break;
continue;
}
emptyStreak = 0;
// mês vem abreviado (JAN/FEV/...)
var raw = ws.Cell(headerRow, col).GetValue<string>()?.Trim().ToUpperInvariant() ?? "";
if (months.Contains(raw))
cols.Add(col);
}
return cols;
}
private static int FindMinAnoRef(IXLWorksheet ws, int startRow, int colAno, int colLinha)
{
int min = int.MaxValue;
for (int r = startRow; r <= startRow + 250; r++)
{
var linha = ws.Cell(r, colLinha).GetValue<string>()?.Trim();
if (string.IsNullOrWhiteSpace(linha)) break;
var sAno = ws.Cell(r, colAno).GetValue<string>()?.Trim();
if (int.TryParse(OnlyDigits(sAno), out var ano) && ano > 1900)
min = Math.Min(min, ano);
}
return min == int.MaxValue ? 2025 : min;
}
private static Dictionary<int, DateTime> BuildCompetenciaMap(
IXLWorksheet ws,
int headerRow,
int yearRow,
List<int> monthCols,
int minAnoRef)
{
int MonthToNum(string abbr) => abbr.ToUpperInvariant() switch
{
"JAN" => 1,
"FEV" => 2,
"MAR" => 3,
"ABR" => 4,
"MAI" => 5,
"JUN" => 6,
"JUL" => 7,
"AGO" => 8,
"SET" => 9,
"OUT" => 10,
"NOV" => 11,
"DEZ" => 12,
_ => 1
};
int YearAtColumnMergeAware(int col)
{
// caminha para esquerda até achar um número de ano
for (int c = col; c >= 1; c--)
{
var s = ws.Cell(yearRow, c).GetValue<string>()?.Trim();
if (string.IsNullOrWhiteSpace(s)) continue;
if (int.TryParse(OnlyDigits(s), out var y) && y >= 1000)
{
// correção: 2005 na sua planilha é 2025 (quando minAnoRef >= 2020)
if (y < 2010 && minAnoRef >= 2020) y += 20;
return y;
}
}
return minAnoRef;
}
var map = new Dictionary<int, DateTime>();
foreach (var col in monthCols)
{
var mAbbr = ws.Cell(headerRow, col).GetValue<string>()?.Trim().ToUpperInvariant() ?? "";
var year = YearAtColumnMergeAware(col);
var month = MonthToNum(mAbbr);
map[col] = new DateTime(year, month, 1);
}
return map;
}
private static decimal? ParseDecimalCell(IXLCell cell)
{
// tenta tipo numérico direto
if (cell.DataType == XLDataType.Number)
{
if (cell.TryGetValue<double>(out var d))
return (decimal)d;
}
// tenta string
var s = cell.GetValue<string>()?.Trim();
return TryDecimal(s);
}
// ==========================================================
// HELPERS (SEUS - MANTIDOS)
// ========================================================== // ==========================================================
private static Dictionary<string, int> BuildHeaderMap(IXLRow headerRow) private static Dictionary<string, int> BuildHeaderMap(IXLRow headerRow)
{ {
@ -1515,6 +1245,17 @@ namespace line_gestao_api.Controllers
return map.TryGetValue(k, out var c) ? GetCellString(ws, row, c) : ""; return map.TryGetValue(k, out var c) ? GetCellString(ws, row, c) : "";
} }
private static string GetCellByHeaderAny(IXLWorksheet ws, int row, Dictionary<string, int> map, params string[] headers)
{
foreach (var h in headers)
{
var k = NormalizeHeader(h);
if (map.TryGetValue(k, out var c))
return GetCellString(ws, row, c);
}
return "";
}
private static string GetCellString(IXLWorksheet ws, int row, int col) private static string GetCellString(IXLWorksheet ws, int row, int col)
{ {
if (col <= 0) return ""; if (col <= 0) return "";
@ -1558,7 +1299,6 @@ namespace line_gestao_api.Controllers
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)) return d;
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d; if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
// fallback: normaliza separadores se vier "7.469,62"
var s2 = s.Replace(".", "").Replace(",", "."); var s2 = s.Replace(".", "").Replace(",", ".");
if (decimal.TryParse(s2, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d; if (decimal.TryParse(s2, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
@ -1584,6 +1324,12 @@ namespace line_gestao_api.Controllers
return sb.ToString(); return sb.ToString();
} }
private static string? NullIfEmptyDigits(string? s)
{
var d = OnlyDigits(s);
return string.IsNullOrWhiteSpace(d) ? null : d;
}
private static string NormalizeHeader(string? s) private static string NormalizeHeader(string? s)
{ {
if (string.IsNullOrWhiteSpace(s)) return ""; if (string.IsNullOrWhiteSpace(s)) return "";
@ -1603,7 +1349,7 @@ namespace line_gestao_api.Controllers
} }
// ========================================================== // ==========================================================
// ✅ BILLING HELPERS (resolve duplicados tipo "R$" no PJ) // ✅ BILLING HELPERS
// ========================================================== // ==========================================================
private static int GetLastUsedColumn(IXLWorksheet ws, int headerRowIndex) private static int GetLastUsedColumn(IXLWorksheet ws, int headerRowIndex)
{ {

View File

@ -1,181 +0,0 @@
using line_gestao_api.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
namespace line_gestao_api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ParcelamentoController : ControllerBase
{
private readonly AppDbContext _db;
public ParcelamentoController(AppDbContext db)
{
_db = db;
}
public class ParcelamentoKpisDto
{
public decimal TotalGeral { get; set; }
public int Linhas { get; set; }
public int Clientes { get; set; }
public string? CompetenciaInicial { get; set; } // yyyy-MM
public string? CompetenciaFinal { get; set; } // yyyy-MM
public string? MesAtual { get; set; } // yyyy-MM
public decimal TotalMesAtual { get; set; }
}
public class ParcelamentoMonthlyTotalDto
{
public string Competencia { get; set; } = ""; // yyyy-MM
public decimal Total { get; set; }
}
public class ParcelamentoMonthDetailDto
{
public string? Linha { get; set; }
public string? Cliente { get; set; }
public decimal Valor { get; set; }
}
// =========================
// Clientes (dropdown)
// =========================
[HttpGet("clientes")]
public async Task<ActionResult<List<string>>> GetClientes()
{
var clientes = await _db.ParcelamentoLines.AsNoTracking()
.Where(x => x.Cliente != null && x.Cliente != "")
.Select(x => x.Cliente!)
.Distinct()
.OrderBy(x => x)
.ToListAsync();
return Ok(clientes);
}
// =========================
// KPIs
// =========================
[HttpGet("kpis")]
public async Task<ActionResult<ParcelamentoKpisDto>> GetKpis([FromQuery] string? cliente)
{
var qLines = _db.ParcelamentoLines.AsNoTracking();
if (!string.IsNullOrWhiteSpace(cliente))
qLines = qLines.Where(x => x.Cliente == cliente);
var qMeses = _db.ParcelamentoMonthValues.AsNoTracking()
.Join(qLines, m => m.ParcelamentoLineId, l => l.Id, (m, l) => m);
var totalGeral = await qMeses.SumAsync(x => (decimal?)x.Valor) ?? 0m;
var linhas = await qLines.CountAsync();
var clientes = await qLines
.Where(x => x.Cliente != null && x.Cliente != "")
.Select(x => x.Cliente!)
.Distinct()
.CountAsync();
var minComp = await qMeses.MinAsync(x => (DateTime?)x.Competencia);
var maxComp = await qMeses.MaxAsync(x => (DateTime?)x.Competencia);
var now = DateTime.Now;
var mesAtual = new DateTime(now.Year, now.Month, 1);
var totalMesAtual = await qMeses
.Where(x => x.Competencia == mesAtual)
.SumAsync(x => (decimal?)x.Valor) ?? 0m;
return Ok(new ParcelamentoKpisDto
{
TotalGeral = totalGeral,
Linhas = linhas,
Clientes = clientes,
CompetenciaInicial = minComp?.ToString("yyyy-MM"),
CompetenciaFinal = maxComp?.ToString("yyyy-MM"),
MesAtual = mesAtual.ToString("yyyy-MM"),
TotalMesAtual = totalMesAtual
});
}
// =========================
// Série mensal (gráfico)
// =========================
[HttpGet("monthly")]
public async Task<ActionResult<List<ParcelamentoMonthlyTotalDto>>> GetMonthlyTotals(
[FromQuery] string? cliente,
[FromQuery] string? from, // yyyy-MM
[FromQuery] string? to // yyyy-MM
)
{
var qLines = _db.ParcelamentoLines.AsNoTracking();
if (!string.IsNullOrWhiteSpace(cliente))
qLines = qLines.Where(x => x.Cliente == cliente);
var qMeses = _db.ParcelamentoMonthValues.AsNoTracking()
.Join(qLines, m => m.ParcelamentoLineId, l => l.Id, (m, l) => m);
if (TryParseYm(from, out var fromDt))
qMeses = qMeses.Where(x => x.Competencia >= fromDt);
if (TryParseYm(to, out var toDt))
qMeses = qMeses.Where(x => x.Competencia <= toDt);
var data = await qMeses
.GroupBy(x => x.Competencia)
.OrderBy(g => g.Key)
.Select(g => new ParcelamentoMonthlyTotalDto
{
Competencia = g.Key.ToString("yyyy-MM"),
Total = g.Sum(x => x.Valor)
})
.ToListAsync();
return Ok(data);
}
// =========================
// Detalhe do mês (clique no gráfico)
// =========================
[HttpGet("month-details")]
public async Task<ActionResult<List<ParcelamentoMonthDetailDto>>> GetMonthDetails(
[FromQuery] string competencia, // yyyy-MM
[FromQuery] string? cliente
)
{
if (!TryParseYm(competencia, out var comp))
return BadRequest("competencia inválida. Use yyyy-MM (ex.: 2026-01).");
var qLines = _db.ParcelamentoLines.AsNoTracking();
if (!string.IsNullOrWhiteSpace(cliente))
qLines = qLines.Where(x => x.Cliente == cliente);
var data = await _db.ParcelamentoMonthValues.AsNoTracking()
.Where(x => x.Competencia == comp)
.Join(qLines, m => m.ParcelamentoLineId, l => l.Id, (m, l) => new ParcelamentoMonthDetailDto
{
Linha = l.Linha,
Cliente = l.Cliente,
Valor = m.Valor
})
.OrderByDescending(x => x.Valor)
.Take(200)
.ToListAsync();
return Ok(data);
}
// =========================
// Helpers
// =========================
private static bool TryParseYm(string? ym, out DateTime dt)
{
dt = default;
if (string.IsNullOrWhiteSpace(ym)) return false;
return DateTime.TryParseExact(ym.Trim(), "yyyy-MM", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);
}
}
}

View File

@ -24,10 +24,6 @@ public class AppDbContext : DbContext
public DbSet<TrocaNumeroLine> TrocaNumeroLines => Set<TrocaNumeroLine>(); public DbSet<TrocaNumeroLine> TrocaNumeroLines => Set<TrocaNumeroLine>();
// ✅ PARCELAMENTO
public DbSet<ParcelamentoLine> ParcelamentoLines => Set<ParcelamentoLine>();
public DbSet<ParcelamentoMonthValue> ParcelamentoMonthValues => Set<ParcelamentoMonthValue>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@ -73,48 +69,16 @@ public class AppDbContext : DbContext
}); });
// ========================================================== // ==========================================================
// ✅ PARCELAMENTO - MAPEAMENTO COMPLETO // ✅ VIGÊNCIA (se você quiser índices aqui também, pode manter)
// ========================================================== // ==========================================================
modelBuilder.Entity<ParcelamentoLine>(e => // modelBuilder.Entity<VigenciaLine>().HasIndex(x => x.Cliente);
{ // modelBuilder.Entity<VigenciaLine>().HasIndex(x => x.Linha);
// Nome físico fixo no Postgres
e.ToTable("parcelamento_lines");
e.HasKey(x => x.Id); // ==========================================================
// ✅ TROCA NÚMERO (opcional: índices)
e.Property(x => x.Linha).HasMaxLength(32); // ==========================================================
e.Property(x => x.Cliente).HasMaxLength(120); // modelBuilder.Entity<TrocaNumeroLine>().HasIndex(x => x.Cliente);
e.Property(x => x.QtParcelas).HasMaxLength(32); // modelBuilder.Entity<TrocaNumeroLine>().HasIndex(x => x.LinhaAntiga);
// modelBuilder.Entity<TrocaNumeroLine>().HasIndex(x => x.LinhaNova);
// índices úteis para filtro e performance
e.HasIndex(x => x.Cliente);
e.HasIndex(x => x.Linha);
e.HasIndex(x => x.AnoRef);
// se você quiser evitar duplicar importação da mesma linha/cliente/item:
// (deixa comentado pra não quebrar caso existam repetições legítimas)
// e.HasIndex(x => new { x.AnoRef, x.Item, x.Linha, x.Cliente }).IsUnique();
});
modelBuilder.Entity<ParcelamentoMonthValue>(e =>
{
e.ToTable("parcelamento_month_values");
e.HasKey(x => x.Id);
// relação 1:N (ParcelamentoLine -> Meses)
e.HasOne(x => x.ParcelamentoLine)
.WithMany(x => x.Meses)
.HasForeignKey(x => x.ParcelamentoLineId)
.OnDelete(DeleteBehavior.Cascade);
// índices para gráfico e consultas por mês
e.HasIndex(x => x.Competencia);
e.HasIndex(x => x.ParcelamentoLineId);
// garante 1 valor por mês por linha (evita duplicar mês)
e.HasIndex(x => new { x.ParcelamentoLineId, x.Competencia })
.IsUnique();
});
} }
} }

View File

@ -1,15 +0,0 @@
namespace line_gestao_api.Dtos
{
public class ParcelamentoKpisDto
{
public decimal TotalGeral { get; set; }
public int Linhas { get; set; }
public int Clientes { get; set; }
public string? CompetenciaInicial { get; set; } // "2025-12"
public string? CompetenciaFinal { get; set; } // "2027-06"
public string? MesAtual { get; set; } // "2026-01"
public decimal TotalMesAtual { get; set; }
}
}

View File

@ -1,9 +0,0 @@
namespace line_gestao_api.Dtos
{
public class ParcelamentoMonthDetailDto
{
public string? Linha { get; set; }
public string? Cliente { get; set; }
public decimal Valor { get; set; }
}
}

View File

@ -1,8 +0,0 @@
namespace line_gestao_api.Dtos
{
public class ParcelamentoMonthlyTotalDto
{
public string Competencia { get; set; } = ""; // "2026-01"
public decimal Total { get; set; }
}
}

View File

@ -0,0 +1,422 @@
// <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("20260112191043_RemoveParcelamento")]
partial class RemoveParcelamento
{
/// <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.BillingClient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Aparelho")
.HasColumnType("text");
b.Property<string>("Cliente")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FormaPagamento")
.HasColumnType("text");
b.Property<decimal?>("FranquiaLine")
.HasColumnType("numeric");
b.Property<decimal?>("FranquiaVivo")
.HasColumnType("numeric");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<decimal?>("Lucro")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<string>("Tipo")
.IsRequired()
.HasMaxLength(2)
.HasColumnType("character varying(2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorContratoLine")
.HasColumnType("numeric");
b.Property<decimal?>("ValorContratoVivo")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("Item");
b.HasIndex("Tipo");
b.HasIndex("Tipo", "Cliente");
b.ToTable("billing_clients", (string)null);
});
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.MuregLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDaMureg")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("ICCID");
b.HasIndex("Item");
b.HasIndex("LinhaNova");
b.ToTable("MuregLines");
});
modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataTroca")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<string>("Motivo")
.HasColumnType("text");
b.Property<string>("Observacao")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("TrocaNumeroLines");
});
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");
});
modelBuilder.Entity("line_gestao_api.Models.UserData", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Celular")
.HasColumnType("text");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Cpf")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataNascimento")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<string>("Endereco")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("Rg")
.HasColumnType("text");
b.Property<string>("TelefoneFixo")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("UserDatas");
});
modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Conta")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtEfetivacaoServico")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtTerminoFidelizacao")
.HasColumnType("timestamp with time zone");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("PlanoContrato")
.HasColumnType("text");
b.Property<decimal?>("Total")
.HasColumnType("numeric");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Usuario")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("VigenciaLines");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class RemoveParcelamento : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "parcelamento_month_values");
migrationBuilder.DropTable(
name: "parcelamento_lines");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "parcelamento_lines",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
AnoRef = table.Column<int>(type: "integer", nullable: true),
Cliente = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Desconto = table.Column<decimal>(type: "numeric", nullable: true),
Item = table.Column<int>(type: "integer", nullable: true),
Linha = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
QtParcelas = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ValorCheio = table.Column<decimal>(type: "numeric", nullable: true),
ValorComDesconto = table.Column<decimal>(type: "numeric", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_parcelamento_lines", x => x.Id);
});
migrationBuilder.CreateTable(
name: "parcelamento_month_values",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ParcelamentoLineId = table.Column<Guid>(type: "uuid", nullable: false),
Competencia = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Valor = table.Column<decimal>(type: "numeric", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_parcelamento_month_values", x => x.Id);
table.ForeignKey(
name: "FK_parcelamento_month_values_parcelamento_lines_ParcelamentoLi~",
column: x => x.ParcelamentoLineId,
principalTable: "parcelamento_lines",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_parcelamento_lines_AnoRef",
table: "parcelamento_lines",
column: "AnoRef");
migrationBuilder.CreateIndex(
name: "IX_parcelamento_lines_Cliente",
table: "parcelamento_lines",
column: "Cliente");
migrationBuilder.CreateIndex(
name: "IX_parcelamento_lines_Linha",
table: "parcelamento_lines",
column: "Linha");
migrationBuilder.CreateIndex(
name: "IX_parcelamento_month_values_Competencia",
table: "parcelamento_month_values",
column: "Competencia");
migrationBuilder.CreateIndex(
name: "IX_parcelamento_month_values_ParcelamentoLineId",
table: "parcelamento_month_values",
column: "ParcelamentoLineId");
migrationBuilder.CreateIndex(
name: "IX_parcelamento_month_values_ParcelamentoLineId_Competencia",
table: "parcelamento_month_values",
columns: new[] { "ParcelamentoLineId", "Competencia" },
unique: true);
}
}
}

View File

@ -0,0 +1,513 @@
// <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("20260112205658_AddParcelamentoTables")]
partial class AddParcelamentoTables
{
/// <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.BillingClient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Aparelho")
.HasColumnType("text");
b.Property<string>("Cliente")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FormaPagamento")
.HasColumnType("text");
b.Property<decimal?>("FranquiaLine")
.HasColumnType("numeric");
b.Property<decimal?>("FranquiaVivo")
.HasColumnType("numeric");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<decimal?>("Lucro")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<string>("Tipo")
.IsRequired()
.HasMaxLength(2)
.HasColumnType("character varying(2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorContratoLine")
.HasColumnType("numeric");
b.Property<decimal?>("ValorContratoVivo")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("Item");
b.HasIndex("Tipo");
b.HasIndex("Tipo", "Cliente");
b.ToTable("billing_clients", (string)null);
});
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.MuregLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDaMureg")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("ICCID");
b.HasIndex("Item");
b.HasIndex("LinhaNova");
b.ToTable("MuregLines");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("Ano")
.HasColumnType("integer");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Desconto")
.HasColumnType("numeric");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("QtParcelas")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorCheio")
.HasColumnType("numeric");
b.Property<decimal?>("ValorComDesconto")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("Linha");
b.ToTable("ParcelamentoLines");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoMonthValue", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("Ano")
.HasColumnType("integer");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Mes")
.HasColumnType("integer");
b.Property<Guid>("ParcelamentoLineId")
.HasColumnType("uuid");
b.Property<decimal>("Valor")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("ParcelamentoLineId");
b.HasIndex("Ano", "Mes");
b.ToTable("ParcelamentoMonthValues");
});
modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataTroca")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<string>("Motivo")
.HasColumnType("text");
b.Property<string>("Observacao")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("TrocaNumeroLines");
});
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");
});
modelBuilder.Entity("line_gestao_api.Models.UserData", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Celular")
.HasColumnType("text");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Cpf")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataNascimento")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<string>("Endereco")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("Rg")
.HasColumnType("text");
b.Property<string>("TelefoneFixo")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("UserDatas");
});
modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Conta")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtEfetivacaoServico")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtTerminoFidelizacao")
.HasColumnType("timestamp with time zone");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("PlanoContrato")
.HasColumnType("text");
b.Property<decimal?>("Total")
.HasColumnType("numeric");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Usuario")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("VigenciaLines");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoMonthValue", b =>
{
b.HasOne("line_gestao_api.Models.ParcelamentoLine", "ParcelamentoLine")
.WithMany("MonthValues")
.HasForeignKey("ParcelamentoLineId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ParcelamentoLine");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoLine", b =>
{
b.Navigation("MonthValues");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddParcelamentoTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ParcelamentoLines",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Ano = table.Column<int>(type: "integer", nullable: false),
Item = table.Column<int>(type: "integer", nullable: false),
Linha = table.Column<string>(type: "text", nullable: true),
Cliente = table.Column<string>(type: "text", nullable: true),
QtParcelas = table.Column<string>(type: "text", nullable: true),
ValorCheio = table.Column<decimal>(type: "numeric", nullable: true),
Desconto = table.Column<decimal>(type: "numeric", nullable: true),
ValorComDesconto = table.Column<decimal>(type: "numeric", 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_ParcelamentoLines", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ParcelamentoMonthValues",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ParcelamentoLineId = table.Column<Guid>(type: "uuid", nullable: false),
Ano = table.Column<int>(type: "integer", nullable: false),
Mes = table.Column<int>(type: "integer", nullable: false),
Valor = table.Column<decimal>(type: "numeric", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ParcelamentoMonthValues", x => x.Id);
table.ForeignKey(
name: "FK_ParcelamentoMonthValues_ParcelamentoLines_ParcelamentoLineId",
column: x => x.ParcelamentoLineId,
principalTable: "ParcelamentoLines",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_Cliente",
table: "ParcelamentoLines",
column: "Cliente");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_Linha",
table: "ParcelamentoLines",
column: "Linha");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_Ano_Mes",
table: "ParcelamentoMonthValues",
columns: new[] { "Ano", "Mes" });
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_ParcelamentoLineId",
table: "ParcelamentoMonthValues",
column: "ParcelamentoLineId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ParcelamentoMonthValues");
migrationBuilder.DropTable(
name: "ParcelamentoLines");
}
}
}

View File

@ -0,0 +1,422 @@
// <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("20260113133045_RemoveParcelamentoV2")]
partial class RemoveParcelamentoV2
{
/// <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.BillingClient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Aparelho")
.HasColumnType("text");
b.Property<string>("Cliente")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FormaPagamento")
.HasColumnType("text");
b.Property<decimal?>("FranquiaLine")
.HasColumnType("numeric");
b.Property<decimal?>("FranquiaVivo")
.HasColumnType("numeric");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<decimal?>("Lucro")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<string>("Tipo")
.IsRequired()
.HasMaxLength(2)
.HasColumnType("character varying(2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorContratoLine")
.HasColumnType("numeric");
b.Property<decimal?>("ValorContratoVivo")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("Item");
b.HasIndex("Tipo");
b.HasIndex("Tipo", "Cliente");
b.ToTable("billing_clients", (string)null);
});
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.MuregLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDaMureg")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Cliente");
b.HasIndex("ICCID");
b.HasIndex("Item");
b.HasIndex("LinhaNova");
b.ToTable("MuregLines");
});
modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataTroca")
.HasColumnType("timestamp with time zone");
b.Property<string>("ICCID")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("LinhaAntiga")
.HasColumnType("text");
b.Property<string>("LinhaNova")
.HasColumnType("text");
b.Property<string>("Motivo")
.HasColumnType("text");
b.Property<string>("Observacao")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("TrocaNumeroLines");
});
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");
});
modelBuilder.Entity("line_gestao_api.Models.UserData", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Celular")
.HasColumnType("text");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Cpf")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataNascimento")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<string>("Endereco")
.HasColumnType("text");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("Rg")
.HasColumnType("text");
b.Property<string>("TelefoneFixo")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("UserDatas");
});
modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Cliente")
.HasColumnType("text");
b.Property<string>("Conta")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtEfetivacaoServico")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DtTerminoFidelizacao")
.HasColumnType("timestamp with time zone");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasColumnType("text");
b.Property<string>("PlanoContrato")
.HasColumnType("text");
b.Property<decimal?>("Total")
.HasColumnType("numeric");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Usuario")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("VigenciaLines");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class RemoveParcelamentoV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ParcelamentoMonthValues");
migrationBuilder.DropTable(
name: "ParcelamentoLines");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ParcelamentoLines",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Ano = table.Column<int>(type: "integer", nullable: false),
Cliente = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Desconto = table.Column<decimal>(type: "numeric", nullable: true),
Item = table.Column<int>(type: "integer", nullable: false),
Linha = table.Column<string>(type: "text", nullable: true),
QtParcelas = table.Column<string>(type: "text", nullable: true),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ValorCheio = table.Column<decimal>(type: "numeric", nullable: true),
ValorComDesconto = table.Column<decimal>(type: "numeric", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ParcelamentoLines", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ParcelamentoMonthValues",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ParcelamentoLineId = table.Column<Guid>(type: "uuid", nullable: false),
Ano = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Mes = table.Column<int>(type: "integer", nullable: false),
Valor = table.Column<decimal>(type: "numeric", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ParcelamentoMonthValues", x => x.Id);
table.ForeignKey(
name: "FK_ParcelamentoMonthValues_ParcelamentoLines_ParcelamentoLineId",
column: x => x.ParcelamentoLineId,
principalTable: "ParcelamentoLines",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_Cliente",
table: "ParcelamentoLines",
column: "Cliente");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_Linha",
table: "ParcelamentoLines",
column: "Linha");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_Ano_Mes",
table: "ParcelamentoMonthValues",
columns: new[] { "Ano", "Mes" });
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_ParcelamentoLineId",
table: "ParcelamentoMonthValues",
column: "ParcelamentoLineId");
}
}
}

View File

@ -249,80 +249,6 @@ namespace line_gestao_api.Migrations
b.ToTable("MuregLines"); b.ToTable("MuregLines");
}); });
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int?>("AnoRef")
.HasColumnType("integer");
b.Property<string>("Cliente")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Desconto")
.HasColumnType("numeric");
b.Property<int?>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("QtParcelas")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<decimal?>("ValorCheio")
.HasColumnType("numeric");
b.Property<decimal?>("ValorComDesconto")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("AnoRef");
b.HasIndex("Cliente");
b.HasIndex("Linha");
b.ToTable("parcelamento_lines", (string)null);
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoMonthValue", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Competencia")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("ParcelamentoLineId")
.HasColumnType("uuid");
b.Property<decimal>("Valor")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Competencia");
b.HasIndex("ParcelamentoLineId");
b.HasIndex("ParcelamentoLineId", "Competencia")
.IsUnique();
b.ToTable("parcelamento_month_values", (string)null);
});
modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b => modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -487,22 +413,6 @@ namespace line_gestao_api.Migrations
b.ToTable("VigenciaLines"); b.ToTable("VigenciaLines");
}); });
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoMonthValue", b =>
{
b.HasOne("line_gestao_api.Models.ParcelamentoLine", "ParcelamentoLine")
.WithMany("Meses")
.HasForeignKey("ParcelamentoLineId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ParcelamentoLine");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoLine", b =>
{
b.Navigation("Meses");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -1,30 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace line_gestao_api.Models
{
public class ParcelamentoLine
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
public int? AnoRef { get; set; } // coluna "2025" (primeira coluna numérica)
public int? Item { get; set; } // coluna do item (1,2,3...)
[MaxLength(32)]
public string? Linha { get; set; }
[MaxLength(120)]
public string? Cliente { get; set; }
[MaxLength(32)]
public string? QtParcelas { get; set; } // exemplo "06/24"
public decimal? ValorCheio { get; set; }
public decimal? Desconto { get; set; }
public decimal? ValorComDesconto { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public List<ParcelamentoMonthValue> Meses { get; set; } = new();
}
}

View File

@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace line_gestao_api.Models
{
public class ParcelamentoMonthValue
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[ForeignKey(nameof(ParcelamentoLine))]
public Guid ParcelamentoLineId { get; set; }
public ParcelamentoLine? ParcelamentoLine { get; set; }
// Competência (sempre dia 01)
public DateTime Competencia { get; set; }
public decimal Valor { get; set; }
}
}

View File

@ -5,6 +5,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>line_gestao_api</RootNamespace> <RootNamespace>line_gestao_api</RootNamespace>
<!-- ✅ evita gerar/rodar o .exe (apphost) que o Windows está bloqueando -->
<UseAppHost>false</UseAppHost>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>