diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index aa11a3c..7c4b97d 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -203,6 +203,39 @@ namespace line_gestao_api.Controllers return Ok(clients); } + // ========================================================== + // ✅ 2.1 ENDPOINT: LINHAS POR CLIENTE (para SELECT do MUREG) + // GET: /api/lines/by-client?cliente=... + // ========================================================== + [HttpGet("by-client")] + public async Task>> GetLinesByClient([FromQuery] string cliente) + { + if (string.IsNullOrWhiteSpace(cliente)) + return Ok(new List()); + + var c = cliente.Trim(); + + // ⚠️ use ILike para não depender de maiúscula/minúscula + var items = await _db.MobileLines + .AsNoTracking() + .Where(x => x.Cliente != null && EF.Functions.ILike(x.Cliente, c)) + .Where(x => x.Linha != null && x.Linha != "") + .OrderBy(x => x.Item) + .Select(x => new LineOptionDto + { + Id = x.Id, + Item = x.Item, + Linha = x.Linha, + Chip = x.Chip, + Cliente = x.Cliente, + Usuario = x.Usuario, + Skil = x.Skil + }) + .ToListAsync(); + + return Ok(items); + } + // ========================================================== // ✅ 3. GET ALL (GERAL) // ========================================================== @@ -464,10 +497,7 @@ namespace line_gestao_api.Controllers } // ========================================================== - // ✅ 8. IMPORT EXCEL (GERAL + MUREG + FATURAMENTO + DADOS USUÁRIOS + VIGÊNCIA + TROCA DE NÚMERO) - // - // ✅ 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) + // ✅ 8. IMPORT EXCEL // ========================================================== [HttpPost("import-excel")] [Consumes("multipart/form-data")] @@ -580,7 +610,7 @@ namespace line_gestao_api.Controllers } // ========================= - // ✅ IMPORTA MUREG + // ✅ IMPORTA MUREG (ALTERADO: NÃO ESTOURA ERRO SE LINHANOVA JÁ EXISTIR) // ========================= await ImportMuregFromWorkbook(wb); @@ -616,6 +646,10 @@ namespace line_gestao_api.Controllers // ========================================================== // ✅ IMPORTAÇÃO DA ABA MUREG + // ✅ NOVA REGRA: + // - Se LinhaNova já existir em OUTRA linha da GERAL => NÃO atualiza a GERAL, NÃO dá erro + // - Mesmo assim salva o registro na MUREG normalmente + // - Evita duplicidade na coluna Linha da GERAL // ========================================================== private async Task ImportMuregFromWorkbook(XLWorkbook wb) { @@ -634,8 +668,38 @@ namespace line_gestao_api.Controllers var startRow = headerRow.RowNumber() + 1; + // limpa MUREG antes (idempotente) await _db.MuregLines.ExecuteDeleteAsync(); + // ✅ dicionários para resolver MobileLineId por Linha/Chip + var mobilePairs = await _db.MobileLines + .AsNoTracking() + .Select(x => new { x.Id, x.Linha, x.Chip }) + .ToListAsync(); + + var mobileByLinha = new Dictionary(StringComparer.Ordinal); + var mobileByChip = new Dictionary(StringComparer.Ordinal); + + foreach (var m in mobilePairs) + { + if (!string.IsNullOrWhiteSpace(m.Linha)) + { + var k = OnlyDigits(m.Linha); + if (!string.IsNullOrWhiteSpace(k) && !mobileByLinha.ContainsKey(k)) + mobileByLinha[k] = m.Id; + } + + if (!string.IsNullOrWhiteSpace(m.Chip)) + { + var k = OnlyDigits(m.Chip); + if (!string.IsNullOrWhiteSpace(k) && !mobileByChip.ContainsKey(k)) + mobileByChip[k] = m.Id; + } + } + + // ✅ cache de entidades tracked para atualizar a GERAL sem consultar toda hora + var mobileCache = new Dictionary(); + var buffer = new List(600); var lastRow = wsM.LastRowUsed()?.RowNumber() ?? startRow; @@ -644,19 +708,100 @@ namespace line_gestao_api.Controllers var itemStr = GetCellString(wsM, r, colItem); if (string.IsNullOrWhiteSpace(itemStr)) break; + var linhaAntiga = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA")); + var linhaNova = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA")); + var iccid = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "ICCID")); + var dataMureg = TryDate(wsM, r, map, "DATA DA MUREG"); + + // ✅ resolve MobileLineId (prioridade: LinhaAntiga, depois ICCID) + Guid mobileLineId = Guid.Empty; + + if (!string.IsNullOrWhiteSpace(linhaAntiga) && mobileByLinha.TryGetValue(linhaAntiga, out var idPorLinha)) + mobileLineId = idPorLinha; + else if (!string.IsNullOrWhiteSpace(iccid) && mobileByChip.TryGetValue(iccid, out var idPorChip)) + mobileLineId = idPorChip; + + // Se não encontrou correspondência na GERAL, não dá pra salvar (MobileLineId é obrigatório) + if (mobileLineId == Guid.Empty) + continue; + + // ✅ snapshot da linha antiga: se vier vazia na planilha, pega a linha atual da GERAL + string? linhaAntigaSnapshot = linhaAntiga; + if (string.IsNullOrWhiteSpace(linhaAntigaSnapshot)) + { + if (!mobileCache.TryGetValue(mobileLineId, out var mobTmp)) + { + mobTmp = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == mobileLineId); + if (mobTmp != null) mobileCache[mobileLineId] = mobTmp; + } + + linhaAntigaSnapshot = mobTmp?.Linha; + } + + var now = DateTime.UtcNow; + + // ✅ salva MUREG sempre var e = new MuregLine { Id = Guid.NewGuid(), Item = TryInt(itemStr), - LinhaAntiga = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA")), - LinhaNova = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA")), - ICCID = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "ICCID")), - DataDaMureg = TryDate(wsM, r, map, "DATA DA MUREG"), - Cliente = GetCellByHeader(wsM, r, map, "CLIENTE"), + MobileLineId = mobileLineId, + LinhaAntiga = linhaAntigaSnapshot, + LinhaNova = linhaNova, + ICCID = iccid, + DataDaMureg = dataMureg, + CreatedAt = now, + UpdatedAt = now }; buffer.Add(e); + // ✅ REFLETE NA GERAL (somente se NÃO houver conflito) + if (!string.IsNullOrWhiteSpace(linhaNova)) + { + // Se LinhaNova já existe na GERAL em OUTRA MobileLine => ignora update (não duplica) + if (mobileByLinha.TryGetValue(linhaNova, out var idJaExiste) && idJaExiste != mobileLineId) + { + // ignora update da GERAL + } + else + { + // carrega entity tracked (cache) e atualiza + if (!mobileCache.TryGetValue(mobileLineId, out var mobile)) + { + mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == mobileLineId); + if (mobile != null) mobileCache[mobileLineId] = mobile; + } + + if (mobile != null) + { + // valida conflito de ICCID também (evita duplicidade de CHIP) + var iccidConflita = false; + if (!string.IsNullOrWhiteSpace(iccid) && + mobileByChip.TryGetValue(iccid, out var chipJaExiste) && + chipJaExiste != mobileLineId) + { + iccidConflita = true; + } + + // atualiza Linha + mobile.Linha = linhaNova; + + // atualiza Chip se ICCID vier e NÃO conflitar + if (!string.IsNullOrWhiteSpace(iccid) && !iccidConflita) + mobile.Chip = iccid; + + mobile.UpdatedAt = DateTime.UtcNow; + + // atualiza os dicionários para próximas linhas do MUREG + mobileByLinha[linhaNova] = mobileLineId; + + if (!string.IsNullOrWhiteSpace(iccid) && !iccidConflita) + mobileByChip[iccid] = mobileLineId; + } + } + } + if (buffer.Count >= 500) { await _db.MuregLines.AddRangeAsync(buffer); @@ -1159,7 +1304,7 @@ namespace line_gestao_api.Controllers } // ========================================================== - // HELPERS (SEUS - + AJUSTES) + // HELPERS (SEUS) // ========================================================== private static Dictionary BuildHeaderMap(IXLRow headerRow) { diff --git a/Controllers/MuregController.cs b/Controllers/MuregController.cs index d7c40b5..b0b5492 100644 --- a/Controllers/MuregController.cs +++ b/Controllers/MuregController.cs @@ -23,11 +23,13 @@ namespace line_gestao_api.Controllers } // ========================================================== - // ✅ GET: /api/mureg (com paginação, busca e ordenação) + // ✅ GET: /api/mureg (paginação, busca e ordenação) + // Cliente vem da GERAL (MobileLines) // ========================================================== [HttpGet] public async Task>> GetAll( [FromQuery] string? search, + [FromQuery] string? client, [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? sortBy = "item", @@ -36,7 +38,16 @@ namespace line_gestao_api.Controllers page = page < 1 ? 1 : page; pageSize = pageSize < 1 ? 10 : pageSize; - var q = _db.MuregLines.AsNoTracking(); + var q = _db.MuregLines + .AsNoTracking() + .Include(x => x.MobileLine) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(client)) + { + var c = client.Trim(); + q = q.Where(x => EF.Functions.ILike((x.MobileLine.Cliente ?? ""), c)); + } if (!string.IsNullOrWhiteSpace(search)) { @@ -45,7 +56,9 @@ namespace line_gestao_api.Controllers EF.Functions.ILike((x.LinhaAntiga ?? ""), $"%{s}%") || EF.Functions.ILike((x.LinhaNova ?? ""), $"%{s}%") || EF.Functions.ILike((x.ICCID ?? ""), $"%{s}%") || - EF.Functions.ILike((x.Cliente ?? ""), $"%{s}%") || + EF.Functions.ILike((x.MobileLine.Cliente ?? ""), $"%{s}%") || + EF.Functions.ILike((x.MobileLine.Usuario ?? ""), $"%{s}%") || + EF.Functions.ILike((x.MobileLine.Skil ?? ""), $"%{s}%") || EF.Functions.ILike(x.Item.ToString(), $"%{s}%")); } @@ -61,7 +74,7 @@ namespace line_gestao_api.Controllers "linhanova" => desc ? q.OrderByDescending(x => x.LinhaNova ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.LinhaNova ?? "").ThenBy(x => x.Item), "iccid" => desc ? q.OrderByDescending(x => x.ICCID ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.ICCID ?? "").ThenBy(x => x.Item), "datadamureg" => desc ? q.OrderByDescending(x => x.DataDaMureg).ThenBy(x => x.Item) : q.OrderBy(x => x.DataDaMureg).ThenBy(x => x.Item), - "cliente" => desc ? q.OrderByDescending(x => x.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente ?? "").ThenBy(x => x.Item), + "cliente" => desc ? q.OrderByDescending(x => x.MobileLine.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.MobileLine.Cliente ?? "").ThenBy(x => x.Item), _ => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item) }; @@ -76,7 +89,8 @@ namespace line_gestao_api.Controllers LinhaNova = x.LinhaNova, ICCID = x.ICCID, DataDaMureg = x.DataDaMureg, - Cliente = x.Cliente + Cliente = x.MobileLine.Cliente, + MobileLineId = x.MobileLineId }) .ToListAsync(); @@ -90,17 +104,279 @@ namespace line_gestao_api.Controllers } // ========================================================== - // ✅ POST: /api/mureg/import-excel (opcional) - // Se você usar o botão "Importar" da tela MUREG, ele vai bater aqui + // ✅ GET: /api/mureg/{id} + // Detalhe para modal (puxa dados da GERAL) + // ========================================================== + [HttpGet("{id:guid}")] + public async Task> GetById(Guid id) + { + var x = await _db.MuregLines + .AsNoTracking() + .Include(a => a.MobileLine) + .FirstOrDefaultAsync(a => a.Id == id); + + if (x == null) return NotFound(); + + return Ok(new MuregDetailDto + { + Id = x.Id, + Item = x.Item, + LinhaAntiga = x.LinhaAntiga, + LinhaNova = x.LinhaNova, + ICCID = x.ICCID, + DataDaMureg = x.DataDaMureg, + MobileLineId = x.MobileLineId, + + Cliente = x.MobileLine?.Cliente, + Usuario = x.MobileLine?.Usuario, + Skil = x.MobileLine?.Skil, + LinhaAtualNaGeral = x.MobileLine?.Linha, + ChipNaGeral = x.MobileLine?.Chip, + ContaNaGeral = x.MobileLine?.Conta, + StatusNaGeral = x.MobileLine?.Status + }); + } + + // ========================================================== + // ✅ GET: /api/mureg/clients (filtro no front) + // ========================================================== + [HttpGet("clients")] + public async Task>> GetClients() + { + var clients = await _db.MuregLines + .AsNoTracking() + .Include(x => x.MobileLine) + .Where(x => x.MobileLine.Cliente != null && x.MobileLine.Cliente != "") + .Select(x => x.MobileLine.Cliente!) + .Distinct() + .OrderBy(x => x) + .ToListAsync(); + + return Ok(clients); + } + + // ========================================================== + // ✅ POST: /api/mureg + // Cria MUREG manualmente (usando MobileLineId) + // - MobileLines (GERAL) prevalece: Cliente/Usuário/Skil etc vêm da GERAL + // - Se LinhaNova vier, reflete na GERAL (Linha e Chip=ICCID se vier) + // ========================================================== + public class CreateMuregDto + { + public int Item { get; set; } + public Guid MobileLineId { get; set; } + + public string? LinhaAntiga { get; set; } // opcional (snapshot) + public string? LinhaNova { get; set; } // opcional + public string? ICCID { get; set; } // opcional + public DateTime? DataDaMureg { get; set; } // opcional + } + + [HttpPost] + public async Task> Create([FromBody] CreateMuregDto req) + { + if (req.MobileLineId == Guid.Empty) + return BadRequest(new { message = "mobileLineId é obrigatório." }); + + // linha canônica (GERAL) + var mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == req.MobileLineId); + if (mobile == null) + return BadRequest(new { message = "MobileLine não encontrada (mobileLineId inválido)." }); + + // normaliza números + string? linhaNova = NullIfEmptyDigits(req.LinhaNova); + string? iccid = NullIfEmptyDigits(req.ICCID); + string? linhaAntiga = NullIfEmptyDigits(req.LinhaAntiga); + + // snapshot: se LinhaAntiga não veio, captura da GERAL + var linhaAntigaSnapshot = !string.IsNullOrWhiteSpace(linhaAntiga) ? linhaAntiga : mobile.Linha; + + // conflito: LinhaNova já existe em outra MobileLine + if (!string.IsNullOrWhiteSpace(linhaNova)) + { + var exists = await _db.MobileLines.AsNoTracking() + .AnyAsync(x => x.Linha == linhaNova && x.Id != mobile.Id); + + if (exists) + return Conflict(new { message = $"Conflito: a LinhaNova {linhaNova} já existe em outra linha da GERAL." }); + } + + // item: se não vier, gera sequencial + int item = req.Item; + if (item <= 0) + { + var maxItem = await _db.MuregLines.MaxAsync(x => (int?)x.Item) ?? 0; + item = maxItem + 1; + } + + var now = DateTime.UtcNow; + + var entity = new MuregLine + { + Id = Guid.NewGuid(), + Item = item, + MobileLineId = mobile.Id, + LinhaAntiga = linhaAntigaSnapshot, + LinhaNova = linhaNova, + ICCID = iccid, + DataDaMureg = ToUtc(req.DataDaMureg), + CreatedAt = now, + UpdatedAt = now + }; + + _db.MuregLines.Add(entity); + + // ✅ reflete na GERAL (prevalece) + if (!string.IsNullOrWhiteSpace(linhaNova)) + mobile.Linha = linhaNova; + + if (!string.IsNullOrWhiteSpace(iccid)) + mobile.Chip = iccid; + + mobile.UpdatedAt = DateTime.UtcNow; + + await _db.SaveChangesAsync(); + + // devolve detalhe + return CreatedAtAction(nameof(GetById), new { id = entity.Id }, new MuregDetailDto + { + Id = entity.Id, + Item = entity.Item, + LinhaAntiga = entity.LinhaAntiga, + LinhaNova = entity.LinhaNova, + ICCID = entity.ICCID, + DataDaMureg = entity.DataDaMureg, + MobileLineId = entity.MobileLineId, + + Cliente = mobile.Cliente, + Usuario = mobile.Usuario, + Skil = mobile.Skil, + LinhaAtualNaGeral = mobile.Linha, + ChipNaGeral = mobile.Chip, + ContaNaGeral = mobile.Conta, + StatusNaGeral = mobile.Status + }); + } + + // ========================================================== + // ✅ PUT: /api/mureg/{id} + // Atualiza o registro MUREG e (se LinhaNova/ICCID vierem) reflete na GERAL + // ========================================================== + public class UpdateMuregDto + { + public int? Item { get; set; } + public Guid? MobileLineId { get; set; } // opcional mudar vínculo + + public string? LinhaAntiga { get; set; } + public string? LinhaNova { get; set; } + public string? ICCID { get; set; } + public DateTime? DataDaMureg { get; set; } + } + + [HttpPut("{id:guid}")] + public async Task Update(Guid id, [FromBody] UpdateMuregDto req) + { + var entity = await _db.MuregLines.FirstOrDefaultAsync(x => x.Id == id); + if (entity == null) return NotFound(); + + // troca vínculo (se mandou) + if (req.MobileLineId.HasValue && req.MobileLineId.Value != Guid.Empty && req.MobileLineId.Value != entity.MobileLineId) + { + var mobileNew = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == req.MobileLineId.Value); + if (mobileNew == null) + return BadRequest(new { message = "MobileLineId inválido." }); + + entity.MobileLineId = mobileNew.Id; + } + + // carrega MobileLine atual vinculada + var mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == entity.MobileLineId); + if (mobile == null) + return BadRequest(new { message = "MobileLine vinculada não encontrada." }); + + // normaliza + string? linhaNova = req.LinhaNova != null ? NullIfEmptyDigits(req.LinhaNova) : null; + string? iccid = req.ICCID != null ? NullIfEmptyDigits(req.ICCID) : null; + string? linhaAntiga = req.LinhaAntiga != null ? NullIfEmptyDigits(req.LinhaAntiga) : null; + + // item + if (req.Item.HasValue && req.Item.Value > 0) + entity.Item = req.Item.Value; + + // snapshot linha antiga (se enviar null/"" não mexe; se enviar valor, atualiza) + if (req.LinhaAntiga != null) + entity.LinhaAntiga = string.IsNullOrWhiteSpace(linhaAntiga) ? entity.LinhaAntiga : linhaAntiga; + + if (req.DataDaMureg != null) + entity.DataDaMureg = ToUtc(req.DataDaMureg); + + if (req.LinhaNova != null) + entity.LinhaNova = linhaNova; // pode virar null + + if (req.ICCID != null) + entity.ICCID = iccid; // pode virar null + + // conflito de LinhaNova na GERAL + if (!string.IsNullOrWhiteSpace(linhaNova)) + { + var exists = await _db.MobileLines.AsNoTracking() + .AnyAsync(x => x.Linha == linhaNova && x.Id != mobile.Id); + + if (exists) + return Conflict(new { message = $"Conflito: a LinhaNova {linhaNova} já existe em outra linha da GERAL." }); + } + + // ✅ reflete na GERAL (se o usuário mandou esses campos) + if (req.LinhaNova != null && !string.IsNullOrWhiteSpace(linhaNova)) + mobile.Linha = linhaNova; + + if (req.ICCID != null && !string.IsNullOrWhiteSpace(iccid)) + mobile.Chip = iccid; + + entity.UpdatedAt = DateTime.UtcNow; + mobile.UpdatedAt = DateTime.UtcNow; + + await _db.SaveChangesAsync(); + return NoContent(); + } + + // ========================================================== + // ✅ POST: /api/mureg/import-excel (mantido) // ========================================================== [HttpPost("import-excel")] [Consumes("multipart/form-data")] [RequestSizeLimit(50_000_000)] public async Task ImportExcel([FromForm] ImportExcelForm form) { - // Se você quiser manter "importa só no GERAL", pode remover este endpoint. - // Eu deixei para não quebrar o botão do seu front (que chama /api/mureg/import-excel). - return BadRequest(new { message = "Importe a planilha pela página GERAL. O MUREG será carregado automaticamente." }); + return BadRequest(new + { + message = "Importe a planilha pela página GERAL. O MUREG será carregado automaticamente." + }); + } + + // ========================================================== + // HELPERS (iguais ao LinesController, só o que precisamos aqui) + // ========================================================== + 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 string OnlyDigits(string? s) + { + if (string.IsNullOrWhiteSpace(s)) return ""; + var sb = new System.Text.StringBuilder(); + foreach (var c in s) if (char.IsDigit(c)) sb.Append(c); + return sb.ToString(); + } + + private static string? NullIfEmptyDigits(string? s) + { + var d = OnlyDigits(s); + return string.IsNullOrWhiteSpace(d) ? null : d; } } } diff --git a/Controllers/RelatoriosController.cs b/Controllers/RelatoriosController.cs new file mode 100644 index 0000000..ff10924 --- /dev/null +++ b/Controllers/RelatoriosController.cs @@ -0,0 +1,320 @@ +using line_gestao_api.Data; +using line_gestao_api.Dtos; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace line_gestao_api.Controllers +{ + [ApiController] + [Route("api/relatorios")] + public class RelatoriosController : ControllerBase + { + private readonly AppDbContext _db; + + public RelatoriosController(AppDbContext db) + { + _db = db; + } + + [HttpGet("dashboard")] + public async Task> GetDashboard() + { + var today = DateTime.UtcNow.Date; + var last30 = today.AddDays(-30); + var limit30 = today.AddDays(30); + + var minUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + // ========================= + // GERAL (MobileLines) + // ========================= + var qLines = _db.MobileLines.AsNoTracking(); + + var totalLinhas = await qLines.CountAsync(); + + var clientesUnicos = await qLines + .Where(x => x.Cliente != null && x.Cliente != "") + .Select(x => x.Cliente!) + .Distinct() + .CountAsync(); + + var ativos = await qLines.CountAsync(x => + EF.Functions.ILike((x.Status ?? "").Trim(), "%ativo%")); + + var bloqueadosPerdaRoubo = await qLines.CountAsync(x => + EF.Functions.ILike((x.Status ?? "").Trim(), "%perda%") || + EF.Functions.ILike((x.Status ?? "").Trim(), "%roubo%")); + + var bloqueados120Dias = await qLines.CountAsync(x => + EF.Functions.ILike((x.Status ?? "").Trim(), "%bloque%") && + EF.Functions.ILike((x.Status ?? "").Trim(), "%120%") && + EF.Functions.ILike((x.Status ?? "").Trim(), "%dia%")); + + var bloqueadosOutros = await qLines.CountAsync(x => + EF.Functions.ILike((x.Status ?? "").Trim(), "%bloque%") && + !(EF.Functions.ILike((x.Status ?? "").Trim(), "%120%") && EF.Functions.ILike((x.Status ?? "").Trim(), "%dia%")) && + !(EF.Functions.ILike((x.Status ?? "").Trim(), "%perda%") || EF.Functions.ILike((x.Status ?? "").Trim(), "%roubo%")) + ); + + var bloqueados = bloqueadosPerdaRoubo + bloqueados120Dias + bloqueadosOutros; + + var reservas = await qLines.CountAsync(x => + (x.Cliente ?? "").ToUpper() == "RESERVA" || + (x.Usuario ?? "").ToUpper() == "RESERVA" || + (x.Skil ?? "").ToUpper() == "RESERVA"); + + var topClientes = await qLines + .Where(x => x.Cliente != null && x.Cliente != "") + .GroupBy(x => x.Cliente!) + .Select(g => new TopClienteDto + { + Cliente = g.Key, + Linhas = g.Count() + }) + .OrderByDescending(x => x.Linhas) + .ThenBy(x => x.Cliente) + .Take(10) + .ToListAsync(); + + // ========================= + // MUREG + // ========================= + var qMureg = _db.MuregLines.AsNoTracking().Include(x => x.MobileLine); + + var totalMuregs = await qMureg.CountAsync(); + + var muregsUltimos30 = await qMureg.CountAsync(x => + x.DataDaMureg != null && x.DataDaMureg.Value.Date >= last30); + + var muregsRecentes = await qMureg + .OrderByDescending(x => x.DataDaMureg ?? minUtc) + .ThenByDescending(x => x.Item) + .Take(10) + .Select(x => new MuregRecenteDto + { + Id = x.Id, + Item = x.Item, + LinhaAntiga = x.LinhaAntiga, + LinhaNova = x.LinhaNova, + ICCID = x.ICCID, + DataDaMureg = x.DataDaMureg, + Cliente = x.MobileLine != null ? x.MobileLine.Cliente : null, + MobileLineId = x.MobileLineId + }) + .ToListAsync(); + + var serieMureg12 = await BuildSerieUltimos12Meses_Mureg(today); + + // ========================= + // TROCA DE NÚMERO + // ========================= + var qTroca = _db.TrocaNumeroLines.AsNoTracking(); + + var totalTrocas = await qTroca.CountAsync(); + + var trocasUltimos30 = await qTroca.CountAsync(x => + x.DataTroca != null && x.DataTroca.Value.Date >= last30); + + var trocasRecentes = await qTroca + .OrderByDescending(x => x.DataTroca ?? minUtc) + .ThenByDescending(x => x.Item) + .Take(10) + .Select(x => new TrocaRecenteDto + { + Id = x.Id, + Item = x.Item, + LinhaAntiga = x.LinhaAntiga, + LinhaNova = x.LinhaNova, + ICCID = x.ICCID, + DataTroca = x.DataTroca, + Motivo = x.Motivo + }) + .ToListAsync(); + + var serieTroca12 = await BuildSerieUltimos12Meses_Troca(today); + + // ========================= + // VIGÊNCIA + // ========================= + var qVig = _db.VigenciaLines.AsNoTracking(); + + var totalVig = await qVig.CountAsync(); + + var vigVencidos = await qVig.CountAsync(x => + x.DtTerminoFidelizacao != null && x.DtTerminoFidelizacao.Value.Date < today); + + var vigAVencer30 = await qVig.CountAsync(x => + x.DtTerminoFidelizacao != null && + x.DtTerminoFidelizacao.Value.Date >= today && + x.DtTerminoFidelizacao.Value.Date <= limit30); + + // ✅ NOVO: série próximos 12 meses (mês/ano) + var serieVigProx12 = await BuildSerieProximos12Meses_VigenciaEncerramentos(today); + + // ✅ NOVO: buckets de supervisão + var vigBuckets = await BuildVigenciaBuckets(today); + + // ========================= + // USER DATA + // ========================= + var qUserData = _db.UserDatas.AsNoTracking(); + + var userDataRegistros = await qUserData.CountAsync(); + var userDataComCpf = await qUserData.CountAsync(x => x.Cpf != null && x.Cpf != ""); + var userDataComEmail = await qUserData.CountAsync(x => x.Email != null && x.Email != ""); + + // ========================= + // RESPOSTA + // ========================= + var dto = new RelatoriosDashboardDto + { + Kpis = new DashboardKpisDto + { + TotalLinhas = totalLinhas, + ClientesUnicos = clientesUnicos, + Ativos = ativos, + + Bloqueados = bloqueados, + BloqueadosPerdaRoubo = bloqueadosPerdaRoubo, + Bloqueados120Dias = bloqueados120Dias, + BloqueadosOutros = bloqueadosOutros, + + Reservas = reservas, + + TotalMuregs = totalMuregs, + MuregsUltimos30Dias = muregsUltimos30, + + TotalVigenciaLinhas = totalVig, + VigenciaVencidos = vigVencidos, + VigenciaAVencer30 = vigAVencer30, + + TotalTrocas = totalTrocas, + TrocasUltimos30Dias = trocasUltimos30, + + UserDataRegistros = userDataRegistros, + UserDataComCpf = userDataComCpf, + UserDataComEmail = userDataComEmail + }, + TopClientes = topClientes, + SerieMuregUltimos12Meses = serieMureg12, + SerieTrocaUltimos12Meses = serieTroca12, + MuregsRecentes = muregsRecentes, + TrocasRecentes = trocasRecentes, + + SerieVigenciaEncerramentosProx12Meses = serieVigProx12, + VigenciaBuckets = vigBuckets + }; + + return Ok(dto); + } + + // ========================= + // Helpers + // ========================= + private async Task> BuildSerieUltimos12Meses_Mureg(DateTime todayUtcDate) + { + var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc) + .AddMonths(-11); + + var raw = await _db.MuregLines.AsNoTracking() + .Where(x => x.DataDaMureg != null && x.DataDaMureg.Value >= start) + .GroupBy(x => new { x.DataDaMureg!.Value.Year, x.DataDaMureg!.Value.Month }) + .Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() }) + .ToListAsync(); + + return Fill12Months(start, raw.Select(r => (r.Year, r.Month, r.Total))); + } + + private async Task> BuildSerieUltimos12Meses_Troca(DateTime todayUtcDate) + { + var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc) + .AddMonths(-11); + + var raw = await _db.TrocaNumeroLines.AsNoTracking() + .Where(x => x.DataTroca != null && x.DataTroca.Value >= start) + .GroupBy(x => new { x.DataTroca!.Value.Year, x.DataTroca!.Value.Month }) + .Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() }) + .ToListAsync(); + + return Fill12Months(start, raw.Select(r => (r.Year, r.Month, r.Total))); + } + + // ✅ série próximos 12 meses (vigência encerrando) + private async Task> BuildSerieProximos12Meses_VigenciaEncerramentos(DateTime todayUtcDate) + { + var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc); + var end = start.AddMonths(12); + + var raw = await _db.VigenciaLines.AsNoTracking() + .Where(x => x.DtTerminoFidelizacao != null && + x.DtTerminoFidelizacao.Value >= start && + x.DtTerminoFidelizacao.Value < end) + .GroupBy(x => new { x.DtTerminoFidelizacao!.Value.Year, x.DtTerminoFidelizacao!.Value.Month }) + .Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() }) + .ToListAsync(); + + return Fill12MonthsForward(start, raw.Select(r => (r.Year, r.Month, r.Total))); + } + + // ✅ buckets de supervisão de vigência + private async Task BuildVigenciaBuckets(DateTime todayUtcDate) + { + // Importante: DtTerminoFidelizacao pode ser null + var rows = await _db.VigenciaLines.AsNoTracking() + .Where(x => x.DtTerminoFidelizacao != null) + .Select(x => x.DtTerminoFidelizacao!.Value) + .ToListAsync(); + + int vencidos = 0, a0_30 = 0, a31_60 = 0, a61_90 = 0, acima90 = 0; + + foreach (var dt in rows) + { + var days = (dt.Date - todayUtcDate).Days; + + if (days < 0) vencidos++; + else if (days <= 30) a0_30++; + else if (days <= 60) a31_60++; + else if (days <= 90) a61_90++; + else acima90++; + } + + return new VigenciaBucketsDto + { + Vencidos = vencidos, + AVencer0a30 = a0_30, + AVencer31a60 = a31_60, + AVencer61a90 = a61_90, + Acima90 = acima90 + }; + } + + private static List Fill12Months(DateTime startMonth, IEnumerable<(int year, int month, int total)> raw) + { + var dict = raw.ToDictionary(x => $"{x.year:D4}-{x.month:D2}", x => x.total); + + var list = new List(12); + for (int i = 0; i < 12; i++) + { + var dt = startMonth.AddMonths(i); + var key = $"{dt.Year:D4}-{dt.Month:D2}"; + list.Add(new SerieMesDto { Mes = key, Total = dict.TryGetValue(key, out var v) ? v : 0 }); + } + return list; + } + + // ✅ para frente (começa no mês atual e vai +11) + private static List Fill12MonthsForward(DateTime startMonth, IEnumerable<(int year, int month, int total)> raw) + { + var dict = raw.ToDictionary(x => $"{x.year:D4}-{x.month:D2}", x => x.total); + + var list = new List(12); + for (int i = 0; i < 12; i++) + { + var dt = startMonth.AddMonths(i); + var key = $"{dt.Year:D4}-{dt.Month:D2}"; + list.Add(new SerieMesDto { Mes = key, Total = dict.TryGetValue(key, out var v) ? v : 0 }); + } + return list; + } + } +} diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs index e8d9ce3..4591868 100644 --- a/Data/AppDbContext.cs +++ b/Data/AppDbContext.cs @@ -18,10 +18,13 @@ public class AppDbContext : DbContext // ✅ tabela para espelhar o FATURAMENTO (PF/PJ) public DbSet BillingClients => Set(); + // ✅ tabela DADOS DOS USUÁRIOS public DbSet UserDatas => Set(); - public DbSet VigenciaLines { get; set; } = default!; + // ✅ tabela VIGÊNCIA + public DbSet VigenciaLines => Set(); + // ✅ tabela TROCA DE NÚMERO public DbSet TrocaNumeroLines => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -38,23 +41,44 @@ public class AppDbContext : DbContext // ========================= // ✅ GERAL (MobileLine) // ========================= - modelBuilder.Entity() - .HasIndex(x => x.Linha) - .IsUnique(); + modelBuilder.Entity(e => + { + // Mantém UNIQUE por Linha (se Linha puder ser null no banco, Postgres aceita múltiplos nulls) + e.HasIndex(x => x.Linha).IsUnique(); + + // performance + e.HasIndex(x => x.Chip); + e.HasIndex(x => x.Cliente); + e.HasIndex(x => x.Usuario); + e.HasIndex(x => x.Skil); + e.HasIndex(x => x.Status); + }); // ========================= - // ✅ MUREG + // ✅ MUREG (FK para MobileLines) // ========================= - modelBuilder.Entity().HasIndex(x => x.Item); - modelBuilder.Entity().HasIndex(x => x.Cliente); - modelBuilder.Entity().HasIndex(x => x.ICCID); - modelBuilder.Entity().HasIndex(x => x.LinhaNova); + modelBuilder.Entity(e => + { + e.HasIndex(x => x.Item); + e.HasIndex(x => x.ICCID); + e.HasIndex(x => x.LinhaAntiga); + e.HasIndex(x => x.LinhaNova); - // ========================================================== - // ✅ FATURAMENTO (BillingClient) - mantém seu mapeamento - // ========================================================== + // FK + index + e.HasIndex(x => x.MobileLineId); + + e.HasOne(x => x.MobileLine) + .WithMany(m => m.Muregs) + .HasForeignKey(x => x.MobileLineId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // ========================= + // ✅ FATURAMENTO (BillingClient) + // ========================= modelBuilder.Entity(e => { + // ⚠️ só mantenha se seu banco realmente usa esse nome e.ToTable("billing_clients"); e.HasKey(x => x.Id); @@ -68,17 +92,40 @@ public class AppDbContext : DbContext e.HasIndex(x => x.Item); }); - // ========================================================== - // ✅ VIGÊNCIA (se você quiser índices aqui também, pode manter) - // ========================================================== - // modelBuilder.Entity().HasIndex(x => x.Cliente); - // modelBuilder.Entity().HasIndex(x => x.Linha); + // ========================= + // ✅ DADOS DOS USUÁRIOS (UserData) + // ✅ (SEM "Nome" pq não existe no model) + // ========================= + modelBuilder.Entity(e => + { + e.HasIndex(x => x.Item); + e.HasIndex(x => x.Cliente); + e.HasIndex(x => x.Linha); + e.HasIndex(x => x.Cpf); + e.HasIndex(x => x.Email); + }); - // ========================================================== - // ✅ TROCA NÚMERO (opcional: índices) - // ========================================================== - // modelBuilder.Entity().HasIndex(x => x.Cliente); - // modelBuilder.Entity().HasIndex(x => x.LinhaAntiga); - // modelBuilder.Entity().HasIndex(x => x.LinhaNova); + // ========================= + // ✅ VIGÊNCIA + // ========================= + modelBuilder.Entity(e => + { + e.HasIndex(x => x.Item); + e.HasIndex(x => x.Cliente); + e.HasIndex(x => x.Linha); + e.HasIndex(x => x.DtTerminoFidelizacao); + }); + + // ========================= + // ✅ TROCA NÚMERO + // ========================= + modelBuilder.Entity(e => + { + e.HasIndex(x => x.Item); + e.HasIndex(x => x.LinhaAntiga); + e.HasIndex(x => x.LinhaNova); + e.HasIndex(x => x.ICCID); + e.HasIndex(x => x.DataTroca); + }); } } diff --git a/Dtos/MobileLineDtos.cs b/Dtos/MobileLineDtos.cs index 247d860..42d9e5c 100644 --- a/Dtos/MobileLineDtos.cs +++ b/Dtos/MobileLineDtos.cs @@ -98,4 +98,16 @@ { public int Imported { get; set; } } + + public class LineOptionDto + { + public Guid Id { get; set; } + public int Item { get; set; } + public string? Linha { get; set; } + public string? Chip { get; set; } + public string? Cliente { get; set; } + public string? Usuario { get; set; } + public string? Skil { get; set; } + } + } diff --git a/Dtos/MuregDetailDto.cs b/Dtos/MuregDetailDto.cs new file mode 100644 index 0000000..2a5cead --- /dev/null +++ b/Dtos/MuregDetailDto.cs @@ -0,0 +1,27 @@ +using System; + +namespace line_gestao_api.Dtos +{ + public class MuregDetailDto + { + public Guid Id { get; set; } + public int Item { get; set; } + + public string? LinhaAntiga { get; set; } + public string? LinhaNova { get; set; } + public string? ICCID { get; set; } + public DateTime? DataDaMureg { get; set; } + + // ✅ FK para a linha “canônica” na GERAL + public Guid MobileLineId { get; set; } + + // ✅ Dados vindos da GERAL (MobileLines) + public string? Cliente { get; set; } + public string? Usuario { get; set; } + public string? Skil { get; set; } + public string? LinhaAtualNaGeral { get; set; } + public string? ChipNaGeral { get; set; } + public string? ContaNaGeral { get; set; } + public string? StatusNaGeral { get; set; } + } +} diff --git a/Dtos/MuregListDto.cs b/Dtos/MuregListDto.cs index f5775a0..584a005 100644 --- a/Dtos/MuregListDto.cs +++ b/Dtos/MuregListDto.cs @@ -5,11 +5,19 @@ namespace line_gestao_api.Dtos public class MuregListDto { public Guid Id { get; set; } + public int Item { get; set; } + public string? LinhaAntiga { get; set; } public string? LinhaNova { get; set; } public string? ICCID { get; set; } + public DateTime? DataDaMureg { get; set; } + + // ✅ Cliente vem da MobileLine (GERAL) public string? Cliente { get; set; } + + // ✅ Novo: referência do registro "canônico" na GERAL + public Guid MobileLineId { get; set; } } } diff --git a/Dtos/RelatoriosDashboardDto.cs b/Dtos/RelatoriosDashboardDto.cs new file mode 100644 index 0000000..90a916d --- /dev/null +++ b/Dtos/RelatoriosDashboardDto.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; + +namespace line_gestao_api.Dtos +{ + public class RelatoriosDashboardDto + { + public DashboardKpisDto Kpis { get; set; } = new(); + public List TopClientes { get; set; } = new(); + public List SerieMuregUltimos12Meses { get; set; } = new(); + public List SerieTrocaUltimos12Meses { get; set; } = new(); + public List MuregsRecentes { get; set; } = new(); + public List TrocasRecentes { get; set; } = new(); + + // ✅ NOVO: VIGÊNCIA + public List SerieVigenciaEncerramentosProx12Meses { get; set; } = new(); + public VigenciaBucketsDto VigenciaBuckets { get; set; } = new(); + } + + public class DashboardKpisDto + { + public int TotalLinhas { get; set; } + public int ClientesUnicos { get; set; } + public int Ativos { get; set; } + + public int Bloqueados { get; set; } + public int BloqueadosPerdaRoubo { get; set; } + public int Bloqueados120Dias { get; set; } + public int BloqueadosOutros { get; set; } + + public int Reservas { get; set; } + + public int TotalMuregs { get; set; } + public int MuregsUltimos30Dias { get; set; } + + public int TotalVigenciaLinhas { get; set; } + public int VigenciaVencidos { get; set; } + public int VigenciaAVencer30 { get; set; } + + public int TotalTrocas { get; set; } + public int TrocasUltimos30Dias { get; set; } + + public int UserDataRegistros { get; set; } + public int UserDataComCpf { get; set; } + public int UserDataComEmail { get; set; } + } + + public class TopClienteDto + { + public string Cliente { get; set; } = ""; + public int Linhas { get; set; } + } + + public class SerieMesDto + { + public string Mes { get; set; } = ""; + public int Total { get; set; } + } + + public class VigenciaBucketsDto + { + public int Vencidos { get; set; } + public int AVencer0a30 { get; set; } + public int AVencer31a60 { get; set; } + public int AVencer61a90 { get; set; } + public int Acima90 { get; set; } + } + + public class MuregRecenteDto + { + public Guid Id { get; set; } + public int Item { get; set; } + public string? LinhaAntiga { get; set; } + public string? LinhaNova { get; set; } + public string? ICCID { get; set; } + public DateTime? DataDaMureg { get; set; } + public string? Cliente { get; set; } + public Guid MobileLineId { get; set; } + } + + public class TrocaRecenteDto + { + public Guid Id { get; set; } + public int Item { get; set; } + public string? LinhaAntiga { get; set; } + public string? LinhaNova { get; set; } + public string? ICCID { get; set; } + public DateTime? DataTroca { get; set; } + public string? Motivo { get; set; } + } +} diff --git a/Migrations/20260113151828_AddMuregMobileLineRelations.Designer.cs b/Migrations/20260113151828_AddMuregMobileLineRelations.Designer.cs new file mode 100644 index 0000000..090929f --- /dev/null +++ b/Migrations/20260113151828_AddMuregMobileLineRelations.Designer.cs @@ -0,0 +1,447 @@ +// +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("20260113151828_AddMuregMobileLineRelations")] + partial class AddMuregMobileLineRelations + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aparelho") + .HasColumnType("text"); + + b.Property("Cliente") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FormaPagamento") + .HasColumnType("text"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("QtdLinhas") + .HasColumnType("integer"); + + b.Property("Tipo") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cedente") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Chip") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Cliente") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Conta") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataBloqueio") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaCliente") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaOpera") + .HasColumnType("timestamp with time zone"); + + b.Property("Desconto") + .HasColumnType("numeric"); + + b.Property("FranquiaGestao") + .HasColumnType("numeric"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("GestaoVozDados") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LocacaoAp") + .HasColumnType("numeric"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("Modalidade") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("PlanoContrato") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Skeelo") + .HasColumnType("numeric"); + + b.Property("Skil") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("Solicitante") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Status") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("ValorContratoVivo") + .HasColumnType("numeric"); + + b.Property("ValorPlanoVivo") + .HasColumnType("numeric"); + + b.Property("VencConta") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("VivoGestaoDispositivo") + .HasColumnType("numeric"); + + b.Property("VivoNewsPlus") + .HasColumnType("numeric"); + + b.Property("VivoTravelMundo") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("Chip"); + + b.HasIndex("Cliente"); + + b.HasIndex("Linha") + .IsUnique(); + + b.ToTable("MobileLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataDaMureg") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LinhaNova") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("MobileLineId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ICCID"); + + b.HasIndex("Item"); + + b.HasIndex("LinhaAntiga"); + + b.HasIndex("LinhaNova"); + + b.HasIndex("MobileLineId"); + + b.ToTable("MuregLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataTroca") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasColumnType("text"); + + b.Property("LinhaNova") + .HasColumnType("text"); + + b.Property("Motivo") + .HasColumnType("text"); + + b.Property("Observacao") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("TrocaNumeroLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Celular") + .HasColumnType("text"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Cpf") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataNascimento") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Endereco") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("Rg") + .HasColumnType("text"); + + b.Property("TelefoneFixo") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("UserDatas"); + }); + + modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Conta") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DtEfetivacaoServico") + .HasColumnType("timestamp with time zone"); + + b.Property("DtTerminoFidelizacao") + .HasColumnType("timestamp with time zone"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("PlanoContrato") + .HasColumnType("text"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("VigenciaLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine") + .WithMany("Muregs") + .HasForeignKey("MobileLineId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MobileLine"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MobileLine", b => + { + b.Navigation("Muregs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260113151828_AddMuregMobileLineRelations.cs b/Migrations/20260113151828_AddMuregMobileLineRelations.cs new file mode 100644 index 0000000..89a1315 --- /dev/null +++ b/Migrations/20260113151828_AddMuregMobileLineRelations.cs @@ -0,0 +1,157 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace line_gestao_api.Migrations +{ + /// + public partial class AddMuregMobileLineRelations : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_MuregLines_Cliente", + table: "MuregLines"); + + migrationBuilder.DropColumn( + name: "Cliente", + table: "MuregLines"); + + migrationBuilder.AlterColumn( + name: "LinhaNova", + table: "MuregLines", + type: "character varying(30)", + maxLength: 30, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LinhaAntiga", + table: "MuregLines", + type: "character varying(30)", + maxLength: 30, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ICCID", + table: "MuregLines", + type: "character varying(40)", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "MobileLineId", + table: "MuregLines", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_MuregLines_LinhaAntiga", + table: "MuregLines", + column: "LinhaAntiga"); + + migrationBuilder.CreateIndex( + name: "IX_MuregLines_MobileLineId", + table: "MuregLines", + column: "MobileLineId"); + + migrationBuilder.CreateIndex( + name: "IX_MobileLines_Chip", + table: "MobileLines", + column: "Chip"); + + migrationBuilder.CreateIndex( + name: "IX_MobileLines_Cliente", + table: "MobileLines", + column: "Cliente"); + + migrationBuilder.AddForeignKey( + name: "FK_MuregLines_MobileLines_MobileLineId", + table: "MuregLines", + column: "MobileLineId", + principalTable: "MobileLines", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_MuregLines_MobileLines_MobileLineId", + table: "MuregLines"); + + migrationBuilder.DropIndex( + name: "IX_MuregLines_LinhaAntiga", + table: "MuregLines"); + + migrationBuilder.DropIndex( + name: "IX_MuregLines_MobileLineId", + table: "MuregLines"); + + migrationBuilder.DropIndex( + name: "IX_MobileLines_Chip", + table: "MobileLines"); + + migrationBuilder.DropIndex( + name: "IX_MobileLines_Cliente", + table: "MobileLines"); + + migrationBuilder.DropColumn( + name: "MobileLineId", + table: "MuregLines"); + + migrationBuilder.AlterColumn( + name: "LinhaNova", + table: "MuregLines", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(30)", + oldMaxLength: 30, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LinhaAntiga", + table: "MuregLines", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(30)", + oldMaxLength: 30, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ICCID", + table: "MuregLines", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(40)", + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Cliente", + table: "MuregLines", + type: "text", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_MuregLines_Cliente", + table: "MuregLines", + column: "Cliente"); + } + } +} diff --git a/Migrations/20260120204206_AddMuregMobileLineFk.Designer.cs b/Migrations/20260120204206_AddMuregMobileLineFk.Designer.cs new file mode 100644 index 0000000..b19e735 --- /dev/null +++ b/Migrations/20260120204206_AddMuregMobileLineFk.Designer.cs @@ -0,0 +1,447 @@ +// +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("20260120204206_AddMuregMobileLineFk")] + partial class AddMuregMobileLineFk + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aparelho") + .HasColumnType("text"); + + b.Property("Cliente") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FormaPagamento") + .HasColumnType("text"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("QtdLinhas") + .HasColumnType("integer"); + + b.Property("Tipo") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cedente") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Chip") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Cliente") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Conta") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataBloqueio") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaCliente") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaOpera") + .HasColumnType("timestamp with time zone"); + + b.Property("Desconto") + .HasColumnType("numeric"); + + b.Property("FranquiaGestao") + .HasColumnType("numeric"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("GestaoVozDados") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LocacaoAp") + .HasColumnType("numeric"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("Modalidade") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("PlanoContrato") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Skeelo") + .HasColumnType("numeric"); + + b.Property("Skil") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("Solicitante") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Status") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("ValorContratoVivo") + .HasColumnType("numeric"); + + b.Property("ValorPlanoVivo") + .HasColumnType("numeric"); + + b.Property("VencConta") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("VivoGestaoDispositivo") + .HasColumnType("numeric"); + + b.Property("VivoNewsPlus") + .HasColumnType("numeric"); + + b.Property("VivoTravelMundo") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("Chip"); + + b.HasIndex("Cliente"); + + b.HasIndex("Linha") + .IsUnique(); + + b.ToTable("MobileLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataDaMureg") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LinhaNova") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("MobileLineId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ICCID"); + + b.HasIndex("Item"); + + b.HasIndex("LinhaAntiga"); + + b.HasIndex("LinhaNova"); + + b.HasIndex("MobileLineId"); + + b.ToTable("MuregLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataTroca") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasColumnType("text"); + + b.Property("LinhaNova") + .HasColumnType("text"); + + b.Property("Motivo") + .HasColumnType("text"); + + b.Property("Observacao") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("TrocaNumeroLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Celular") + .HasColumnType("text"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Cpf") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataNascimento") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Endereco") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("Rg") + .HasColumnType("text"); + + b.Property("TelefoneFixo") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("UserDatas"); + }); + + modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Conta") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DtEfetivacaoServico") + .HasColumnType("timestamp with time zone"); + + b.Property("DtTerminoFidelizacao") + .HasColumnType("timestamp with time zone"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("PlanoContrato") + .HasColumnType("text"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("VigenciaLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine") + .WithMany("Muregs") + .HasForeignKey("MobileLineId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MobileLine"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MobileLine", b => + { + b.Navigation("Muregs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260120204206_AddMuregMobileLineFk.cs b/Migrations/20260120204206_AddMuregMobileLineFk.cs new file mode 100644 index 0000000..f83bf4e --- /dev/null +++ b/Migrations/20260120204206_AddMuregMobileLineFk.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace line_gestao_api.Migrations +{ + /// + public partial class AddMuregMobileLineFk : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Migrations/20260121190524_AddIndexesReportsSupport.Designer.cs b/Migrations/20260121190524_AddIndexesReportsSupport.Designer.cs new file mode 100644 index 0000000..2fb1d96 --- /dev/null +++ b/Migrations/20260121190524_AddIndexesReportsSupport.Designer.cs @@ -0,0 +1,481 @@ +// +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("20260121190524_AddIndexesReportsSupport")] + partial class AddIndexesReportsSupport + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aparelho") + .HasColumnType("text"); + + b.Property("Cliente") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FormaPagamento") + .HasColumnType("text"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("QtdLinhas") + .HasColumnType("integer"); + + b.Property("Tipo") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cedente") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Chip") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Cliente") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Conta") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataBloqueio") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaCliente") + .HasColumnType("timestamp with time zone"); + + b.Property("DataEntregaOpera") + .HasColumnType("timestamp with time zone"); + + b.Property("Desconto") + .HasColumnType("numeric"); + + b.Property("FranquiaGestao") + .HasColumnType("numeric"); + + b.Property("FranquiaLine") + .HasColumnType("numeric"); + + b.Property("FranquiaVivo") + .HasColumnType("numeric"); + + b.Property("GestaoVozDados") + .HasColumnType("numeric"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LocacaoAp") + .HasColumnType("numeric"); + + b.Property("Lucro") + .HasColumnType("numeric"); + + b.Property("Modalidade") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("PlanoContrato") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Skeelo") + .HasColumnType("numeric"); + + b.Property("Skil") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("Solicitante") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Status") + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ValorContratoLine") + .HasColumnType("numeric"); + + b.Property("ValorContratoVivo") + .HasColumnType("numeric"); + + b.Property("ValorPlanoVivo") + .HasColumnType("numeric"); + + b.Property("VencConta") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("VivoGestaoDispositivo") + .HasColumnType("numeric"); + + b.Property("VivoNewsPlus") + .HasColumnType("numeric"); + + b.Property("VivoTravelMundo") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("Chip"); + + b.HasIndex("Cliente"); + + b.HasIndex("Linha") + .IsUnique(); + + b.HasIndex("Skil"); + + b.HasIndex("Status"); + + b.HasIndex("Usuario"); + + b.ToTable("MobileLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataDaMureg") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("LinhaNova") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("MobileLineId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ICCID"); + + b.HasIndex("Item"); + + b.HasIndex("LinhaAntiga"); + + b.HasIndex("LinhaNova"); + + b.HasIndex("MobileLineId"); + + b.ToTable("MuregLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataTroca") + .HasColumnType("timestamp with time zone"); + + b.Property("ICCID") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("LinhaAntiga") + .HasColumnType("text"); + + b.Property("LinhaNova") + .HasColumnType("text"); + + b.Property("Motivo") + .HasColumnType("text"); + + b.Property("Observacao") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DataTroca"); + + b.HasIndex("ICCID"); + + b.HasIndex("Item"); + + b.HasIndex("LinhaAntiga"); + + b.HasIndex("LinhaNova"); + + b.ToTable("TrocaNumeroLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Celular") + .HasColumnType("text"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Cpf") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DataNascimento") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Endereco") + .HasColumnType("text"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("Rg") + .HasColumnType("text"); + + b.Property("TelefoneFixo") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Cliente"); + + b.HasIndex("Cpf"); + + b.HasIndex("Email"); + + b.HasIndex("Item"); + + b.HasIndex("Linha"); + + b.ToTable("UserDatas"); + }); + + modelBuilder.Entity("line_gestao_api.Models.VigenciaLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Cliente") + .HasColumnType("text"); + + b.Property("Conta") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DtEfetivacaoServico") + .HasColumnType("timestamp with time zone"); + + b.Property("DtTerminoFidelizacao") + .HasColumnType("timestamp with time zone"); + + b.Property("Item") + .HasColumnType("integer"); + + b.Property("Linha") + .HasColumnType("text"); + + b.Property("PlanoContrato") + .HasColumnType("text"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Usuario") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Cliente"); + + b.HasIndex("DtTerminoFidelizacao"); + + b.HasIndex("Item"); + + b.HasIndex("Linha"); + + b.ToTable("VigenciaLines"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine") + .WithMany("Muregs") + .HasForeignKey("MobileLineId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MobileLine"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MobileLine", b => + { + b.Navigation("Muregs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260121190524_AddIndexesReportsSupport.cs b/Migrations/20260121190524_AddIndexesReportsSupport.cs new file mode 100644 index 0000000..4f0347d --- /dev/null +++ b/Migrations/20260121190524_AddIndexesReportsSupport.cs @@ -0,0 +1,171 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace line_gestao_api.Migrations +{ + /// + public partial class AddIndexesReportsSupport : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_VigenciaLines_Cliente", + table: "VigenciaLines", + column: "Cliente"); + + migrationBuilder.CreateIndex( + name: "IX_VigenciaLines_DtTerminoFidelizacao", + table: "VigenciaLines", + column: "DtTerminoFidelizacao"); + + migrationBuilder.CreateIndex( + name: "IX_VigenciaLines_Item", + table: "VigenciaLines", + column: "Item"); + + migrationBuilder.CreateIndex( + name: "IX_VigenciaLines_Linha", + table: "VigenciaLines", + column: "Linha"); + + migrationBuilder.CreateIndex( + name: "IX_UserDatas_Cliente", + table: "UserDatas", + column: "Cliente"); + + migrationBuilder.CreateIndex( + name: "IX_UserDatas_Cpf", + table: "UserDatas", + column: "Cpf"); + + migrationBuilder.CreateIndex( + name: "IX_UserDatas_Email", + table: "UserDatas", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_UserDatas_Item", + table: "UserDatas", + column: "Item"); + + migrationBuilder.CreateIndex( + name: "IX_UserDatas_Linha", + table: "UserDatas", + column: "Linha"); + + migrationBuilder.CreateIndex( + name: "IX_TrocaNumeroLines_DataTroca", + table: "TrocaNumeroLines", + column: "DataTroca"); + + migrationBuilder.CreateIndex( + name: "IX_TrocaNumeroLines_ICCID", + table: "TrocaNumeroLines", + column: "ICCID"); + + migrationBuilder.CreateIndex( + name: "IX_TrocaNumeroLines_Item", + table: "TrocaNumeroLines", + column: "Item"); + + migrationBuilder.CreateIndex( + name: "IX_TrocaNumeroLines_LinhaAntiga", + table: "TrocaNumeroLines", + column: "LinhaAntiga"); + + migrationBuilder.CreateIndex( + name: "IX_TrocaNumeroLines_LinhaNova", + table: "TrocaNumeroLines", + column: "LinhaNova"); + + migrationBuilder.CreateIndex( + name: "IX_MobileLines_Skil", + table: "MobileLines", + column: "Skil"); + + migrationBuilder.CreateIndex( + name: "IX_MobileLines_Status", + table: "MobileLines", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_MobileLines_Usuario", + table: "MobileLines", + column: "Usuario"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_VigenciaLines_Cliente", + table: "VigenciaLines"); + + migrationBuilder.DropIndex( + name: "IX_VigenciaLines_DtTerminoFidelizacao", + table: "VigenciaLines"); + + migrationBuilder.DropIndex( + name: "IX_VigenciaLines_Item", + table: "VigenciaLines"); + + migrationBuilder.DropIndex( + name: "IX_VigenciaLines_Linha", + table: "VigenciaLines"); + + migrationBuilder.DropIndex( + name: "IX_UserDatas_Cliente", + table: "UserDatas"); + + migrationBuilder.DropIndex( + name: "IX_UserDatas_Cpf", + table: "UserDatas"); + + migrationBuilder.DropIndex( + name: "IX_UserDatas_Email", + table: "UserDatas"); + + migrationBuilder.DropIndex( + name: "IX_UserDatas_Item", + table: "UserDatas"); + + migrationBuilder.DropIndex( + name: "IX_UserDatas_Linha", + table: "UserDatas"); + + migrationBuilder.DropIndex( + name: "IX_TrocaNumeroLines_DataTroca", + table: "TrocaNumeroLines"); + + migrationBuilder.DropIndex( + name: "IX_TrocaNumeroLines_ICCID", + table: "TrocaNumeroLines"); + + migrationBuilder.DropIndex( + name: "IX_TrocaNumeroLines_Item", + table: "TrocaNumeroLines"); + + migrationBuilder.DropIndex( + name: "IX_TrocaNumeroLines_LinhaAntiga", + table: "TrocaNumeroLines"); + + migrationBuilder.DropIndex( + name: "IX_TrocaNumeroLines_LinhaNova", + table: "TrocaNumeroLines"); + + migrationBuilder.DropIndex( + name: "IX_MobileLines_Skil", + table: "MobileLines"); + + migrationBuilder.DropIndex( + name: "IX_MobileLines_Status", + table: "MobileLines"); + + migrationBuilder.DropIndex( + name: "IX_MobileLines_Usuario", + table: "MobileLines"); + } + } +} diff --git a/Migrations/AppDbContextModelSnapshot.cs b/Migrations/AppDbContextModelSnapshot.cs index 319e2a6..545aaff 100644 --- a/Migrations/AppDbContextModelSnapshot.cs +++ b/Migrations/AppDbContextModelSnapshot.cs @@ -200,9 +200,19 @@ namespace line_gestao_api.Migrations b.HasKey("Id"); + b.HasIndex("Chip"); + + b.HasIndex("Cliente"); + b.HasIndex("Linha") .IsUnique(); + b.HasIndex("Skil"); + + b.HasIndex("Status"); + + b.HasIndex("Usuario"); + b.ToTable("MobileLines"); }); @@ -212,9 +222,6 @@ namespace line_gestao_api.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("Cliente") - .HasColumnType("text"); - b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -222,30 +229,38 @@ namespace line_gestao_api.Migrations .HasColumnType("timestamp with time zone"); b.Property("ICCID") - .HasColumnType("text"); + .HasMaxLength(40) + .HasColumnType("character varying(40)"); b.Property("Item") .HasColumnType("integer"); b.Property("LinhaAntiga") - .HasColumnType("text"); + .HasMaxLength(30) + .HasColumnType("character varying(30)"); b.Property("LinhaNova") - .HasColumnType("text"); + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("MobileLineId") + .HasColumnType("uuid"); b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("Cliente"); - b.HasIndex("ICCID"); b.HasIndex("Item"); + b.HasIndex("LinhaAntiga"); + b.HasIndex("LinhaNova"); + b.HasIndex("MobileLineId"); + b.ToTable("MuregLines"); }); @@ -284,6 +299,16 @@ namespace line_gestao_api.Migrations b.HasKey("Id"); + b.HasIndex("DataTroca"); + + b.HasIndex("ICCID"); + + b.HasIndex("Item"); + + b.HasIndex("LinhaAntiga"); + + b.HasIndex("LinhaNova"); + b.ToTable("TrocaNumeroLines"); }); @@ -367,6 +392,16 @@ namespace line_gestao_api.Migrations b.HasKey("Id"); + b.HasIndex("Cliente"); + + b.HasIndex("Cpf"); + + b.HasIndex("Email"); + + b.HasIndex("Item"); + + b.HasIndex("Linha"); + b.ToTable("UserDatas"); }); @@ -411,8 +446,32 @@ namespace line_gestao_api.Migrations b.HasKey("Id"); + b.HasIndex("Cliente"); + + b.HasIndex("DtTerminoFidelizacao"); + + b.HasIndex("Item"); + + b.HasIndex("Linha"); + b.ToTable("VigenciaLines"); }); + + modelBuilder.Entity("line_gestao_api.Models.MuregLine", b => + { + b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine") + .WithMany("Muregs") + .HasForeignKey("MobileLineId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MobileLine"); + }); + + modelBuilder.Entity("line_gestao_api.Models.MobileLine", b => + { + b.Navigation("Muregs"); + }); #pragma warning restore 612, 618 } } diff --git a/Models/MobileLine.cs b/Models/MobileLine.cs index 6a3320f..f4e5376 100644 --- a/Models/MobileLine.cs +++ b/Models/MobileLine.cs @@ -6,62 +6,62 @@ namespace line_gestao_api.Models { public Guid Id { get; set; } = Guid.NewGuid(); - // ===== Planilha (GERAL) ===== - public int Item { get; set; } // ITÉM + public int Item { get; set; } [MaxLength(80)] - public string? Conta { get; set; } // CONTA + public string? Conta { get; set; } [MaxLength(30)] - public string? Linha { get; set; } // LINHA (telefone) + public string? Linha { get; set; } [MaxLength(40)] - public string? Chip { get; set; } // CHIP + public string? Chip { get; set; } [MaxLength(200)] - public string? Cliente { get; set; } // CLIENTE + public string? Cliente { get; set; } [MaxLength(200)] - public string? Usuario { get; set; } // USUÁRIO + public string? Usuario { get; set; } [MaxLength(200)] - public string? PlanoContrato { get; set; } // PLANO CONTRATO + public string? PlanoContrato { get; set; } - // ===== 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 + 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; } - // ===== 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? FranquiaLine { get; set; } + public decimal? FranquiaGestao { get; set; } + public decimal? LocacaoAp { get; set; } + public decimal? ValorContratoLine { get; set; } - public decimal? Desconto { get; set; } // DESCONTO - public decimal? Lucro { get; set; } // LUCRO + public decimal? Desconto { get; set; } + public decimal? Lucro { get; set; } [MaxLength(80)] - public string? Status { get; set; } // STATUS - public DateTime? DataBloqueio { get; set; } // DATA DO BLOQUEIO + public string? Status { get; set; } + public DateTime? DataBloqueio { get; set; } [MaxLength(80)] - public string? Skil { get; set; } // SKIL + public string? Skil { get; set; } [MaxLength(80)] - public string? Modalidade { get; set; } // MODALIDADE + public string? Modalidade { get; set; } [MaxLength(150)] - public string? Cedente { get; set; } // CEDENTE + public string? Cedente { get; set; } [MaxLength(150)] - public string? Solicitante { get; set; } // SOLICITANTE + public string? Solicitante { get; set; } - public DateTime? DataEntregaOpera { get; set; } // DATA DA ENTREGA OPERA. - public DateTime? DataEntregaCliente { get; set; } // DATA DA ENTREGA CLIENTE + public DateTime? DataEntregaOpera { get; set; } + public DateTime? DataEntregaCliente { get; set; } [MaxLength(50)] - public string? VencConta { get; set; } // VENC. DA CONTA + public string? VencConta { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // ✅ Navegação (1 MobileLine -> N Muregs) + public ICollection Muregs { get; set; } = new List(); } } diff --git a/Models/MuregLine.cs b/Models/MuregLine.cs index 10d70f8..f076d88 100644 --- a/Models/MuregLine.cs +++ b/Models/MuregLine.cs @@ -1,4 +1,4 @@ -using System; +using System.ComponentModel.DataAnnotations; namespace line_gestao_api.Models { @@ -8,13 +8,23 @@ namespace line_gestao_api.Models public int Item { get; set; } + // Linha escolhida da GERAL no momento do mureg + [MaxLength(30)] public string? LinhaAntiga { get; set; } + + // Linha que o usuário digitou/selecionou como nova + [MaxLength(30)] public string? LinhaNova { get; set; } + + // Se na sua aba MUREG vem o ICCID + [MaxLength(40)] public string? ICCID { get; set; } public DateTime? DataDaMureg { get; set; } - public string? Cliente { get; set; } + // ✅ FK para a linha “canônica” na GERAL (a mesma linha que será atualizada) + public Guid MobileLineId { get; set; } + public MobileLine MobileLine { get; set; } = null!; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;