Atualização de novas alterações

This commit is contained in:
Eduardo 2026-02-06 08:40:26 -03:00
parent dfa34e0f5f
commit 0c17b5e48a
41 changed files with 10574 additions and 259 deletions

View File

@ -1,5 +1,6 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -146,5 +147,75 @@ namespace line_gestao_api.Controllers
return Ok(clients); return Ok(clients);
} }
[HttpGet("{id:guid}")]
public async Task<ActionResult<BillingClientDetailDto>> GetById(Guid id)
{
var x = await _db.BillingClients.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
return Ok(new BillingClientDetailDto
{
Id = x.Id,
Tipo = x.Tipo,
Item = x.Item,
Cliente = x.Cliente,
QtdLinhas = x.QtdLinhas,
FranquiaVivo = x.FranquiaVivo,
ValorContratoVivo = x.ValorContratoVivo,
FranquiaLine = x.FranquiaLine,
ValorContratoLine = x.ValorContratoLine,
Lucro = x.Lucro,
Aparelho = x.Aparelho,
FormaPagamento = x.FormaPagamento,
CreatedAt = x.CreatedAt,
UpdatedAt = x.UpdatedAt
});
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateBillingClientRequest req)
{
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
if (!string.IsNullOrWhiteSpace(req.Tipo))
{
var tipo = req.Tipo.Trim().ToUpperInvariant();
if (tipo != "PF" && tipo != "PJ")
return BadRequest(new { message = "Tipo inválido. Use PF ou PJ." });
x.Tipo = tipo;
}
if (req.Item.HasValue) x.Item = req.Item.Value;
if (req.Cliente != null) x.Cliente = req.Cliente.Trim();
x.QtdLinhas = req.QtdLinhas;
x.FranquiaVivo = req.FranquiaVivo;
x.ValorContratoVivo = req.ValorContratoVivo;
x.FranquiaLine = req.FranquiaLine;
x.ValorContratoLine = req.ValorContratoLine;
x.Lucro = req.Lucro;
x.Aparelho = string.IsNullOrWhiteSpace(req.Aparelho) ? null : req.Aparelho.Trim();
x.FormaPagamento = string.IsNullOrWhiteSpace(req.FormaPagamento) ? null : req.FormaPagamento.Trim();
x.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id)
{
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
_db.BillingClients.Remove(x);
await _db.SaveChangesAsync();
return NoContent();
}
} }
} }

View File

@ -1,6 +1,7 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using line_gestao_api.Models; using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text; using System.Text;
@ -88,11 +89,18 @@ namespace line_gestao_api.Controllers
public async Task<ActionResult<ChipVirgemDetailDto>> Create([FromBody] CreateChipVirgemDto req) public async Task<ActionResult<ChipVirgemDetailDto>> Create([FromBody] CreateChipVirgemDto req)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var item = req.Item ?? 0;
if (item <= 0)
{
var maxItem = await _db.ChipVirgemLines.MaxAsync(x => (int?)x.Item) ?? 0;
item = maxItem + 1;
}
var e = new ChipVirgemLine var e = new ChipVirgemLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = req.Item ?? 0, Item = item,
NumeroDoChip = NullIfEmptyDigits(req.NumeroDoChip), NumeroDoChip = NullIfEmptyDigits(req.NumeroDoChip),
Observacoes = string.IsNullOrWhiteSpace(req.Observacoes) ? null : req.Observacoes.Trim(), Observacoes = string.IsNullOrWhiteSpace(req.Observacoes) ? null : req.Observacoes.Trim(),
CreatedAt = now, CreatedAt = now,
@ -106,6 +114,7 @@ namespace line_gestao_api.Controllers
} }
[HttpPut("{id:guid}")] [HttpPut("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateChipVirgemRequest req) public async Task<IActionResult> Update(Guid id, [FromBody] UpdateChipVirgemRequest req)
{ {
var x = await _db.ChipVirgemLines.FirstOrDefaultAsync(a => a.Id == id); var x = await _db.ChipVirgemLines.FirstOrDefaultAsync(a => a.Id == id);
@ -122,6 +131,7 @@ namespace line_gestao_api.Controllers
} }
[HttpDelete("{id:guid}")] [HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id) public async Task<IActionResult> Delete(Guid id)
{ {
var x = await _db.ChipVirgemLines.FirstOrDefaultAsync(a => a.Id == id); var x = await _db.ChipVirgemLines.FirstOrDefaultAsync(a => a.Id == id);

View File

@ -1,6 +1,7 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using line_gestao_api.Models; using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text; using System.Text;
@ -119,21 +120,46 @@ namespace line_gestao_api.Controllers
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var ano = req.Ano ?? (req.DataDaNf?.Year ?? DateTime.UtcNow.Year);
var item = req.Item ?? 0;
if (item <= 0)
{
var maxItem = await _db.ControleRecebidoLines
.AsNoTracking()
.Where(x => x.Ano == ano)
.MaxAsync(x => (int?)x.Item) ?? 0;
item = maxItem + 1;
}
var quantidade = req.Quantidade;
var valorUnit = req.ValorUnit;
var valorDaNf = req.ValorDaNf;
if (!valorDaNf.HasValue && valorUnit.HasValue && quantidade.HasValue)
{
valorDaNf = Math.Round(valorUnit.Value * quantidade.Value, 2);
}
else if (!valorUnit.HasValue && valorDaNf.HasValue && quantidade.HasValue && quantidade.Value > 0)
{
valorUnit = Math.Round(valorDaNf.Value / quantidade.Value, 2);
}
var e = new ControleRecebidoLine var e = new ControleRecebidoLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Ano = req.Ano ?? DateTime.UtcNow.Year, Ano = ano,
Item = req.Item ?? 0, Item = item,
NotaFiscal = TrimOrNull(req.NotaFiscal), NotaFiscal = TrimOrNull(req.NotaFiscal),
Chip = NullIfEmptyDigits(req.Chip), Chip = NullIfEmptyDigits(req.Chip),
Serial = TrimOrNull(req.Serial), Serial = TrimOrNull(req.Serial),
ConteudoDaNf = TrimOrNull(req.ConteudoDaNf), ConteudoDaNf = TrimOrNull(req.ConteudoDaNf),
NumeroDaLinha = NullIfEmptyDigits(req.NumeroDaLinha), NumeroDaLinha = NullIfEmptyDigits(req.NumeroDaLinha),
ValorUnit = req.ValorUnit, ValorUnit = valorUnit,
ValorDaNf = req.ValorDaNf, ValorDaNf = valorDaNf,
DataDaNf = ToUtc(req.DataDaNf), DataDaNf = ToUtc(req.DataDaNf),
DataDoRecebimento = ToUtc(req.DataDoRecebimento), DataDoRecebimento = ToUtc(req.DataDoRecebimento),
Quantidade = req.Quantidade, Quantidade = quantidade,
IsResumo = req.IsResumo ?? false, IsResumo = req.IsResumo ?? false,
CreatedAt = now, CreatedAt = now,
UpdatedAt = now UpdatedAt = now
@ -146,6 +172,7 @@ namespace line_gestao_api.Controllers
} }
[HttpPut("{id:guid}")] [HttpPut("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateControleRecebidoRequest req) public async Task<IActionResult> Update(Guid id, [FromBody] UpdateControleRecebidoRequest req)
{ {
var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id); var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id);
@ -173,6 +200,7 @@ namespace line_gestao_api.Controllers
} }
[HttpDelete("{id:guid}")] [HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id) public async Task<IActionResult> Delete(Guid id)
{ {
var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id); var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id);

View File

@ -0,0 +1,156 @@
using System.Text.Json;
using line_gestao_api.Data;
using line_gestao_api.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Controllers;
[ApiController]
[Route("api/historico")]
[Authorize(Roles = "admin")]
public class HistoricoController : ControllerBase
{
private readonly AppDbContext _db;
public HistoricoController(AppDbContext db)
{
_db = db;
}
[HttpGet]
public async Task<ActionResult<PagedResult<AuditLogDto>>> GetAll(
[FromQuery] string? pageName,
[FromQuery] string? action,
[FromQuery] string? entity,
[FromQuery] Guid? userId,
[FromQuery] string? search,
[FromQuery] DateTime? dateFrom,
[FromQuery] DateTime? dateTo,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
page = page < 1 ? 1 : page;
pageSize = pageSize < 1 ? 20 : pageSize;
var q = _db.AuditLogs.AsNoTracking();
if (!string.IsNullOrWhiteSpace(pageName))
{
var p = pageName.Trim();
q = q.Where(x => EF.Functions.ILike(x.Page, $"%{p}%"));
}
if (!string.IsNullOrWhiteSpace(action))
{
var a = action.Trim().ToUpperInvariant();
q = q.Where(x => x.Action == a);
}
if (!string.IsNullOrWhiteSpace(entity))
{
var e = entity.Trim();
q = q.Where(x => EF.Functions.ILike(x.EntityName, $"%{e}%"));
}
if (userId.HasValue)
{
q = q.Where(x => x.UserId == userId.Value);
}
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.Trim();
q = q.Where(x =>
EF.Functions.ILike(x.UserName ?? "", $"%{s}%") ||
EF.Functions.ILike(x.UserEmail ?? "", $"%{s}%") ||
EF.Functions.ILike(x.EntityName ?? "", $"%{s}%") ||
EF.Functions.ILike(x.EntityLabel ?? "", $"%{s}%") ||
EF.Functions.ILike(x.EntityId ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Page ?? "", $"%{s}%"));
}
if (dateFrom.HasValue)
{
var fromUtc = ToUtc(dateFrom.Value);
q = q.Where(x => x.OccurredAtUtc >= fromUtc);
}
if (dateTo.HasValue)
{
var toUtc = ToUtc(dateTo.Value);
if (dateTo.Value.TimeOfDay == TimeSpan.Zero)
{
toUtc = toUtc.Date.AddDays(1).AddTicks(-1);
}
q = q.Where(x => x.OccurredAtUtc <= toUtc);
}
var total = await q.CountAsync();
var items = await q
.OrderByDescending(x => x.OccurredAtUtc)
.ThenByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(new PagedResult<AuditLogDto>
{
Page = page,
PageSize = pageSize,
Total = total,
Items = items.Select(ToDto).ToList()
});
}
private static AuditLogDto ToDto(Models.AuditLog log)
{
return new AuditLogDto
{
Id = log.Id,
OccurredAtUtc = log.OccurredAtUtc,
Action = log.Action,
Page = log.Page,
EntityName = log.EntityName,
EntityId = log.EntityId,
EntityLabel = log.EntityLabel,
UserId = log.UserId,
UserName = log.UserName,
UserEmail = log.UserEmail,
RequestPath = log.RequestPath,
RequestMethod = log.RequestMethod,
IpAddress = log.IpAddress,
Changes = ParseChanges(log.ChangesJson)
};
}
private static List<AuditFieldChangeDto> ParseChanges(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return new List<AuditFieldChangeDto>();
}
try
{
return JsonSerializer.Deserialize<List<AuditFieldChangeDto>>(json) ?? new List<AuditFieldChangeDto>();
}
catch
{
return new List<AuditFieldChangeDto>();
}
}
private static DateTime ToUtc(DateTime value)
{
if (value.Kind == DateTimeKind.Utc)
return value;
if (value.Kind == DateTimeKind.Local)
return value.ToUniversalTime();
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
}

View File

@ -23,6 +23,29 @@ namespace line_gestao_api.Controllers
{ {
private readonly AppDbContext _db; private readonly AppDbContext _db;
private readonly ParcelamentosImportService _parcelamentosImportService; private readonly ParcelamentosImportService _parcelamentosImportService;
private static readonly List<AccountCompanyDto> AccountCompanies = new()
{
new AccountCompanyDto
{
Empresa = "CLARO LINE MÓVEL",
Contas = new List<string> { "172593311", "172593840" }
},
new AccountCompanyDto
{
Empresa = "VIVO MACROPHONY",
Contas = new List<string> { "0430237019", "0437488125", "0449508564", "0454371844" }
},
new AccountCompanyDto
{
Empresa = "VIVO LINE MÓVEL",
Contas = new List<string> { "0435288088" }
},
new AccountCompanyDto
{
Empresa = "TIM LINE MÓVEL",
Contas = new List<string> { "0072046192" }
}
};
public LinesController(AppDbContext db, ParcelamentosImportService parcelamentosImportService) public LinesController(AppDbContext db, ParcelamentosImportService parcelamentosImportService)
{ {
@ -48,18 +71,30 @@ namespace line_gestao_api.Controllers
page = page < 1 ? 1 : page; page = page < 1 ? 1 : page;
pageSize = pageSize < 1 ? 10 : pageSize; pageSize = pageSize < 1 ? 10 : pageSize;
var query = _db.MobileLines.AsNoTracking().Where(x => !string.IsNullOrEmpty(x.Cliente)); var query = _db.MobileLines.AsNoTracking();
var reservaFilter = false;
// Filtro SKIL // Filtro SKIL
if (!string.IsNullOrWhiteSpace(skil)) if (!string.IsNullOrWhiteSpace(skil))
{ {
var sSkil = skil.Trim(); var sSkil = skil.Trim();
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase)) if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
query = query.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%")); {
reservaFilter = true;
query = query.Where(x =>
EF.Functions.ILike((x.Cliente ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Usuario ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Skil ?? "").Trim(), "%RESERVA%"));
}
else else
query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%")); query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
} }
if (!reservaFilter)
{
query = query.Where(x => !string.IsNullOrEmpty(x.Cliente));
}
// Filtro SEARCH (Busca pelo Nome do Cliente) // Filtro SEARCH (Busca pelo Nome do Cliente)
if (!string.IsNullOrWhiteSpace(search)) if (!string.IsNullOrWhiteSpace(search))
{ {
@ -67,8 +102,18 @@ namespace line_gestao_api.Controllers
query = query.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%")); query = query.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%"));
} }
var groupedQuery = query var groupedQuery = reservaFilter
.GroupBy(x => x.Cliente) ? query.GroupBy(_ => "RESERVA")
.Select(g => new ClientGroupDto
{
Cliente = g.Key,
TotalLinhas = g.Count(),
Ativos = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%ativo%")),
Bloqueados = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%bloque%") ||
EF.Functions.ILike(x.Status ?? "", "%perda%") ||
EF.Functions.ILike(x.Status ?? "", "%roubo%"))
})
: query.GroupBy(x => x.Cliente)
.Select(g => new ClientGroupDto .Select(g => new ClientGroupDto
{ {
Cliente = g.Key!, Cliente = g.Key!,
@ -108,7 +153,10 @@ namespace line_gestao_api.Controllers
{ {
var sSkil = skil.Trim(); var sSkil = skil.Trim();
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase)) if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
query = query.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%")); query = query.Where(x =>
EF.Functions.ILike((x.Cliente ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Usuario ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Skil ?? "").Trim(), "%RESERVA%"));
else else
query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%")); query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
} }
@ -207,6 +255,37 @@ namespace line_gestao_api.Controllers
return Ok(clients); return Ok(clients);
} }
[HttpGet("account-companies")]
public ActionResult<List<AccountCompanyDto>> GetAccountCompanies()
{
var items = AccountCompanies
.Select(x => new AccountCompanyDto
{
Empresa = x.Empresa,
Contas = x.Contas.ToList()
})
.ToList();
return Ok(items);
}
[HttpGet("accounts")]
public ActionResult<List<string>> GetAccounts([FromQuery] string? empresa)
{
if (string.IsNullOrWhiteSpace(empresa))
return Ok(new List<string>());
var target = empresa.Trim();
var contas = AccountCompanies
.FirstOrDefault(x => string.Equals(x.Empresa, target, StringComparison.OrdinalIgnoreCase))
?.Contas
?.ToList()
?? new List<string>();
return Ok(contas);
}
// ========================================================== // ==========================================================
// ✅ 2.1 ENDPOINT: LINHAS POR CLIENTE (para SELECT do MUREG) // ✅ 2.1 ENDPOINT: LINHAS POR CLIENTE (para SELECT do MUREG)
// GET: /api/lines/by-client?cliente=... // GET: /api/lines/by-client?cliente=...
@ -262,7 +341,10 @@ namespace line_gestao_api.Controllers
{ {
var sSkil = skil.Trim(); var sSkil = skil.Trim();
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase)) if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
q = q.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%")); q = q.Where(x =>
EF.Functions.ILike((x.Cliente ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Usuario ?? "").Trim(), "%RESERVA%") ||
EF.Functions.ILike((x.Skil ?? "").Trim(), "%RESERVA%"));
else else
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%")); q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
} }
@ -372,6 +454,10 @@ namespace line_gestao_api.Controllers
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var planSuggestion = await AutoFillRules.ResolvePlanSuggestionAsync(_db, req.PlanoContrato);
var franquiaVivo = req.FranquiaVivo ?? planSuggestion?.FranquiaGb;
var valorPlanoVivo = req.ValorPlanoVivo ?? planSuggestion?.ValorPlano;
var newLine = new MobileLine var newLine = new MobileLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
@ -394,8 +480,8 @@ namespace line_gestao_api.Controllers
Cedente = req.Cedente?.Trim(), Cedente = req.Cedente?.Trim(),
Solicitante = req.Solicitante?.Trim(), Solicitante = req.Solicitante?.Trim(),
FranquiaVivo = req.FranquiaVivo, FranquiaVivo = franquiaVivo,
ValorPlanoVivo = req.ValorPlanoVivo, ValorPlanoVivo = valorPlanoVivo,
GestaoVozDados = req.GestaoVozDados, GestaoVozDados = req.GestaoVozDados,
Skeelo = req.Skeelo, Skeelo = req.Skeelo,
VivoNewsPlus = req.VivoNewsPlus, VivoNewsPlus = req.VivoNewsPlus,
@ -879,6 +965,7 @@ namespace line_gestao_api.Controllers
if (headerRow == null) return; if (headerRow == null) return;
var headerRowIndex = headerRow.RowNumber(); var headerRowIndex = headerRow.RowNumber();
var map = BuildHeaderMap(headerRow);
// linha acima (grupos VIVO / LINE) // linha acima (grupos VIVO / LINE)
var groupRowIndex = Math.Max(1, headerRowIndex - 1); var groupRowIndex = Math.Max(1, headerRowIndex - 1);
@ -943,8 +1030,6 @@ namespace line_gestao_api.Controllers
if (colFranquiaVivo == 0 || colValorVivo == 0 || colFranquiaLine == 0 || colValorLine == 0) if (colFranquiaVivo == 0 || colValorVivo == 0 || colFranquiaLine == 0 || colValorLine == 0)
{ {
var map = BuildHeaderMap(headerRow);
if (colFranquiaLine == 0) if (colFranquiaLine == 0)
colFranquiaLine = GetColAny(map, "FRAQUIA LINE", "FRANQUIA LINE", "FRANQUIALINE", "FRAQUIALINE"); colFranquiaLine = GetColAny(map, "FRAQUIA LINE", "FRANQUIA LINE", "FRANQUIALINE", "FRAQUIALINE");
@ -973,6 +1058,9 @@ namespace line_gestao_api.Controllers
"VALOR LINE"); "VALOR LINE");
} }
var colRazao = GetColAny(map, "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAOSOCIAL");
var colNome = GetColAny(map, "NOME", "NOME COMPLETO");
var startRow = headerRowIndex + 1; var startRow = headerRowIndex + 1;
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
@ -981,7 +1069,16 @@ namespace line_gestao_api.Controllers
for (int r = startRow; r <= lastRow; r++) for (int r = startRow; r <= lastRow; r++)
{ {
var cliente = GetCellString(ws, r, colCliente); var cliente = colCliente > 0 ? GetCellString(ws, r, colCliente) : "";
var nome = colNome > 0 ? GetCellString(ws, r, colNome) : "";
var razao = colRazao > 0 ? GetCellString(ws, r, colRazao) : "";
if (string.IsNullOrWhiteSpace(cliente))
{
if (!string.IsNullOrWhiteSpace(razao)) cliente = razao;
else if (!string.IsNullOrWhiteSpace(nome)) cliente = nome;
}
if (string.IsNullOrWhiteSpace(cliente)) break; if (string.IsNullOrWhiteSpace(cliente)) break;
seqItem++; seqItem++;
@ -1064,7 +1161,9 @@ namespace line_gestao_api.Controllers
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" ||
NormalizeHeader(c.GetString()) == "CLIENTE")); NormalizeHeader(c.GetString()) == "CLIENTE" ||
NormalizeHeader(c.GetString()) == "RAZAO SOCIAL" ||
NormalizeHeader(c.GetString()) == "NOME"));
if (headerRow == null) return; if (headerRow == null) return;
@ -1072,9 +1171,11 @@ namespace line_gestao_api.Controllers
var colItem = GetCol(map, "ITEM"); var colItem = GetCol(map, "ITEM");
var colCliente = GetCol(map, "CLIENTE"); var colCliente = GetCol(map, "CLIENTE");
var colRazao = GetColAny(map, "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAOSOCIAL");
var colNome = GetColAny(map, "NOME", "NOME COMPLETO");
var colLinha = GetCol(map, "LINHA"); var colLinha = GetCol(map, "LINHA");
if (colCliente == 0) return; if (colCliente == 0 && colRazao == 0 && colNome == 0) return;
await _db.UserDatas.ExecuteDeleteAsync(); await _db.UserDatas.ExecuteDeleteAsync();
@ -1085,6 +1186,7 @@ namespace line_gestao_api.Controllers
var seq = 0; var seq = 0;
var colCpf = GetColAny(map, "CPF"); var colCpf = GetColAny(map, "CPF");
var colCnpj = GetColAny(map, "CNPJ");
var colRg = GetColAny(map, "RG"); var colRg = GetColAny(map, "RG");
var colEmail = GetColAny(map, "EMAIL", "E-MAIL"); var colEmail = GetColAny(map, "EMAIL", "E-MAIL");
var colEndereco = GetColAny(map, "ENDERECO", "ENDEREÇO"); var colEndereco = GetColAny(map, "ENDERECO", "ENDEREÇO");
@ -1101,8 +1203,16 @@ namespace line_gestao_api.Controllers
for (int r = startRow; r <= lastRow; r++) for (int r = startRow; r <= lastRow; r++)
{ {
var cliente = GetCellString(ws, r, colCliente); var cliente = colCliente > 0 ? GetCellString(ws, r, colCliente) : "";
if (string.IsNullOrWhiteSpace(cliente)) break; var razao = colRazao > 0 ? GetCellString(ws, r, colRazao) : "";
var nome = colNome > 0 ? GetCellString(ws, r, colNome) : "";
if (string.IsNullOrWhiteSpace(cliente) && string.IsNullOrWhiteSpace(razao) && string.IsNullOrWhiteSpace(nome)) break;
if (string.IsNullOrWhiteSpace(cliente))
{
cliente = !string.IsNullOrWhiteSpace(razao) ? razao : nome;
}
seq++; seq++;
@ -1117,6 +1227,7 @@ namespace line_gestao_api.Controllers
var linha = colLinha > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colLinha)) : null; var linha = colLinha > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colLinha)) : null;
var cpf = colCpf > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colCpf)) : null; var cpf = colCpf > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colCpf)) : null;
var cnpj = colCnpj > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colCnpj)) : null;
var rg = colRg > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colRg)) : null; var rg = colRg > 0 ? NullIfEmptyDigits(GetCellString(ws, r, colRg)) : null;
DateTime? dataNascimento = null; DateTime? dataNascimento = null;
@ -1131,6 +1242,13 @@ namespace line_gestao_api.Controllers
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var tipoPessoa = !string.IsNullOrWhiteSpace(cnpj) || !string.IsNullOrWhiteSpace(razao)
? "PJ"
: "PF";
var nomeFinal = string.IsNullOrWhiteSpace(nome) ? cliente : nome.Trim();
var razaoFinal = string.IsNullOrWhiteSpace(razao) ? cliente : razao.Trim();
var e = new UserData var e = new UserData
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
@ -1138,6 +1256,10 @@ namespace line_gestao_api.Controllers
Linha = linha, Linha = linha,
Cliente = cliente.Trim(), Cliente = cliente.Trim(),
TipoPessoa = tipoPessoa,
Nome = tipoPessoa == "PF" ? nomeFinal : null,
RazaoSocial = tipoPessoa == "PJ" ? razaoFinal : null,
Cnpj = tipoPessoa == "PJ" ? cnpj : null,
Cpf = cpf, Cpf = cpf,
Rg = rg, Rg = rg,
DataNascimento = ToUtc(dataNascimento), DataNascimento = ToUtc(dataNascimento),
@ -2022,9 +2144,12 @@ namespace line_gestao_api.Controllers
var buffer = new List<ResumoReservaLine>(200); var buffer = new List<ResumoReservaLine>(200);
string? lastDddValid = null; string? lastDddValid = null;
decimal? lastTotalForDdd = null;
var dataStarted = false; var dataStarted = false;
var emptyRowStreak = 0; var emptyRowStreak = 0;
int? totalRowIndex = null; int? totalRowIndex = null;
var totalsFromSheetByDdd = new Dictionary<string, decimal?>();
var sumQtdByDdd = new Dictionary<string, int>();
for (int r = headerRow + 1; r <= lastRowUsed; r++) for (int r = headerRow + 1; r <= lastRowUsed; r++)
{ {
@ -2050,23 +2175,36 @@ namespace line_gestao_api.Controllers
emptyRowStreak = 0; emptyRowStreak = 0;
var franquiaValue = TryDecimal(franquia);
var qtdValue = TryNullableInt(qtdLinhas);
var isDataRow = franquiaValue.HasValue || qtdValue.HasValue;
var dddCandidate = NullIfEmptyDigits(ddd); var dddCandidate = NullIfEmptyDigits(ddd);
var hasFranquiaText = !string.IsNullOrWhiteSpace(franquia);
var hasQtdText = !string.IsNullOrWhiteSpace(qtdLinhas);
var hasTotalText = !string.IsNullOrWhiteSpace(total);
if (!string.IsNullOrWhiteSpace(dddCandidate)) // ✅ Rodapé "TOTAL GERAL" (DDD vazio + franquia vazia + qtd + total preenchidos)
{ var isTotalGeralRow = string.IsNullOrWhiteSpace(dddCandidate)
lastDddValid = dddCandidate; && !hasFranquiaText
} && hasQtdText
&& hasTotalText;
var isTotalRow = !isDataRow && !string.IsNullOrWhiteSpace(total); if (isTotalGeralRow)
if (isTotalRow)
{ {
totalRowIndex = r; totalRowIndex = r;
break; break;
} }
var franquiaValue = TryDecimal(franquia);
var qtdValue = TryNullableInt(qtdLinhas);
var isDataRow = franquiaValue.HasValue || qtdValue.HasValue;
if (!string.IsNullOrWhiteSpace(dddCandidate))
{
if (!string.Equals(lastDddValid, dddCandidate, StringComparison.OrdinalIgnoreCase))
{
lastTotalForDdd = null;
}
lastDddValid = dddCandidate;
}
if (!isDataRow && dataStarted) if (!isDataRow && dataStarted)
{ {
break; break;
@ -2079,13 +2217,26 @@ namespace line_gestao_api.Controllers
: dddCandidate; : dddCandidate;
var totalValue = TryDecimal(total); var totalValue = TryDecimal(total);
if (!string.IsNullOrWhiteSpace(resolvedDdd) && totalValue.HasValue)
{
lastTotalForDdd = totalValue;
totalsFromSheetByDdd[resolvedDdd] = totalValue;
}
if (!string.IsNullOrWhiteSpace(resolvedDdd) && qtdValue.HasValue)
{
if (sumQtdByDdd.TryGetValue(resolvedDdd, out var acc))
sumQtdByDdd[resolvedDdd] = acc + qtdValue.Value;
else
sumQtdByDdd[resolvedDdd] = qtdValue.Value;
}
buffer.Add(new ResumoReservaLine buffer.Add(new ResumoReservaLine
{ {
Ddd = string.IsNullOrWhiteSpace(resolvedDdd) ? null : resolvedDdd, Ddd = string.IsNullOrWhiteSpace(resolvedDdd) ? null : resolvedDdd,
FranquiaGb = franquiaValue, FranquiaGb = franquiaValue,
QtdLinhas = qtdValue, QtdLinhas = qtdValue,
Total = totalValue, Total = totalValue ?? lastTotalForDdd,
CreatedAt = now, CreatedAt = now,
UpdatedAt = now UpdatedAt = now
}); });
@ -2103,6 +2254,33 @@ namespace line_gestao_api.Controllers
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();
} }
if (totalsFromSheetByDdd.Count > 0)
{
foreach (var kv in totalsFromSheetByDdd)
{
if (!kv.Value.HasValue) continue;
if (!sumQtdByDdd.TryGetValue(kv.Key, out var sum)) continue;
var totalInt = (int)Math.Round(kv.Value.Value);
if (totalInt != sum)
{
Console.WriteLine($"[WARN] RESUMO/RESERVA DDD {kv.Key}: TOTAL(planilha)={totalInt} vs SOMA(QTD)={sum}");
}
}
}
var totalGeralLinhasReserva = totalRowIndex.HasValue
? TryNullableInt(GetCellString(ws, totalRowIndex.Value, colQtdLinhas))
: null;
if (totalGeralLinhasReserva.HasValue)
{
var somaPorDdd = sumQtdByDdd.Values.Sum();
if (somaPorDdd != totalGeralLinhasReserva.Value)
{
Console.WriteLine($"[WARN] RESUMO/RESERVA TOTAL GERAL: planilha={totalGeralLinhasReserva.Value} vs SOMA(DDD)={somaPorDdd}");
}
}
if (totalRowIndex == null) if (totalRowIndex == null)
{ {
return; return;
@ -2110,6 +2288,7 @@ namespace line_gestao_api.Controllers
var totalEntity = new ResumoReservaTotal var totalEntity = new ResumoReservaTotal
{ {
TotalGeralLinhasReserva = totalGeralLinhasReserva,
QtdLinhasTotal = TryNullableInt(GetCellString(ws, totalRowIndex.Value, colQtdLinhas)), QtdLinhasTotal = TryNullableInt(GetCellString(ws, totalRowIndex.Value, colQtdLinhas)),
Total = TryDecimal(GetCellString(ws, totalRowIndex.Value, colTotal)), Total = TryDecimal(GetCellString(ws, totalRowIndex.Value, colTotal)),
CreatedAt = now, CreatedAt = now,
@ -2458,7 +2637,7 @@ namespace line_gestao_api.Controllers
private static void ApplyReservaRule(MobileLine x) private static void ApplyReservaRule(MobileLine x)
{ {
if ((x.Cliente ?? "").Trim().ToUpper() == "RESERVA" || (x.Usuario ?? "").Trim().ToUpper() == "RESERVA") if (IsReservaValue(x.Cliente) || IsReservaValue(x.Usuario) || IsReservaValue(x.Skil))
{ {
x.Cliente = "RESERVA"; x.Cliente = "RESERVA";
x.Usuario = "RESERVA"; x.Usuario = "RESERVA";
@ -2466,6 +2645,9 @@ namespace line_gestao_api.Controllers
} }
} }
private static bool IsReservaValue(string? value)
=> string.Equals(value?.Trim(), "RESERVA", StringComparison.OrdinalIgnoreCase);
private static int GetCol(Dictionary<string, int> map, string name) private static int GetCol(Dictionary<string, int> map, string name)
=> map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0; => map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;

View File

@ -1,5 +1,6 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -57,26 +58,54 @@ public class ParcelamentosController : ControllerBase
} }
var total = await query.CountAsync(); var total = await query.CountAsync();
var today = DateOnly.FromDateTime(DateTime.Today);
var items = await query var rows = await query
.OrderBy(x => x.Item) .OrderBy(x => x.Item)
.Skip((page - 1) * pageSize) .Skip((page - 1) * pageSize)
.Take(pageSize) .Take(pageSize)
.Select(x => new ParcelamentoListDto .Select(x => new
{
x.Id,
x.AnoRef,
x.Item,
x.Linha,
x.Cliente,
x.QtParcelas,
x.ParcelaAtual,
x.TotalParcelas,
x.ValorCheio,
x.Desconto,
x.ValorComDesconto,
ParcelaAtualCalc = x.MonthValues.Count(m => m.Competencia <= today),
TotalMeses = x.MonthValues.Count()
})
.ToListAsync();
var items = rows.Select(x =>
{
var (parcelaAtual, totalParcelas) = ResolveParcelasFromCounts(
x.ParcelaAtual,
x.TotalParcelas,
x.QtParcelas,
x.ParcelaAtualCalc,
x.TotalMeses);
return new ParcelamentoListDto
{ {
Id = x.Id, Id = x.Id,
AnoRef = x.AnoRef, AnoRef = x.AnoRef,
Item = x.Item, Item = x.Item,
Linha = x.Linha, Linha = x.Linha,
Cliente = x.Cliente, Cliente = x.Cliente,
QtParcelas = x.QtParcelas, QtParcelas = BuildQtParcelas(parcelaAtual, totalParcelas, x.QtParcelas),
ParcelaAtual = x.ParcelaAtual, ParcelaAtual = parcelaAtual,
TotalParcelas = x.TotalParcelas, TotalParcelas = totalParcelas,
ValorCheio = x.ValorCheio, ValorCheio = x.ValorCheio,
Desconto = x.Desconto, Desconto = x.Desconto,
ValorComDesconto = x.ValorComDesconto ValorComDesconto = x.ValorComDesconto
}) };
.ToListAsync(); }).ToList();
return Ok(new PagedResult<ParcelamentoListDto> return Ok(new PagedResult<ParcelamentoListDto>
{ {
@ -90,39 +119,308 @@ public class ParcelamentosController : ControllerBase
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
public async Task<ActionResult<ParcelamentoDetailDto>> GetById(Guid id) public async Task<ActionResult<ParcelamentoDetailDto>> GetById(Guid id)
{ {
var item = await _db.ParcelamentoLines var entity = await _db.ParcelamentoLines
.AsNoTracking() .AsNoTracking()
.Include(x => x.MonthValues) .Include(x => x.MonthValues)
.FirstOrDefaultAsync(x => x.Id == id); .FirstOrDefaultAsync(x => x.Id == id);
if (item == null) if (entity == null)
{ {
return NotFound(); return NotFound();
} }
var monthValues = entity.MonthValues
.OrderBy(m => m.Competencia)
.Select(m => new ParcelamentoMonthDto
{
Competencia = m.Competencia,
Valor = m.Valor
})
.ToList();
var (parcelaAtual, totalParcelas) = ResolveParcelasFromCompetencias(
entity.ParcelaAtual,
entity.TotalParcelas,
entity.QtParcelas,
monthValues.Select(m => m.Competencia));
var dto = new ParcelamentoDetailDto var dto = new ParcelamentoDetailDto
{ {
Id = item.Id, Id = entity.Id,
AnoRef = item.AnoRef, AnoRef = entity.AnoRef,
Item = item.Item, Item = entity.Item,
Linha = item.Linha, Linha = entity.Linha,
Cliente = item.Cliente, Cliente = entity.Cliente,
QtParcelas = item.QtParcelas, QtParcelas = BuildQtParcelas(parcelaAtual, totalParcelas, entity.QtParcelas),
ParcelaAtual = item.ParcelaAtual, ParcelaAtual = parcelaAtual,
TotalParcelas = item.TotalParcelas, TotalParcelas = totalParcelas,
ValorCheio = item.ValorCheio, ValorCheio = entity.ValorCheio,
Desconto = item.Desconto, Desconto = entity.Desconto,
ValorComDesconto = item.ValorComDesconto, ValorComDesconto = entity.ValorComDesconto,
MonthValues = item.MonthValues MonthValues = monthValues,
.OrderBy(x => x.Competencia) AnnualRows = BuildAnnualRows(monthValues)
.Select(x => new ParcelamentoMonthDto
{
Competencia = x.Competencia,
Valor = x.Valor
})
.ToList()
}; };
return Ok(dto); return Ok(dto);
} }
[HttpPost]
public async Task<ActionResult<ParcelamentoDetailDto>> Create([FromBody] ParcelamentoUpsertDto req)
{
var now = DateTime.UtcNow;
var entity = new ParcelamentoLine
{
Id = Guid.NewGuid(),
AnoRef = req.AnoRef,
Item = req.Item,
Linha = TrimOrNull(req.Linha),
Cliente = TrimOrNull(req.Cliente),
QtParcelas = TrimOrNull(req.QtParcelas),
ParcelaAtual = req.ParcelaAtual,
TotalParcelas = req.TotalParcelas,
ValorCheio = req.ValorCheio,
Desconto = req.Desconto,
ValorComDesconto = ResolveValorComDesconto(req.ValorComDesconto, req.ValorCheio, req.Desconto),
CreatedAt = now,
UpdatedAt = now
};
if (req.AnoRef.HasValue && req.Item.HasValue)
{
var exists = await _db.ParcelamentoLines.AnyAsync(x => x.AnoRef == req.AnoRef && x.Item == req.Item);
if (exists) return Conflict("Já existe um parcelamento com o mesmo AnoRef e Item.");
}
entity.MonthValues = BuildMonthValues(req.MonthValues, entity.Id, now);
ApplyComputedParcelas(entity);
_db.ParcelamentoLines.Add(entity);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = entity.Id }, ToDetailDto(entity));
}
[HttpPut("{id:guid}")]
public async Task<IActionResult> Update(Guid id, [FromBody] ParcelamentoUpsertDto req)
{
var entity = await _db.ParcelamentoLines
.Include(x => x.MonthValues)
.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null) return NotFound();
if (req.AnoRef.HasValue && req.Item.HasValue)
{
var exists = await _db.ParcelamentoLines.AnyAsync(x =>
x.Id != id && x.AnoRef == req.AnoRef && x.Item == req.Item);
if (exists) return Conflict("Já existe um parcelamento com o mesmo AnoRef e Item.");
}
entity.AnoRef = req.AnoRef;
entity.Item = req.Item;
entity.Linha = TrimOrNull(req.Linha);
entity.Cliente = TrimOrNull(req.Cliente);
entity.QtParcelas = TrimOrNull(req.QtParcelas);
entity.ParcelaAtual = req.ParcelaAtual;
entity.TotalParcelas = req.TotalParcelas;
entity.ValorCheio = req.ValorCheio;
entity.Desconto = req.Desconto;
entity.ValorComDesconto = ResolveValorComDesconto(req.ValorComDesconto, req.ValorCheio, req.Desconto);
entity.UpdatedAt = DateTime.UtcNow;
_db.ParcelamentoMonthValues.RemoveRange(entity.MonthValues);
entity.MonthValues = BuildMonthValues(req.MonthValues, entity.Id, entity.UpdatedAt);
ApplyComputedParcelas(entity);
await _db.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id)
{
var entity = await _db.ParcelamentoLines.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null) return NotFound();
_db.ParcelamentoLines.Remove(entity);
await _db.SaveChangesAsync();
return NoContent();
}
private static List<ParcelamentoAnnualRowDto> BuildAnnualRows(List<ParcelamentoMonthDto> monthValues)
{
if (monthValues.Count == 0) return new List<ParcelamentoAnnualRowDto>();
var groups = monthValues
.GroupBy(m => m.Competencia.Year)
.OrderBy(g => g.Key);
var rows = new List<ParcelamentoAnnualRowDto>();
foreach (var group in groups)
{
var monthMap = group
.GroupBy(m => m.Competencia.Month)
.ToDictionary(g => g.Key, g => g.Sum(x => x.Valor ?? 0m));
var months = Enumerable.Range(1, 12)
.Select(month => new ParcelamentoAnnualMonthDto
{
Month = month,
Valor = monthMap.TryGetValue(month, out var value) ? value : null
})
.ToList();
var total = months.Sum(m => m.Valor ?? 0m);
rows.Add(new ParcelamentoAnnualRowDto
{
Year = group.Key,
Total = total,
Months = months
});
}
return rows;
}
private static List<ParcelamentoMonthValue> BuildMonthValues(
List<ParcelamentoMonthInputDto> inputs,
Guid parcelamentoId,
DateTime now)
{
var map = new Dictionary<DateOnly, decimal?>();
foreach (var input in inputs ?? new List<ParcelamentoMonthInputDto>())
{
if (input.Competencia == default) continue;
map[input.Competencia] = input.Valor;
}
return map
.OrderBy(x => x.Key)
.Select(x => new ParcelamentoMonthValue
{
Id = Guid.NewGuid(),
ParcelamentoLineId = parcelamentoId,
Competencia = x.Key,
Valor = x.Value,
CreatedAt = now
})
.ToList();
}
private static void ApplyComputedParcelas(ParcelamentoLine entity)
{
var competencias = entity.MonthValues.Select(m => m.Competencia).ToList();
var (parcelaAtual, totalParcelas) = ResolveParcelasFromCompetencias(
entity.ParcelaAtual,
entity.TotalParcelas,
entity.QtParcelas,
competencias);
entity.ParcelaAtual = parcelaAtual;
entity.TotalParcelas = totalParcelas;
entity.QtParcelas = BuildQtParcelas(parcelaAtual, totalParcelas, entity.QtParcelas);
}
private static ParcelamentoDetailDto ToDetailDto(ParcelamentoLine entity)
{
var monthValues = entity.MonthValues
.OrderBy(m => m.Competencia)
.Select(m => new ParcelamentoMonthDto
{
Competencia = m.Competencia,
Valor = m.Valor
})
.ToList();
var (parcelaAtual, totalParcelas) = ResolveParcelasFromCompetencias(
entity.ParcelaAtual,
entity.TotalParcelas,
entity.QtParcelas,
monthValues.Select(m => m.Competencia));
return new ParcelamentoDetailDto
{
Id = entity.Id,
AnoRef = entity.AnoRef,
Item = entity.Item,
Linha = entity.Linha,
Cliente = entity.Cliente,
QtParcelas = BuildQtParcelas(parcelaAtual, totalParcelas, entity.QtParcelas),
ParcelaAtual = parcelaAtual,
TotalParcelas = totalParcelas,
ValorCheio = entity.ValorCheio,
Desconto = entity.Desconto,
ValorComDesconto = entity.ValorComDesconto,
MonthValues = monthValues,
AnnualRows = BuildAnnualRows(monthValues)
};
}
private static (int? ParcelaAtual, int? TotalParcelas) ResolveParcelasFromCounts(
int? storedAtual,
int? storedTotal,
string? qtParcelas,
int mesesAteHoje,
int totalMeses)
{
var total = storedTotal ?? ParseQtParcelas(qtParcelas).Total ?? (totalMeses > 0 ? totalMeses : (int?)null);
int? atual = totalMeses > 0 ? mesesAteHoje : storedAtual ?? ParseQtParcelas(qtParcelas).Atual;
if (atual.HasValue && atual.Value < 0) atual = 0;
if (total.HasValue && atual.HasValue)
{
atual = Math.Min(atual.Value, total.Value);
}
return (atual, total);
}
private static (int? ParcelaAtual, int? TotalParcelas) ResolveParcelasFromCompetencias(
int? storedAtual,
int? storedTotal,
string? qtParcelas,
IEnumerable<DateOnly> competencias)
{
var list = competencias?.ToList() ?? new List<DateOnly>();
var totalMeses = list.Count;
var hoje = DateOnly.FromDateTime(DateTime.Today);
var mesesAteHoje = list.Count(c => c <= hoje);
return ResolveParcelasFromCounts(storedAtual, storedTotal, qtParcelas, mesesAteHoje, totalMeses);
}
private static (int? Atual, int? Total) ParseQtParcelas(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return (null, null);
var parts = raw.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
int? atual = null;
int? total = null;
if (parts.Length >= 1 && int.TryParse(OnlyDigits(parts[0]), out var a)) atual = a;
if (parts.Length >= 2 && int.TryParse(OnlyDigits(parts[1]), out var t)) total = t;
return (atual, total);
}
private static string? BuildQtParcelas(int? atual, int? total, string? fallback)
{
if (atual.HasValue && total.HasValue) return $"{atual}/{total}";
return string.IsNullOrWhiteSpace(fallback) ? null : fallback.Trim();
}
private static decimal? ResolveValorComDesconto(decimal? valorCom, decimal? valorCheio, decimal? desconto)
{
if (valorCom.HasValue) return valorCom;
if (!valorCheio.HasValue) return null;
return Math.Max(0, valorCheio.Value - (desconto ?? 0m));
}
private static string? TrimOrNull(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return null;
return s.Trim();
}
private static string OnlyDigits(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return "";
return new string(s.Where(char.IsDigit).ToArray());
}
} }

View File

@ -59,9 +59,9 @@ namespace line_gestao_api.Controllers
var bloqueados = bloqueadosPerdaRoubo + bloqueados120Dias + bloqueadosOutros; var bloqueados = bloqueadosPerdaRoubo + bloqueados120Dias + bloqueadosOutros;
var reservas = await qLines.CountAsync(x => var reservas = await qLines.CountAsync(x =>
(x.Cliente ?? "").ToUpper() == "RESERVA" || EF.Functions.ILike((x.Cliente ?? "").Trim(), "%RESERVA%") ||
(x.Usuario ?? "").ToUpper() == "RESERVA" || EF.Functions.ILike((x.Usuario ?? "").Trim(), "%RESERVA%") ||
(x.Skil ?? "").ToUpper() == "RESERVA"); EF.Functions.ILike((x.Skil ?? "").Trim(), "%RESERVA%"));
var topClientes = await qLines var topClientes = await qLines
.Where(x => x.Cliente != null && x.Cliente != "") .Where(x => x.Cliente != null && x.Cliente != "")

View File

@ -21,6 +21,32 @@ public class ResumoController : ControllerBase
[HttpGet] [HttpGet]
public async Task<ActionResult<ResumoResponseDto>> GetResumo() public async Task<ActionResult<ResumoResponseDto>> GetResumo()
{ {
var reservaLines = await _db.ResumoReservaLines.AsNoTracking()
.OrderBy(x => x.Ddd)
.ToListAsync();
var reservaPorDdd = reservaLines
.Where(x => !string.IsNullOrWhiteSpace(x.Ddd))
.GroupBy(x => x.Ddd!.Trim())
.Select(g => new ResumoReservaPorDddDto
{
Ddd = g.Key,
TotalLinhas = g.Sum(x => x.QtdLinhas ?? 0),
PorFranquia = g.GroupBy(x => x.FranquiaGb)
.Select(fg => new ResumoReservaPorFranquiaDto
{
FranquiaGb = fg.Key,
TotalLinhas = fg.Sum(x => x.QtdLinhas ?? 0)
})
.OrderBy(x => x.FranquiaGb)
.ToList()
})
.OrderBy(x => x.Ddd)
.ToList();
var reservaTotalEntity = await _db.ResumoReservaTotals.AsNoTracking()
.FirstOrDefaultAsync();
var response = new ResumoResponseDto var response = new ResumoResponseDto
{ {
MacrophonyPlans = await _db.ResumoMacrophonyPlans.AsNoTracking() MacrophonyPlans = await _db.ResumoMacrophonyPlans.AsNoTracking()
@ -105,8 +131,7 @@ public class ResumoController : ControllerBase
QtdLinhas = x.QtdLinhas QtdLinhas = x.QtdLinhas
}) })
.ToListAsync(), .ToListAsync(),
ReservaLines = await _db.ResumoReservaLines.AsNoTracking() ReservaLines = reservaLines
.OrderBy(x => x.Ddd)
.Select(x => new ResumoReservaLineDto .Select(x => new ResumoReservaLineDto
{ {
Ddd = x.Ddd, Ddd = x.Ddd,
@ -114,14 +139,16 @@ public class ResumoController : ControllerBase
QtdLinhas = x.QtdLinhas, QtdLinhas = x.QtdLinhas,
Total = x.Total Total = x.Total
}) })
.ToListAsync(), .ToList(),
ReservaTotal = await _db.ResumoReservaTotals.AsNoTracking() ReservaPorDdd = reservaPorDdd,
.Select(x => new ResumoReservaTotalDto TotalGeralLinhasReserva = reservaTotalEntity?.TotalGeralLinhasReserva
?? reservaTotalEntity?.QtdLinhasTotal
?? reservaPorDdd.Sum(x => x.TotalLinhas),
ReservaTotal = reservaTotalEntity == null ? null : new ResumoReservaTotalDto
{ {
QtdLinhasTotal = x.QtdLinhasTotal, QtdLinhasTotal = reservaTotalEntity.QtdLinhasTotal,
Total = x.Total Total = reservaTotalEntity.Total
}) }
.FirstOrDefaultAsync()
}; };
return Ok(response); return Ok(response);

View File

@ -1,7 +1,10 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace line_gestao_api.Controllers namespace line_gestao_api.Controllers
{ {
@ -19,6 +22,7 @@ namespace line_gestao_api.Controllers
public async Task<ActionResult<PagedResult<UserDataListDto>>> GetAll( public async Task<ActionResult<PagedResult<UserDataListDto>>> GetAll(
[FromQuery] string? search, [FromQuery] string? search,
[FromQuery] string? client, // Filtro por cliente [FromQuery] string? client, // Filtro por cliente
[FromQuery] string? tipo, // PF/PJ
[FromQuery] int page = 1, [FromQuery] int page = 1,
[FromQuery] int pageSize = 20, [FromQuery] int pageSize = 20,
[FromQuery] string? sortBy = "item", [FromQuery] string? sortBy = "item",
@ -36,6 +40,12 @@ namespace line_gestao_api.Controllers
q = q.Where(x => x.Cliente == c); q = q.Where(x => x.Cliente == c);
} }
if (!string.IsNullOrWhiteSpace(tipo))
{
var t = tipo.Trim().ToUpperInvariant();
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
}
// Busca global // Busca global
if (!string.IsNullOrWhiteSpace(search)) if (!string.IsNullOrWhiteSpace(search))
{ {
@ -43,7 +53,10 @@ namespace line_gestao_api.Controllers
q = q.Where(x => q = q.Where(x =>
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") || EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") || EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Nome ?? "", $"%{s}%") ||
EF.Functions.ILike(x.RazaoSocial ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Cpf ?? "", $"%{s}%") || EF.Functions.ILike(x.Cpf ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Cnpj ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Email ?? "", $"%{s}%") || EF.Functions.ILike(x.Email ?? "", $"%{s}%") ||
EF.Functions.ILike(x.Celular ?? "", $"%{s}%")); EF.Functions.ILike(x.Celular ?? "", $"%{s}%"));
} }
@ -70,6 +83,10 @@ namespace line_gestao_api.Controllers
Item = x.Item, Item = x.Item,
Linha = x.Linha, Linha = x.Linha,
Cliente = x.Cliente, Cliente = x.Cliente,
TipoPessoa = x.TipoPessoa,
Nome = x.Nome,
RazaoSocial = x.RazaoSocial,
Cnpj = x.Cnpj,
Cpf = x.Cpf, Cpf = x.Cpf,
Rg = x.Rg, Rg = x.Rg,
DataNascimento = x.DataNascimento != null ? x.DataNascimento.Value.ToString("yyyy-MM-dd") : null, DataNascimento = x.DataNascimento != null ? x.DataNascimento.Value.ToString("yyyy-MM-dd") : null,
@ -95,6 +112,7 @@ namespace line_gestao_api.Controllers
[HttpGet("groups")] [HttpGet("groups")]
public async Task<ActionResult<UserDataGroupResponse>> GetGroups( public async Task<ActionResult<UserDataGroupResponse>> GetGroups(
[FromQuery] string? search, [FromQuery] string? search,
[FromQuery] string? tipo,
[FromQuery] int page = 1, [FromQuery] int page = 1,
[FromQuery] int pageSize = 10, [FromQuery] int pageSize = 10,
[FromQuery] string? sortBy = "cliente", [FromQuery] string? sortBy = "cliente",
@ -106,6 +124,12 @@ namespace line_gestao_api.Controllers
var q = _db.UserDatas.AsNoTracking() var q = _db.UserDatas.AsNoTracking()
.Where(x => x.Cliente != null && x.Cliente != ""); .Where(x => x.Cliente != null && x.Cliente != "");
if (!string.IsNullOrWhiteSpace(tipo))
{
var t = tipo.Trim().ToUpperInvariant();
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
}
if (!string.IsNullOrWhiteSpace(search)) if (!string.IsNullOrWhiteSpace(search))
{ {
var s = search.Trim(); var s = search.Trim();
@ -118,6 +142,7 @@ namespace line_gestao_api.Controllers
TotalRegistros = await q.CountAsync(), TotalRegistros = await q.CountAsync(),
ClientesUnicos = await q.Select(x => x.Cliente).Distinct().CountAsync(), ClientesUnicos = await q.Select(x => x.Cliente).Distinct().CountAsync(),
ComCpf = await q.CountAsync(x => x.Cpf != null && x.Cpf != ""), ComCpf = await q.CountAsync(x => x.Cpf != null && x.Cpf != ""),
ComCnpj = await q.CountAsync(x => x.Cnpj != null && x.Cnpj != ""),
ComEmail = await q.CountAsync(x => x.Email != null && x.Email != "") ComEmail = await q.CountAsync(x => x.Email != null && x.Email != "")
}; };
@ -129,6 +154,7 @@ namespace line_gestao_api.Controllers
Cliente = g.Key, Cliente = g.Key,
TotalRegistros = g.Count(), TotalRegistros = g.Count(),
ComCpf = g.Count(x => x.Cpf != null && x.Cpf != ""), ComCpf = g.Count(x => x.Cpf != null && x.Cpf != ""),
ComCnpj = g.Count(x => x.Cnpj != null && x.Cnpj != ""),
ComEmail = g.Count(x => x.Email != null && x.Email != "") ComEmail = g.Count(x => x.Email != null && x.Email != "")
}); });
@ -158,9 +184,17 @@ namespace line_gestao_api.Controllers
} }
[HttpGet("clients")] [HttpGet("clients")]
public async Task<ActionResult<List<string>>> GetClients() public async Task<ActionResult<List<string>>> GetClients([FromQuery] string? tipo)
{ {
return await _db.UserDatas.AsNoTracking() var q = _db.UserDatas.AsNoTracking();
if (!string.IsNullOrWhiteSpace(tipo))
{
var t = tipo.Trim().ToUpperInvariant();
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
}
return await q
.Where(x => !string.IsNullOrEmpty(x.Cliente)) .Where(x => !string.IsNullOrEmpty(x.Cliente))
.Select(x => x.Cliente!) .Select(x => x.Cliente!)
.Distinct() .Distinct()
@ -180,6 +214,10 @@ namespace line_gestao_api.Controllers
Item = x.Item, Item = x.Item,
Linha = x.Linha, Linha = x.Linha,
Cliente = x.Cliente, Cliente = x.Cliente,
TipoPessoa = x.TipoPessoa,
Nome = x.Nome,
RazaoSocial = x.RazaoSocial,
Cnpj = x.Cnpj,
Cpf = x.Cpf, Cpf = x.Cpf,
Rg = x.Rg, Rg = x.Rg,
Email = x.Email, Email = x.Email,
@ -189,5 +227,184 @@ namespace line_gestao_api.Controllers
DataNascimento = x.DataNascimento DataNascimento = x.DataNascimento
}); });
} }
[HttpPost]
[Authorize(Roles = "admin")]
public async Task<ActionResult<UserDataDetailDto>> Create([FromBody] CreateUserDataRequest req)
{
var now = DateTime.UtcNow;
var linha = TrimOrNull(req.Linha);
var cliente = TrimOrNull(req.Cliente);
var nome = TrimOrNull(req.Nome);
var razao = TrimOrNull(req.RazaoSocial);
var cpf = NullIfEmptyDigits(req.Cpf);
var cnpj = NullIfEmptyDigits(req.Cnpj);
var tipo = NormalizeTipo(req.TipoPessoa, cpf, cnpj, razao, nome);
if (string.IsNullOrWhiteSpace(cliente) && !string.IsNullOrWhiteSpace(linha))
{
var linhaDigits = OnlyDigits(linha);
MobileLine? mobile = null;
if (!string.IsNullOrWhiteSpace(linhaDigits))
{
mobile = await _db.MobileLines.AsNoTracking()
.FirstOrDefaultAsync(x => x.Linha == linhaDigits);
}
else
{
var raw = linha.Trim();
mobile = await _db.MobileLines.AsNoTracking()
.FirstOrDefaultAsync(x => EF.Functions.ILike(x.Linha ?? "", raw));
}
if (mobile != null)
{
cliente = mobile.Cliente;
if (string.IsNullOrWhiteSpace(tipo))
{
var skil = (mobile.Skil ?? "").Trim().ToUpperInvariant();
if (skil.Contains("JUR")) tipo = "PJ";
else if (skil.Contains("FIS")) tipo = "PF";
}
}
}
if (string.IsNullOrWhiteSpace(cliente))
{
cliente = !string.IsNullOrWhiteSpace(razao) ? razao : nome;
}
if (string.IsNullOrWhiteSpace(nome) && tipo == "PF") nome = cliente;
if (string.IsNullOrWhiteSpace(razao) && tipo == "PJ") razao = cliente;
var item = req.Item;
if (!item.HasValue || item.Value <= 0)
{
var maxItem = await _db.UserDatas.MaxAsync(x => (int?)x.Item) ?? 0;
item = maxItem + 1;
}
var e = new UserData
{
Id = Guid.NewGuid(),
Item = item.Value,
Linha = linha,
Cliente = cliente,
TipoPessoa = tipo ?? "PF",
Nome = nome,
RazaoSocial = razao,
Cnpj = cnpj,
Cpf = cpf,
Rg = TrimOrNull(req.Rg),
Email = TrimOrNull(req.Email),
Endereco = TrimOrNull(req.Endereco),
Celular = TrimOrNull(req.Celular),
TelefoneFixo = TrimOrNull(req.TelefoneFixo),
DataNascimento = req.DataNascimento.HasValue ? ToUtc(req.DataNascimento.Value) : null,
CreatedAt = now,
UpdatedAt = now
};
_db.UserDatas.Add(e);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = e.Id }, new UserDataDetailDto
{
Id = e.Id,
Item = e.Item,
Linha = e.Linha,
Cliente = e.Cliente,
TipoPessoa = e.TipoPessoa,
Nome = e.Nome,
RazaoSocial = e.RazaoSocial,
Cnpj = e.Cnpj,
Cpf = e.Cpf,
Rg = e.Rg,
Email = e.Email,
Celular = e.Celular,
Endereco = e.Endereco,
TelefoneFixo = e.TelefoneFixo,
DataNascimento = e.DataNascimento
});
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateUserDataRequest req)
{
var x = await _db.UserDatas.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
if (req.Item.HasValue) x.Item = req.Item.Value;
if (req.Linha != null) x.Linha = TrimOrNull(req.Linha);
if (req.Cliente != null) x.Cliente = TrimOrNull(req.Cliente);
if (req.TipoPessoa != null) x.TipoPessoa = NormalizeTipo(req.TipoPessoa, req.Cpf, req.Cnpj, req.RazaoSocial, req.Nome) ?? x.TipoPessoa;
if (req.Nome != null) x.Nome = TrimOrNull(req.Nome);
if (req.RazaoSocial != null) x.RazaoSocial = TrimOrNull(req.RazaoSocial);
if (req.Cnpj != null) x.Cnpj = NullIfEmptyDigits(req.Cnpj);
if (req.Cpf != null) x.Cpf = NullIfEmptyDigits(req.Cpf);
if (req.Rg != null) x.Rg = TrimOrNull(req.Rg);
if (req.Email != null) x.Email = TrimOrNull(req.Email);
if (req.Endereco != null) x.Endereco = TrimOrNull(req.Endereco);
if (req.Celular != null) x.Celular = TrimOrNull(req.Celular);
if (req.TelefoneFixo != null) x.TelefoneFixo = TrimOrNull(req.TelefoneFixo);
if (req.DataNascimento.HasValue)
{
x.DataNascimento = ToUtc(req.DataNascimento.Value);
}
x.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id)
{
var x = await _db.UserDatas.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
_db.UserDatas.Remove(x);
await _db.SaveChangesAsync();
return NoContent();
}
private static string? TrimOrNull(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return null;
return s.Trim();
}
private static DateTime ToUtc(DateTime dt)
{
return dt.Kind == DateTimeKind.Utc ? dt
: (dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : DateTime.SpecifyKind(dt, DateTimeKind.Utc));
}
private static string? NormalizeTipo(string? tipo, string? cpf, string? cnpj, string? razao, string? nome)
{
var t = (tipo ?? "").Trim().ToUpperInvariant();
if (t == "PF" || t == "PJ") return t;
if (!string.IsNullOrWhiteSpace(cnpj) || !string.IsNullOrWhiteSpace(razao)) return "PJ";
if (!string.IsNullOrWhiteSpace(cpf) || !string.IsNullOrWhiteSpace(nome)) return "PF";
return null;
}
private static string? NullIfEmptyDigits(string? s)
{
var d = OnlyDigits(s);
return string.IsNullOrWhiteSpace(d) ? null : d;
}
private static string OnlyDigits(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return "";
return new string(s.Where(char.IsDigit).ToArray());
}
} }
} }

View File

@ -1,7 +1,11 @@
using line_gestao_api.Data; using line_gestao_api.Data;
using line_gestao_api.Dtos; using line_gestao_api.Dtos;
using line_gestao_api.Models;
using line_gestao_api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace line_gestao_api.Controllers namespace line_gestao_api.Controllers
{ {
@ -181,5 +185,176 @@ namespace line_gestao_api.Controllers
.OrderBy(x => x) .OrderBy(x => x)
.ToListAsync(); .ToListAsync();
} }
[HttpGet("{id:guid}")]
public async Task<ActionResult<VigenciaLineDetailDto>> GetById(Guid id)
{
var x = await _db.VigenciaLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
return Ok(new VigenciaLineDetailDto
{
Id = x.Id,
Item = x.Item,
Conta = x.Conta,
Linha = x.Linha,
Cliente = x.Cliente,
Usuario = x.Usuario,
PlanoContrato = x.PlanoContrato,
DtEfetivacaoServico = x.DtEfetivacaoServico,
DtTerminoFidelizacao = x.DtTerminoFidelizacao,
Total = x.Total,
CreatedAt = x.CreatedAt,
UpdatedAt = x.UpdatedAt
});
}
[HttpPost]
[Authorize(Roles = "admin")]
public async Task<ActionResult<VigenciaLineDetailDto>> Create([FromBody] CreateVigenciaRequest req)
{
var now = DateTime.UtcNow;
var linha = TrimOrNull(req.Linha);
var conta = TrimOrNull(req.Conta);
var cliente = TrimOrNull(req.Cliente);
var usuario = TrimOrNull(req.Usuario);
var plano = TrimOrNull(req.PlanoContrato);
MobileLine? mobile = null;
if (!string.IsNullOrWhiteSpace(linha))
{
var linhaDigits = OnlyDigits(linha);
if (!string.IsNullOrWhiteSpace(linhaDigits))
{
mobile = await _db.MobileLines.AsNoTracking()
.FirstOrDefaultAsync(x => x.Linha == linhaDigits);
}
else
{
var raw = linha.Trim();
mobile = await _db.MobileLines.AsNoTracking()
.FirstOrDefaultAsync(x => EF.Functions.ILike(x.Linha ?? "", raw));
}
if (mobile != null)
{
conta ??= mobile.Conta;
cliente ??= mobile.Cliente;
usuario ??= mobile.Usuario;
plano ??= mobile.PlanoContrato;
}
}
decimal? total = req.Total;
if (!total.HasValue && mobile?.ValorPlanoVivo != null)
{
total = mobile.ValorPlanoVivo;
}
if (!total.HasValue && !string.IsNullOrWhiteSpace(plano))
{
var planSuggestion = await AutoFillRules.ResolvePlanSuggestionAsync(_db, plano);
total = planSuggestion?.ValorPlano;
}
var item = req.Item;
if (!item.HasValue || item.Value <= 0)
{
var maxItem = await _db.VigenciaLines.MaxAsync(x => (int?)x.Item) ?? 0;
item = maxItem + 1;
}
var e = new VigenciaLine
{
Id = Guid.NewGuid(),
Item = item.Value,
Conta = conta,
Linha = linha,
Cliente = cliente,
Usuario = usuario,
PlanoContrato = plano,
DtEfetivacaoServico = req.DtEfetivacaoServico.HasValue ? ToUtc(req.DtEfetivacaoServico.Value) : null,
DtTerminoFidelizacao = req.DtTerminoFidelizacao.HasValue ? ToUtc(req.DtTerminoFidelizacao.Value) : null,
Total = total,
CreatedAt = now,
UpdatedAt = now
};
_db.VigenciaLines.Add(e);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = e.Id }, new VigenciaLineDetailDto
{
Id = e.Id,
Item = e.Item,
Conta = e.Conta,
Linha = e.Linha,
Cliente = e.Cliente,
Usuario = e.Usuario,
PlanoContrato = e.PlanoContrato,
DtEfetivacaoServico = e.DtEfetivacaoServico,
DtTerminoFidelizacao = e.DtTerminoFidelizacao,
Total = e.Total,
CreatedAt = e.CreatedAt,
UpdatedAt = e.UpdatedAt
});
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateVigenciaRequest req)
{
var x = await _db.VigenciaLines.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
if (req.Item.HasValue) x.Item = req.Item.Value;
if (req.Conta != null) x.Conta = TrimOrNull(req.Conta);
if (req.Linha != null) x.Linha = TrimOrNull(req.Linha);
if (req.Cliente != null) x.Cliente = TrimOrNull(req.Cliente);
if (req.Usuario != null) x.Usuario = TrimOrNull(req.Usuario);
if (req.PlanoContrato != null) x.PlanoContrato = TrimOrNull(req.PlanoContrato);
if (req.DtEfetivacaoServico.HasValue) x.DtEfetivacaoServico = ToUtc(req.DtEfetivacaoServico.Value);
if (req.DtTerminoFidelizacao.HasValue) x.DtTerminoFidelizacao = ToUtc(req.DtTerminoFidelizacao.Value);
if (req.Total.HasValue) x.Total = req.Total.Value;
x.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id)
{
var x = await _db.VigenciaLines.FirstOrDefaultAsync(a => a.Id == id);
if (x == null) return NotFound();
_db.VigenciaLines.Remove(x);
await _db.SaveChangesAsync();
return NoContent();
}
private static string? TrimOrNull(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return null;
return s.Trim();
}
private static DateTime ToUtc(DateTime dt)
{
return dt.Kind == DateTimeKind.Utc ? dt
: (dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : DateTime.SpecifyKind(dt, DateTimeKind.Utc));
}
private static string OnlyDigits(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return "";
return new string(s.Where(char.IsDigit).ToArray());
}
} }
} }

View File

@ -9,10 +9,15 @@ namespace line_gestao_api.Data;
public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid> public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{ {
private readonly ITenantProvider _tenantProvider; private readonly ITenantProvider _tenantProvider;
private readonly IAuditLogBuilder _auditLogBuilder;
public AppDbContext(DbContextOptions<AppDbContext> options, ITenantProvider tenantProvider) : base(options) public AppDbContext(
DbContextOptions<AppDbContext> options,
ITenantProvider tenantProvider,
IAuditLogBuilder auditLogBuilder) : base(options)
{ {
_tenantProvider = tenantProvider; _tenantProvider = tenantProvider;
_auditLogBuilder = auditLogBuilder;
} }
public DbSet<Tenant> Tenants => Set<Tenant>(); public DbSet<Tenant> Tenants => Set<Tenant>();
@ -60,6 +65,9 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
public DbSet<ParcelamentoLine> ParcelamentoLines => Set<ParcelamentoLine>(); public DbSet<ParcelamentoLine> ParcelamentoLines => Set<ParcelamentoLine>();
public DbSet<ParcelamentoMonthValue> ParcelamentoMonthValues => Set<ParcelamentoMonthValue>(); public DbSet<ParcelamentoMonthValue> ParcelamentoMonthValues => Set<ParcelamentoMonthValue>();
// ✅ tabela AUDIT LOGS (Histórico)
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@ -132,7 +140,6 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
// ========================= // =========================
// ✅ DADOS DOS USUÁRIOS (UserData) // ✅ DADOS DOS USUÁRIOS (UserData)
// ✅ (SEM "Nome" pq não existe no model)
// ========================= // =========================
modelBuilder.Entity<UserData>(e => modelBuilder.Entity<UserData>(e =>
{ {
@ -141,6 +148,10 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
e.HasIndex(x => x.Linha); e.HasIndex(x => x.Linha);
e.HasIndex(x => x.Cpf); e.HasIndex(x => x.Cpf);
e.HasIndex(x => x.Email); e.HasIndex(x => x.Email);
e.HasIndex(x => x.TipoPessoa);
e.HasIndex(x => x.Cnpj);
e.HasIndex(x => x.Nome);
e.HasIndex(x => x.RazaoSocial);
e.HasIndex(x => x.TenantId); e.HasIndex(x => x.TenantId);
}); });
@ -248,6 +259,30 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
}); });
// =========================
// ✅ AUDIT LOGS
// =========================
modelBuilder.Entity<AuditLog>(e =>
{
e.Property(x => x.Action).HasMaxLength(20);
e.Property(x => x.Page).HasMaxLength(80);
e.Property(x => x.EntityName).HasMaxLength(120);
e.Property(x => x.EntityId).HasMaxLength(200);
e.Property(x => x.EntityLabel).HasMaxLength(255);
e.Property(x => x.UserName).HasMaxLength(200);
e.Property(x => x.UserEmail).HasMaxLength(200);
e.Property(x => x.RequestPath).HasMaxLength(255);
e.Property(x => x.RequestMethod).HasMaxLength(10);
e.Property(x => x.IpAddress).HasMaxLength(80);
e.Property(x => x.ChangesJson).HasColumnType("jsonb");
e.HasIndex(x => x.TenantId);
e.HasIndex(x => x.OccurredAtUtc);
e.HasIndex(x => x.Page);
e.HasIndex(x => x.UserId);
e.HasIndex(x => x.EntityName);
});
modelBuilder.Entity<MobileLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<MobileLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<MuregLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<MuregLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<BillingClient>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<BillingClient>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
@ -269,18 +304,21 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
modelBuilder.Entity<ResumoReservaTotal>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<ResumoReservaTotal>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<ParcelamentoLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<ParcelamentoLine>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<ParcelamentoMonthValue>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<ParcelamentoMonthValue>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<AuditLog>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
modelBuilder.Entity<ApplicationUser>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId); modelBuilder.Entity<ApplicationUser>().HasQueryFilter(x => _tenantProvider.TenantId != null && x.TenantId == _tenantProvider.TenantId);
} }
public override int SaveChanges() public override int SaveChanges()
{ {
ApplyTenantIds(); ApplyTenantIds();
AddAuditLogs();
return base.SaveChanges(); return base.SaveChanges();
} }
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
ApplyTenantIds(); ApplyTenantIds();
AddAuditLogs();
return base.SaveChangesAsync(cancellationToken); return base.SaveChangesAsync(cancellationToken);
} }
@ -300,4 +338,16 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
} }
} }
} }
private void AddAuditLogs()
{
if (_auditLogBuilder == null)
return;
var logs = _auditLogBuilder.BuildAuditLogs(ChangeTracker);
if (logs.Count > 0)
{
AuditLogs.AddRange(logs);
}
}
} }

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace line_gestao_api.Dtos
{
public class AccountCompanyDto
{
public string Empresa { get; set; } = "";
public List<string> Contas { get; set; } = new();
}
}

34
Dtos/AuditLogDtos.cs Normal file
View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace line_gestao_api.Dtos;
public class AuditLogDto
{
public Guid Id { get; set; }
public DateTime OccurredAtUtc { get; set; }
public string Action { get; set; } = string.Empty;
public string Page { get; set; } = string.Empty;
public string EntityName { get; set; } = string.Empty;
public string? EntityId { get; set; }
public string? EntityLabel { get; set; }
public Guid? UserId { get; set; }
public string? UserName { get; set; }
public string? UserEmail { get; set; }
public string? RequestPath { get; set; }
public string? RequestMethod { get; set; }
public string? IpAddress { get; set; }
public List<AuditFieldChangeDto> Changes { get; set; } = new();
}
public class AuditFieldChangeDto
{
public string Field { get; set; } = string.Empty;
public string ChangeType { get; set; } = string.Empty;
public string? OldValue { get; set; }
public string? NewValue { get; set; }
}

View File

@ -17,4 +17,25 @@
public string? Aparelho { get; set; } public string? Aparelho { get; set; }
public string? FormaPagamento { get; set; } public string? FormaPagamento { get; set; }
} }
public class BillingClientDetailDto : BillingClientListDto
{
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class UpdateBillingClientRequest
{
public string? Tipo { get; set; }
public int? Item { get; set; }
public string? Cliente { get; set; }
public int? QtdLinhas { get; set; }
public decimal? FranquiaVivo { get; set; }
public decimal? ValorContratoVivo { get; set; }
public decimal? FranquiaLine { get; set; }
public decimal? ValorContratoLine { get; set; }
public decimal? Lucro { get; set; }
public string? Aparelho { get; set; }
public string? FormaPagamento { get; set; }
}
} }

View File

@ -21,9 +21,44 @@ public sealed class ParcelamentoMonthDto
public decimal? Valor { get; set; } public decimal? Valor { get; set; }
} }
public sealed class ParcelamentoMonthInputDto
{
public DateOnly Competencia { get; set; }
public decimal? Valor { get; set; }
}
public sealed class ParcelamentoUpsertDto
{
public int? AnoRef { get; set; }
public int? Item { get; set; }
public string? Linha { get; set; }
public string? Cliente { get; set; }
public string? QtParcelas { get; set; }
public int? ParcelaAtual { get; set; }
public int? TotalParcelas { get; set; }
public decimal? ValorCheio { get; set; }
public decimal? Desconto { get; set; }
public decimal? ValorComDesconto { get; set; }
public List<ParcelamentoMonthInputDto> MonthValues { get; set; } = new();
}
public sealed class ParcelamentoAnnualMonthDto
{
public int Month { get; set; }
public decimal? Valor { get; set; }
}
public sealed class ParcelamentoAnnualRowDto
{
public int Year { get; set; }
public decimal Total { get; set; }
public List<ParcelamentoAnnualMonthDto> Months { get; set; } = new();
}
public sealed class ParcelamentoDetailDto : ParcelamentoListDto public sealed class ParcelamentoDetailDto : ParcelamentoListDto
{ {
public List<ParcelamentoMonthDto> MonthValues { get; set; } = new(); public List<ParcelamentoMonthDto> MonthValues { get; set; } = new();
public List<ParcelamentoAnnualRowDto> AnnualRows { get; set; } = new();
} }
public sealed class ParcelamentosImportErrorDto public sealed class ParcelamentosImportErrorDto

View File

@ -11,6 +11,8 @@ public sealed class ResumoResponseDto
public ResumoPlanoContratoTotalDto? PlanoContratoTotal { get; set; } public ResumoPlanoContratoTotalDto? PlanoContratoTotal { get; set; }
public List<ResumoLineTotaisDto> LineTotais { get; set; } = new(); public List<ResumoLineTotaisDto> LineTotais { get; set; } = new();
public List<ResumoReservaLineDto> ReservaLines { get; set; } = new(); public List<ResumoReservaLineDto> ReservaLines { get; set; } = new();
public List<ResumoReservaPorDddDto> ReservaPorDdd { get; set; } = new();
public int? TotalGeralLinhasReserva { get; set; }
public ResumoReservaTotalDto? ReservaTotal { get; set; } public ResumoReservaTotalDto? ReservaTotal { get; set; }
} }
@ -91,6 +93,19 @@ public sealed class ResumoReservaLineDto
public decimal? Total { get; set; } public decimal? Total { get; set; }
} }
public sealed class ResumoReservaPorDddDto
{
public string Ddd { get; set; } = "";
public int TotalLinhas { get; set; }
public List<ResumoReservaPorFranquiaDto> PorFranquia { get; set; } = new();
}
public sealed class ResumoReservaPorFranquiaDto
{
public decimal? FranquiaGb { get; set; }
public int TotalLinhas { get; set; }
}
public sealed class ResumoReservaTotalDto public sealed class ResumoReservaTotalDto
{ {
public int? QtdLinhasTotal { get; set; } public int? QtdLinhasTotal { get; set; }

View File

@ -8,6 +8,10 @@ namespace line_gestao_api.Dtos
public int Item { get; set; } public int Item { get; set; }
public string? Linha { get; set; } public string? Linha { get; set; }
public string? Cliente { get; set; } public string? Cliente { get; set; }
public string? TipoPessoa { get; set; }
public string? Nome { get; set; }
public string? RazaoSocial { get; set; }
public string? Cnpj { get; set; }
public string? Cpf { get; set; } public string? Cpf { get; set; }
public string? Rg { get; set; } public string? Rg { get; set; }
public string? DataNascimento { get; set; } public string? DataNascimento { get; set; }
@ -23,6 +27,46 @@ namespace line_gestao_api.Dtos
public int Item { get; set; } public int Item { get; set; }
public string? Linha { get; set; } public string? Linha { get; set; }
public string? Cliente { get; set; } public string? Cliente { get; set; }
public string? TipoPessoa { get; set; }
public string? Nome { get; set; }
public string? RazaoSocial { get; set; }
public string? Cnpj { get; set; }
public string? Cpf { get; set; }
public string? Rg { get; set; }
public DateTime? DataNascimento { get; set; }
public string? Email { get; set; }
public string? Endereco { get; set; }
public string? Celular { get; set; }
public string? TelefoneFixo { get; set; }
}
public class CreateUserDataRequest
{
public int? Item { get; set; }
public string? Linha { get; set; }
public string? Cliente { get; set; }
public string? TipoPessoa { get; set; }
public string? Nome { get; set; }
public string? RazaoSocial { get; set; }
public string? Cnpj { get; set; }
public string? Cpf { get; set; }
public string? Rg { get; set; }
public DateTime? DataNascimento { get; set; }
public string? Email { get; set; }
public string? Endereco { get; set; }
public string? Celular { get; set; }
public string? TelefoneFixo { get; set; }
}
public class UpdateUserDataRequest
{
public int? Item { get; set; }
public string? Linha { get; set; }
public string? Cliente { get; set; }
public string? TipoPessoa { get; set; }
public string? Nome { get; set; }
public string? RazaoSocial { get; set; }
public string? Cnpj { get; set; }
public string? Cpf { get; set; } public string? Cpf { get; set; }
public string? Rg { get; set; } public string? Rg { get; set; }
public DateTime? DataNascimento { get; set; } public DateTime? DataNascimento { get; set; }
@ -37,6 +81,7 @@ namespace line_gestao_api.Dtos
public int TotalRegistros { get; set; } public int TotalRegistros { get; set; }
public int ClientesUnicos { get; set; } public int ClientesUnicos { get; set; }
public int ComCpf { get; set; } public int ComCpf { get; set; }
public int ComCnpj { get; set; }
public int ComEmail { get; set; } public int ComEmail { get; set; }
} }
@ -46,6 +91,7 @@ namespace line_gestao_api.Dtos
public string Cliente { get; set; } = ""; public string Cliente { get; set; } = "";
public int TotalRegistros { get; set; } public int TotalRegistros { get; set; }
public int ComCpf { get; set; } public int ComCpf { get; set; }
public int ComCnpj { get; set; }
public int ComEmail { get; set; } public int ComEmail { get; set; }
} }

View File

@ -17,6 +17,38 @@ namespace line_gestao_api.Dtos
public decimal? Total { get; set; } public decimal? Total { get; set; }
} }
public class VigenciaLineDetailDto : VigenciaLineListDto
{
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class CreateVigenciaRequest
{
public int? Item { get; set; }
public string? Conta { get; set; }
public string? Linha { get; set; }
public string? Cliente { get; set; }
public string? Usuario { get; set; }
public string? PlanoContrato { get; set; }
public DateTime? DtEfetivacaoServico { get; set; }
public DateTime? DtTerminoFidelizacao { get; set; }
public decimal? Total { get; set; }
}
public class UpdateVigenciaRequest
{
public int? Item { get; set; }
public string? Conta { get; set; }
public string? Linha { get; set; }
public string? Cliente { get; set; }
public string? Usuario { get; set; }
public string? PlanoContrato { get; set; }
public DateTime? DtEfetivacaoServico { get; set; }
public DateTime? DtTerminoFidelizacao { get; set; }
public decimal? Total { get; set; }
}
public class VigenciaClientGroupDto public class VigenciaClientGroupDto
{ {
public string Cliente { get; set; } = ""; public string Cliente { get; set; } = "";

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,232 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddResumoTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ResumoClienteEspeciais",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
Nome = table.Column<string>(type: "text", nullable: true),
Valor = 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_ResumoClienteEspeciais", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoLineTotais",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
Tipo = table.Column<string>(type: "text", nullable: true),
ValorTotalLine = table.Column<decimal>(type: "numeric", nullable: true),
LucroTotalLine = table.Column<decimal>(type: "numeric", nullable: true),
QtdLinhas = table.Column<int>(type: "integer", 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_ResumoLineTotais", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoMacrophonyPlans",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
PlanoContrato = table.Column<string>(type: "text", nullable: true),
Gb = table.Column<decimal>(type: "numeric", nullable: true),
ValorIndividualComSvas = table.Column<decimal>(type: "numeric", nullable: true),
FranquiaGb = table.Column<decimal>(type: "numeric", nullable: true),
TotalLinhas = table.Column<int>(type: "integer", nullable: true),
ValorTotal = table.Column<decimal>(type: "numeric", nullable: true),
VivoTravel = table.Column<bool>(type: "boolean", nullable: false),
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_ResumoMacrophonyPlans", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoMacrophonyTotals",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
FranquiaGbTotal = table.Column<decimal>(type: "numeric", nullable: true),
TotalLinhasTotal = table.Column<int>(type: "integer", nullable: true),
ValorTotal = 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_ResumoMacrophonyTotals", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoPlanoContratoResumos",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
PlanoContrato = table.Column<string>(type: "text", nullable: true),
Gb = table.Column<decimal>(type: "numeric", nullable: true),
ValorIndividualComSvas = table.Column<decimal>(type: "numeric", nullable: true),
FranquiaGb = table.Column<decimal>(type: "numeric", nullable: true),
TotalLinhas = table.Column<int>(type: "integer", nullable: true),
ValorTotal = 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_ResumoPlanoContratoResumos", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoPlanoContratoTotals",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
ValorTotal = 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_ResumoPlanoContratoTotals", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoReservaLines",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
Ddd = table.Column<string>(type: "text", nullable: true),
FranquiaGb = table.Column<decimal>(type: "numeric", nullable: true),
QtdLinhas = table.Column<int>(type: "integer", nullable: true),
Total = 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_ResumoReservaLines", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoReservaTotals",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
QtdLinhasTotal = table.Column<int>(type: "integer", nullable: true),
Total = 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_ResumoReservaTotals", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoVivoLineResumos",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
Skil = table.Column<string>(type: "text", nullable: true),
Cliente = table.Column<string>(type: "text", nullable: true),
QtdLinhas = table.Column<int>(type: "integer", nullable: true),
FranquiaTotal = table.Column<decimal>(type: "numeric", nullable: true),
ValorContratoVivo = table.Column<decimal>(type: "numeric", nullable: true),
FranquiaLine = table.Column<decimal>(type: "numeric", nullable: true),
ValorContratoLine = table.Column<decimal>(type: "numeric", nullable: true),
Lucro = 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_ResumoVivoLineResumos", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ResumoVivoLineTotals",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
QtdLinhasTotal = table.Column<int>(type: "integer", nullable: true),
FranquiaTotal = table.Column<decimal>(type: "numeric", nullable: true),
ValorContratoVivo = table.Column<decimal>(type: "numeric", nullable: true),
FranquiaLine = table.Column<decimal>(type: "numeric", nullable: true),
ValorContratoLine = table.Column<decimal>(type: "numeric", nullable: true),
Lucro = 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_ResumoVivoLineTotals", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ResumoClienteEspeciais");
migrationBuilder.DropTable(
name: "ResumoLineTotais");
migrationBuilder.DropTable(
name: "ResumoMacrophonyPlans");
migrationBuilder.DropTable(
name: "ResumoMacrophonyTotals");
migrationBuilder.DropTable(
name: "ResumoPlanoContratoResumos");
migrationBuilder.DropTable(
name: "ResumoPlanoContratoTotals");
migrationBuilder.DropTable(
name: "ResumoReservaLines");
migrationBuilder.DropTable(
name: "ResumoReservaTotals");
migrationBuilder.DropTable(
name: "ResumoVivoLineResumos");
migrationBuilder.DropTable(
name: "ResumoVivoLineTotals");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddParcelamentosTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ParcelamentoLines",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
AnoRef = table.Column<int>(type: "integer", nullable: true),
Item = table.Column<int>(type: "integer", nullable: true),
Linha = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
Cliente = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: true),
QtParcelas = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ParcelaAtual = table.Column<int>(type: "integer", nullable: true),
TotalParcelas = table.Column<int>(type: "integer", nullable: true),
ValorCheio = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
Desconto = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
ValorComDesconto = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, 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),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
ParcelamentoLineId = table.Column<Guid>(type: "uuid", nullable: false),
Competencia = table.Column<DateOnly>(type: "date", nullable: false),
Valor = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
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_AnoRef_Item",
table: "ParcelamentoLines",
columns: new[] { "AnoRef", "Item" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_Linha",
table: "ParcelamentoLines",
column: "Linha");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoLines_TenantId",
table: "ParcelamentoLines",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_ParcelamentoLineId_Competencia",
table: "ParcelamentoMonthValues",
columns: new[] { "ParcelamentoLineId", "Competencia" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ParcelamentoMonthValues_TenantId",
table: "ParcelamentoMonthValues",
column: "TenantId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ParcelamentoMonthValues");
migrationBuilder.DropTable(
name: "ParcelamentoLines");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddVivoSyncTipoDeChipToMobileLines : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "TipoDeChip",
table: "MobileLines",
type: "character varying(80)",
maxLength: 80,
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "VivoSync",
table: "MobileLines",
type: "numeric",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TipoDeChip",
table: "MobileLines");
migrationBuilder.DropColumn(
name: "VivoSync",
table: "MobileLines");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddUserDataPessoaFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Cnpj",
table: "UserDatas",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Nome",
table: "UserDatas",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "RazaoSocial",
table: "UserDatas",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "TipoPessoa",
table: "UserDatas",
type: "text",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_UserDatas_Cnpj",
table: "UserDatas",
column: "Cnpj");
migrationBuilder.CreateIndex(
name: "IX_UserDatas_Nome",
table: "UserDatas",
column: "Nome");
migrationBuilder.CreateIndex(
name: "IX_UserDatas_RazaoSocial",
table: "UserDatas",
column: "RazaoSocial");
migrationBuilder.CreateIndex(
name: "IX_UserDatas_TipoPessoa",
table: "UserDatas",
column: "TipoPessoa");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_UserDatas_Cnpj",
table: "UserDatas");
migrationBuilder.DropIndex(
name: "IX_UserDatas_Nome",
table: "UserDatas");
migrationBuilder.DropIndex(
name: "IX_UserDatas_RazaoSocial",
table: "UserDatas");
migrationBuilder.DropIndex(
name: "IX_UserDatas_TipoPessoa",
table: "UserDatas");
migrationBuilder.DropColumn(
name: "Cnpj",
table: "UserDatas");
migrationBuilder.DropColumn(
name: "Nome",
table: "UserDatas");
migrationBuilder.DropColumn(
name: "RazaoSocial",
table: "UserDatas");
migrationBuilder.DropColumn(
name: "TipoPessoa",
table: "UserDatas");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddResumoReservaTotalGeral : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TotalGeralLinhasReserva",
table: "ResumoReservaTotals",
type: "integer",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TotalGeralLinhasReserva",
table: "ResumoReservaTotals");
}
}
}

View File

@ -0,0 +1,19 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using line_gestao_api.Data;
#nullable disable
namespace line_gestao_api.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260212120000_AddAuditLogs")]
partial class AddAuditLogs
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace line_gestao_api.Migrations
{
/// <inheritdoc />
public partial class AddAuditLogs : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AuditLogs",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
OccurredAtUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UserId = table.Column<Guid>(type: "uuid", nullable: true),
UserName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
UserEmail = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
Action = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Page = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
EntityName = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
EntityId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
EntityLabel = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
ChangesJson = table.Column<string>(type: "jsonb", nullable: false),
RequestPath = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
RequestMethod = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true),
IpAddress = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditLogs", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_EntityName",
table: "AuditLogs",
column: "EntityName");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_OccurredAtUtc",
table: "AuditLogs",
column: "OccurredAtUtc");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_Page",
table: "AuditLogs",
column: "Page");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_TenantId",
table: "AuditLogs",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_UserId",
table: "AuditLogs",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AuditLogs");
}
}
}

View File

@ -234,6 +234,83 @@ namespace line_gestao_api.Migrations
b.ToTable("AspNetUsers", (string)null); b.ToTable("AspNetUsers", (string)null);
}); });
modelBuilder.Entity("line_gestao_api.Models.AuditLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ChangesJson")
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("EntityId")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("EntityLabel")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("EntityName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("IpAddress")
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<DateTime>("OccurredAtUtc")
.HasColumnType("timestamp with time zone");
b.Property<string>("Page")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("RequestMethod")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("RequestPath")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("UserEmail")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<Guid?>("UserId")
.HasColumnType("uuid");
b.Property<string>("UserName")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.HasKey("Id");
b.HasIndex("EntityName");
b.HasIndex("OccurredAtUtc");
b.HasIndex("Page");
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.ToTable("AuditLogs");
});
modelBuilder.Entity("line_gestao_api.Models.BillingClient", b => modelBuilder.Entity("line_gestao_api.Models.BillingClient", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -301,6 +378,125 @@ namespace line_gestao_api.Migrations
b.ToTable("billing_clients", (string)null); b.ToTable("billing_clients", (string)null);
}); });
modelBuilder.Entity("line_gestao_api.Models.ChipVirgemLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("NumeroDoChip")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("Observacoes")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Item");
b.HasIndex("NumeroDoChip");
b.HasIndex("TenantId");
b.ToTable("ChipVirgemLines");
});
modelBuilder.Entity("line_gestao_api.Models.ControleRecebidoLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("Ano")
.HasColumnType("integer");
b.Property<string>("Chip")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("ConteudoDaNf")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDaNf")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDoRecebimento")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsResumo")
.HasColumnType("boolean");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("NotaFiscal")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("NumeroDaLinha")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<int?>("Quantidade")
.HasColumnType("integer");
b.Property<string>("Serial")
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorDaNf")
.HasColumnType("numeric");
b.Property<decimal?>("ValorUnit")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Ano");
b.HasIndex("Chip");
b.HasIndex("DataDaNf");
b.HasIndex("DataDoRecebimento");
b.HasIndex("Item");
b.HasIndex("NotaFiscal");
b.HasIndex("NumeroDaLinha");
b.HasIndex("Serial");
b.HasIndex("TenantId");
b.ToTable("ControleRecebidoLines");
});
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b => modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -389,6 +585,10 @@ namespace line_gestao_api.Migrations
b.Property<Guid>("TenantId") b.Property<Guid>("TenantId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<string>("TipoDeChip")
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@ -415,6 +615,9 @@ namespace line_gestao_api.Migrations
b.Property<decimal?>("VivoNewsPlus") b.Property<decimal?>("VivoNewsPlus")
.HasColumnType("numeric"); .HasColumnType("numeric");
b.Property<decimal?>("VivoSync")
.HasColumnType("numeric");
b.Property<decimal?>("VivoTravelMundo") b.Property<decimal?>("VivoTravelMundo")
.HasColumnType("numeric"); .HasColumnType("numeric");
@ -564,6 +767,433 @@ namespace line_gestao_api.Migrations
b.ToTable("Notifications"); b.ToTable("Notifications");
}); });
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")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<int?>("Item")
.HasColumnType("integer");
b.Property<string>("Linha")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<int?>("ParcelaAtual")
.HasColumnType("integer");
b.Property<string>("QtParcelas")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<int?>("TotalParcelas")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorCheio")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<decimal?>("ValorComDesconto")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.HasKey("Id");
b.HasIndex("Linha");
b.HasIndex("TenantId");
b.HasIndex("AnoRef", "Item")
.IsUnique();
b.ToTable("ParcelamentoLines");
});
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoMonthValue", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateOnly>("Competencia")
.HasColumnType("date");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("ParcelamentoLineId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<decimal?>("Valor")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.HasKey("Id");
b.HasIndex("TenantId");
b.HasIndex("ParcelamentoLineId", "Competencia")
.IsUnique();
b.ToTable("ParcelamentoMonthValues");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoClienteEspecial", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Nome")
.HasColumnType("text");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Valor")
.HasColumnType("numeric");
b.HasKey("Id");
b.ToTable("ResumoClienteEspeciais");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoLineTotais", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("LucroTotalLine")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("Tipo")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorTotalLine")
.HasColumnType("numeric");
b.HasKey("Id");
b.ToTable("ResumoLineTotais");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoMacrophonyPlan", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("FranquiaGb")
.HasColumnType("numeric");
b.Property<decimal?>("Gb")
.HasColumnType("numeric");
b.Property<string>("PlanoContrato")
.HasColumnType("text");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<int?>("TotalLinhas")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorIndividualComSvas")
.HasColumnType("numeric");
b.Property<decimal?>("ValorTotal")
.HasColumnType("numeric");
b.Property<bool>("VivoTravel")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("ResumoMacrophonyPlans");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoMacrophonyTotal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("FranquiaGbTotal")
.HasColumnType("numeric");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<int?>("TotalLinhasTotal")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorTotal")
.HasColumnType("numeric");
b.HasKey("Id");
b.ToTable("ResumoMacrophonyTotals");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoPlanoContratoResumo", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("FranquiaGb")
.HasColumnType("numeric");
b.Property<decimal?>("Gb")
.HasColumnType("numeric");
b.Property<string>("PlanoContrato")
.HasColumnType("text");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<int?>("TotalLinhas")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorIndividualComSvas")
.HasColumnType("numeric");
b.Property<decimal?>("ValorTotal")
.HasColumnType("numeric");
b.HasKey("Id");
b.ToTable("ResumoPlanoContratoResumos");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoPlanoContratoTotal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorTotal")
.HasColumnType("numeric");
b.HasKey("Id");
b.ToTable("ResumoPlanoContratoTotals");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoReservaLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Ddd")
.HasColumnType("text");
b.Property<decimal?>("FranquiaGb")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<decimal?>("Total")
.HasColumnType("numeric");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("ResumoReservaLines");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoReservaTotal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("QtdLinhasTotal")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<decimal?>("Total")
.HasColumnType("numeric");
b.Property<int?>("TotalGeralLinhasReserva")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("ResumoReservaTotals");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoVivoLineResumo", 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<decimal?>("FranquiaLine")
.HasColumnType("numeric");
b.Property<decimal?>("FranquiaTotal")
.HasColumnType("numeric");
b.Property<decimal?>("Lucro")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhas")
.HasColumnType("integer");
b.Property<string>("Skil")
.HasColumnType("text");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
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.ToTable("ResumoVivoLineResumos");
});
modelBuilder.Entity("line_gestao_api.Models.ResumoVivoLineTotal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("FranquiaLine")
.HasColumnType("numeric");
b.Property<decimal?>("FranquiaTotal")
.HasColumnType("numeric");
b.Property<decimal?>("Lucro")
.HasColumnType("numeric");
b.Property<int?>("QtdLinhasTotal")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
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.ToTable("ResumoVivoLineTotals");
});
modelBuilder.Entity("line_gestao_api.Models.Tenant", b => modelBuilder.Entity("line_gestao_api.Models.Tenant", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -582,125 +1212,6 @@ namespace line_gestao_api.Migrations
b.ToTable("Tenants"); b.ToTable("Tenants");
}); });
modelBuilder.Entity("line_gestao_api.Models.ChipVirgemLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("NumeroDoChip")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("Observacoes")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Item");
b.HasIndex("NumeroDoChip");
b.HasIndex("TenantId");
b.ToTable("ChipVirgemLines");
});
modelBuilder.Entity("line_gestao_api.Models.ControleRecebidoLine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("Ano")
.HasColumnType("integer");
b.Property<string>("Chip")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("ConteudoDaNf")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDaNf")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DataDoRecebimento")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsResumo")
.HasColumnType("boolean");
b.Property<int>("Item")
.HasColumnType("integer");
b.Property<string>("NotaFiscal")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("NumeroDaLinha")
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<int?>("Quantidade")
.HasColumnType("integer");
b.Property<string>("Serial")
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("ValorDaNf")
.HasColumnType("numeric");
b.Property<decimal?>("ValorUnit")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("Ano");
b.HasIndex("Chip");
b.HasIndex("DataDaNf");
b.HasIndex("DataDoRecebimento");
b.HasIndex("Item");
b.HasIndex("NotaFiscal");
b.HasIndex("NumeroDaLinha");
b.HasIndex("Serial");
b.HasIndex("TenantId");
b.ToTable("ControleRecebidoLines");
});
modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b => modelBuilder.Entity("line_gestao_api.Models.TrocaNumeroLine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -766,6 +1277,9 @@ namespace line_gestao_api.Migrations
b.Property<string>("Cliente") b.Property<string>("Cliente")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Cnpj")
.HasColumnType("text");
b.Property<string>("Cpf") b.Property<string>("Cpf")
.HasColumnType("text"); .HasColumnType("text");
@ -787,6 +1301,12 @@ namespace line_gestao_api.Migrations
b.Property<string>("Linha") b.Property<string>("Linha")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Nome")
.HasColumnType("text");
b.Property<string>("RazaoSocial")
.HasColumnType("text");
b.Property<string>("Rg") b.Property<string>("Rg")
.HasColumnType("text"); .HasColumnType("text");
@ -796,6 +1316,9 @@ namespace line_gestao_api.Migrations
b.Property<Guid>("TenantId") b.Property<Guid>("TenantId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<string>("TipoPessoa")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@ -803,6 +1326,8 @@ namespace line_gestao_api.Migrations
b.HasIndex("Cliente"); b.HasIndex("Cliente");
b.HasIndex("Cnpj");
b.HasIndex("Cpf"); b.HasIndex("Cpf");
b.HasIndex("Email"); b.HasIndex("Email");
@ -811,8 +1336,14 @@ namespace line_gestao_api.Migrations
b.HasIndex("Linha"); b.HasIndex("Linha");
b.HasIndex("Nome");
b.HasIndex("RazaoSocial");
b.HasIndex("TenantId"); b.HasIndex("TenantId");
b.HasIndex("TipoPessoa");
b.ToTable("UserDatas"); b.ToTable("UserDatas");
}); });
@ -952,10 +1483,26 @@ namespace line_gestao_api.Migrations
b.Navigation("VigenciaLine"); b.Navigation("VigenciaLine");
}); });
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.MobileLine", b => modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
{ {
b.Navigation("Muregs"); b.Navigation("Muregs");
}); });
modelBuilder.Entity("line_gestao_api.Models.ParcelamentoLine", b =>
{
b.Navigation("MonthValues");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

28
Models/AuditLog.cs Normal file
View File

@ -0,0 +1,28 @@
using System;
namespace line_gestao_api.Models;
public class AuditLog : ITenantEntity
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid TenantId { get; set; }
public DateTime OccurredAtUtc { get; set; } = DateTime.UtcNow;
public Guid? UserId { get; set; }
public string? UserName { get; set; }
public string? UserEmail { get; set; }
public string Action { get; set; } = string.Empty;
public string Page { get; set; } = string.Empty;
public string EntityName { get; set; } = string.Empty;
public string? EntityId { get; set; }
public string? EntityLabel { get; set; }
public string ChangesJson { get; set; } = "[]";
public string? RequestPath { get; set; }
public string? RequestMethod { get; set; }
public string? IpAddress { get; set; }
}

View File

@ -6,6 +6,7 @@ public class ResumoReservaTotal : ITenantEntity
public Guid TenantId { get; set; } public Guid TenantId { get; set; }
public int? TotalGeralLinhasReserva { get; set; }
public int? QtdLinhasTotal { get; set; } public int? QtdLinhasTotal { get; set; }
public decimal? Total { get; set; } public decimal? Total { get; set; }

View File

@ -15,6 +15,12 @@ namespace line_gestao_api.Models
public string? Linha { get; set; } public string? Linha { get; set; }
public string? Cliente { get; set; } public string? Cliente { get; set; }
// PF/PJ
public string? TipoPessoa { get; set; } // "PF" | "PJ"
public string? Nome { get; set; } // PF
public string? RazaoSocial { get; set; } // PJ
public string? Cnpj { get; set; } // PJ
public string? Cpf { get; set; } public string? Cpf { get; set; }
public string? Rg { get; set; } public string? Rg { get; set; }

View File

@ -35,6 +35,7 @@ builder.Services.AddDbContext<AppDbContext>(options =>
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenantProvider, TenantProvider>(); builder.Services.AddScoped<ITenantProvider, TenantProvider>();
builder.Services.AddScoped<IAuditLogBuilder, AuditLogBuilder>();
builder.Services.AddScoped<ParcelamentosImportService>(); builder.Services.AddScoped<ParcelamentosImportService>();
builder.Services.AddScoped<GeralDashboardInsightsService>(); builder.Services.AddScoped<GeralDashboardInsightsService>();

280
Services/AuditLogBuilder.cs Normal file
View File

@ -0,0 +1,280 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.IdentityModel.Tokens.Jwt;
using line_gestao_api.Dtos;
using line_gestao_api.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace line_gestao_api.Services;
public class AuditLogBuilder : IAuditLogBuilder
{
private static readonly Dictionary<string, string> PageByEntity = new(StringComparer.OrdinalIgnoreCase)
{
{ nameof(MobileLine), "Geral" },
{ nameof(MuregLine), "Mureg" },
{ nameof(BillingClient), "Faturamento" },
{ nameof(ParcelamentoLine), "Parcelamentos" },
{ nameof(ParcelamentoMonthValue), "Parcelamentos" },
{ nameof(UserData), "Dados e Usuários" },
{ nameof(ApplicationUser), "Dados e Usuários" },
{ nameof(VigenciaLine), "Vigência" },
{ nameof(ChipVirgemLine), "Chips Virgens e Recebidos" },
{ nameof(ControleRecebidoLine), "Chips Virgens e Recebidos" },
{ nameof(TrocaNumeroLine), "Troca de número" }
};
private static readonly HashSet<string> IgnoredProperties = new(StringComparer.OrdinalIgnoreCase)
{
"TenantId",
"CreatedAt",
"UpdatedAt",
"PasswordHash",
"SecurityStamp",
"ConcurrencyStamp",
"NormalizedEmail",
"NormalizedUserName",
"LockoutEnd",
"AccessFailedCount",
"LockoutEnabled",
"EmailConfirmed",
"PhoneNumberConfirmed",
"TwoFactorEnabled"
};
private static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ITenantProvider _tenantProvider;
public AuditLogBuilder(IHttpContextAccessor httpContextAccessor, ITenantProvider tenantProvider)
{
_httpContextAccessor = httpContextAccessor;
_tenantProvider = tenantProvider;
}
public List<AuditLog> BuildAuditLogs(ChangeTracker changeTracker)
{
var tenantId = _tenantProvider.TenantId;
if (tenantId == null)
{
return new List<AuditLog>();
}
changeTracker.DetectChanges();
var httpContext = _httpContextAccessor.HttpContext;
var userInfo = ResolveUserInfo(httpContext?.User);
var request = httpContext?.Request;
var requestPath = request?.Path.Value;
var requestMethod = request?.Method;
var ipAddress = httpContext?.Connection?.RemoteIpAddress?.ToString();
var logs = new List<AuditLog>();
foreach (var entry in changeTracker.Entries())
{
if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;
if (entry.Entity is AuditLog)
continue;
var entityName = entry.Metadata.ClrType.Name;
if (!PageByEntity.TryGetValue(entityName, out var page))
continue;
var changes = BuildChanges(entry);
if (changes.Count == 0)
continue;
logs.Add(new AuditLog
{
TenantId = tenantId.Value,
OccurredAtUtc = DateTime.UtcNow,
UserId = userInfo.UserId,
UserName = userInfo.UserName,
UserEmail = userInfo.UserEmail,
Action = ResolveAction(entry.State),
Page = page,
EntityName = entityName,
EntityId = BuildEntityId(entry),
EntityLabel = BuildEntityLabel(entry),
ChangesJson = JsonSerializer.Serialize(changes, JsonOptions),
RequestPath = requestPath,
RequestMethod = requestMethod,
IpAddress = ipAddress
});
}
return logs;
}
private static string ResolveAction(EntityState state)
=> state switch
{
EntityState.Added => "CREATE",
EntityState.Modified => "UPDATE",
EntityState.Deleted => "DELETE",
_ => "UNKNOWN"
};
private static List<AuditFieldChangeDto> BuildChanges(EntityEntry entry)
{
var changes = new List<AuditFieldChangeDto>();
foreach (var property in entry.Properties)
{
if (IgnoredProperties.Contains(property.Metadata.Name))
continue;
if (entry.State == EntityState.Added)
{
var newValue = FormatValue(property.CurrentValue);
if (newValue == null)
continue;
changes.Add(new AuditFieldChangeDto
{
Field = property.Metadata.Name,
ChangeType = "added",
NewValue = newValue
});
}
else if (entry.State == EntityState.Deleted)
{
var oldValue = FormatValue(property.OriginalValue);
if (oldValue == null)
continue;
changes.Add(new AuditFieldChangeDto
{
Field = property.Metadata.Name,
ChangeType = "removed",
OldValue = oldValue
});
}
else if (entry.State == EntityState.Modified)
{
if (!property.IsModified)
continue;
var oldValue = FormatValue(property.OriginalValue);
var newValue = FormatValue(property.CurrentValue);
if (string.Equals(oldValue, newValue, StringComparison.Ordinal))
continue;
changes.Add(new AuditFieldChangeDto
{
Field = property.Metadata.Name,
ChangeType = "modified",
OldValue = oldValue,
NewValue = newValue
});
}
}
return changes;
}
private static string? BuildEntityId(EntityEntry entry)
{
var key = entry.Metadata.FindPrimaryKey();
if (key == null)
return null;
var parts = new List<string>();
foreach (var keyProperty in key.Properties)
{
var property = entry.Property(keyProperty.Name);
var value = FormatValue(property.CurrentValue ?? property.OriginalValue);
if (value == null)
continue;
parts.Add($"{keyProperty.Name}={value}");
}
return parts.Count == 0 ? null : string.Join(";", parts);
}
private static string? BuildEntityLabel(EntityEntry entry)
{
var entityName = entry.Metadata.ClrType.Name;
return entityName switch
{
nameof(MobileLine) => GetValue(entry, "Linha") ?? GetValue(entry, "Item"),
nameof(MuregLine) => GetValue(entry, "LinhaAntiga") ?? GetValue(entry, "LinhaNova") ?? GetValue(entry, "Item"),
nameof(TrocaNumeroLine) => GetValue(entry, "LinhaAntiga") ?? GetValue(entry, "LinhaNova") ?? GetValue(entry, "Item"),
nameof(ChipVirgemLine) => GetValue(entry, "NumeroDoChip") ?? GetValue(entry, "Item"),
nameof(ControleRecebidoLine) => GetValue(entry, "NotaFiscal") ?? GetValue(entry, "Serial") ?? GetValue(entry, "Item"),
nameof(BillingClient) => GetValue(entry, "Cliente") ?? GetValue(entry, "Item"),
nameof(ParcelamentoLine) => GetValue(entry, "Linha") ?? GetValue(entry, "Cliente") ?? GetValue(entry, "Item"),
nameof(ParcelamentoMonthValue) => GetValue(entry, "Competencia") ?? GetValue(entry, "ParcelamentoLineId"),
nameof(UserData) => GetValue(entry, "Linha") ?? GetValue(entry, "Cliente") ?? GetValue(entry, "Item"),
nameof(VigenciaLine) => GetValue(entry, "Linha") ?? GetValue(entry, "Cliente") ?? GetValue(entry, "Item"),
nameof(ApplicationUser) => GetValue(entry, "Email") ?? GetValue(entry, "Name") ?? GetValue(entry, "Id"),
_ => null
};
}
private static string? GetValue(EntityEntry entry, string propertyName)
{
var property = entry.Properties.FirstOrDefault(p => p.Metadata.Name == propertyName);
return property == null ? null : FormatValue(property.CurrentValue ?? property.OriginalValue);
}
private static string? FormatValue(object? value)
{
if (value == null)
return null;
switch (value)
{
case DateTime dt:
var normalized = dt.Kind == DateTimeKind.Utc
? dt
: (dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : DateTime.SpecifyKind(dt, DateTimeKind.Utc));
return normalized.ToString("O", CultureInfo.InvariantCulture);
case DateTimeOffset dto:
return dto.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture);
case IFormattable formattable:
return formattable.ToString(null, CultureInfo.InvariantCulture);
default:
return value.ToString();
}
}
private static (Guid? UserId, string? UserName, string? UserEmail) ResolveUserInfo(ClaimsPrincipal? user)
{
if (user?.Identity?.IsAuthenticated != true)
{
return (null, "SYSTEM", null);
}
var idValue = user.FindFirstValue(ClaimTypes.NameIdentifier)
?? user.FindFirstValue(JwtRegisteredClaimNames.Sub)
?? user.FindFirstValue("sub");
var name = user.FindFirstValue("name")
?? user.FindFirstValue(ClaimTypes.Name)
?? user.Identity?.Name;
var email = user.FindFirstValue(ClaimTypes.Email)
?? user.FindFirstValue(JwtRegisteredClaimNames.Email)
?? user.FindFirstValue("email");
var userId = Guid.TryParse(idValue, out var parsed) ? parsed : (Guid?)null;
var userName = string.IsNullOrWhiteSpace(name) ? email : name;
return (userId, userName, email);
}
}

73
Services/AutoFillRules.cs Normal file
View File

@ -0,0 +1,73 @@
using System.Globalization;
using System.Text.RegularExpressions;
using line_gestao_api.Data;
using line_gestao_api.Models;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Services;
public static class AutoFillRules
{
private static readonly Regex FranquiaRegex = new(
@"(?<val>\d+(?:[.,]\d+)?)\s*(?<unit>GB|MB)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
public sealed record PlanSuggestion(decimal? FranquiaGb, decimal? ValorPlano);
public static async Task<PlanSuggestion?> ResolvePlanSuggestionAsync(AppDbContext db, string? planoContrato)
{
var plan = (planoContrato ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(plan)) return null;
ResumoPlanoContratoResumo? resumoPlano = await db.ResumoPlanoContratoResumos
.AsNoTracking()
.Where(x => x.PlanoContrato != null && EF.Functions.ILike(x.PlanoContrato, plan))
.OrderByDescending(x => x.UpdatedAt)
.FirstOrDefaultAsync();
ResumoMacrophonyPlan? macroPlan = null;
if (resumoPlano == null)
{
macroPlan = await db.ResumoMacrophonyPlans
.AsNoTracking()
.Where(x => x.PlanoContrato != null && EF.Functions.ILike(x.PlanoContrato, plan))
.OrderByDescending(x => x.UpdatedAt)
.FirstOrDefaultAsync();
}
decimal? franquia = resumoPlano?.FranquiaGb ?? resumoPlano?.Gb;
decimal? valor = resumoPlano?.ValorIndividualComSvas;
if (macroPlan != null)
{
franquia ??= macroPlan.FranquiaGb ?? macroPlan.Gb;
valor ??= macroPlan.ValorIndividualComSvas;
}
franquia ??= ParseFranquiaGbFromPlano(plan);
return new PlanSuggestion(franquia, valor);
}
public static decimal? ParseFranquiaGbFromPlano(string? planoContrato)
{
if (string.IsNullOrWhiteSpace(planoContrato)) return null;
var match = FranquiaRegex.Match(planoContrato);
if (!match.Success) return null;
var raw = match.Groups["val"].Value.Replace(",", ".");
if (!decimal.TryParse(raw, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
{
return null;
}
var unit = match.Groups["unit"].Value.ToUpperInvariant();
if (unit == "MB")
{
return Math.Round(value / 1000m, 4);
}
return value;
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using line_gestao_api.Models;
namespace line_gestao_api.Services;
public interface IAuditLogBuilder
{
List<AuditLog> BuildAuditLogs(ChangeTracker changeTracker);
}

View File

@ -41,16 +41,49 @@ public sealed class ParcelamentosImportService
await _db.ParcelamentoLines.ExecuteDeleteAsync(cancellationToken); await _db.ParcelamentoLines.ExecuteDeleteAsync(cancellationToken);
} }
const int startRow = 6; if (!TryFindParcelamentosHeader(ws, out var headerRow, out var headerMap, out var lastCol))
const int colAnoRef = 3; {
const int colItem = 4; return new ParcelamentosImportSummaryDto
const int colLinha = 5; {
const int colCliente = 6; Erros =
const int colQtParcelas = 7; {
const int colValorCheio = 8; new ParcelamentosImportErrorDto
const int colDesconto = 9; {
const int colValorComDesconto = 10; LinhaExcel = 0,
var monthMap = BuildFixedMonthMap(); Motivo = "Cabeçalho da aba PARCELAMENTOS não encontrado (LINHA/CLIENTE/QT PARCELAS/VALOR CHEIO/DESCONTO/VALOR C/ DESCONTO + meses)."
}
}
};
}
var colLinha = GetColAny(headerMap, "LINHA");
var colCliente = GetColAny(headerMap, "CLIENTE");
var colQtParcelas = GetColAny(headerMap, "QT PARCELAS", "QT. PARCELAS", "QTPARCELAS");
var colValorCheio = GetColAny(headerMap, "VALOR CHEIO", "VALORCHEIO");
var colDesconto = GetColAny(headerMap, "DESCONTO");
var colValorComDesconto = GetColAny(headerMap, "VALOR C/ DESCONTO", "VALOR C\\ DESCONTO", "VALORCOMDESCONTO", "VALOR C DESCONTO");
var colItem = GetColAny(headerMap, "ITEM");
var colAnoRef = GetColAny(headerMap, "ANO REF", "ANOREF", "ANO");
if (colItem == 0 && colLinha > 1) colItem = colLinha - 1;
if (colAnoRef == 0 && colLinha > 2) colAnoRef = colLinha - 2;
var monthMap = BuildDynamicMonthMap(ws, headerRow, colValorComDesconto, lastCol);
if (monthMap.Count == 0)
{
return new ParcelamentosImportSummaryDto
{
Erros =
{
new ParcelamentosImportErrorDto
{
LinhaExcel = headerRow,
Motivo = "Meses (JAN..DEZ) não identificados no cabeçalho da aba PARCELAMENTOS."
}
}
};
}
var existing = await _db.ParcelamentoLines var existing = await _db.ParcelamentoLines
.AsNoTracking() .AsNoTracking()
@ -60,22 +93,23 @@ public sealed class ParcelamentosImportService
.ToDictionary(x => (x.AnoRef!.Value, x.Item!.Value), x => x.Id); .ToDictionary(x => (x.AnoRef!.Value, x.Item!.Value), x => x.Id);
var summary = new ParcelamentosImportSummaryDto(); var summary = new ParcelamentosImportSummaryDto();
var startRow = headerRow + 1;
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow; var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
for (int row = startRow; row <= lastRow; row++) for (int row = startRow; row <= lastRow; row++)
{ {
var linhaValue = GetCellStringValue(ws, row, colLinha); var linhaValue = GetCellStringValue(ws, row, colLinha);
var itemStr = GetCellStringValue(ws, row, colItem); var qtParcelas = GetCellStringValue(ws, row, colQtParcelas);
var anoRefStr = GetCellStringValue(ws, row, colAnoRef); if (!IsValidParcelamentoRow(linhaValue, qtParcelas))
if (string.IsNullOrWhiteSpace(itemStr)
&& string.IsNullOrWhiteSpace(linhaValue)
&& string.IsNullOrWhiteSpace(anoRefStr))
{ {
continue; continue;
} }
summary.Lidos++; summary.Lidos++;
var itemStr = GetCellStringValue(ws, row, colItem);
var anoRefStr = GetCellStringValue(ws, row, colAnoRef);
var anoRef = TryNullableInt(anoRefStr); var anoRef = TryNullableInt(anoRefStr);
var item = TryNullableInt(itemStr); var item = TryNullableInt(itemStr);
@ -101,9 +135,12 @@ public sealed class ParcelamentosImportService
continue; continue;
} }
var qtParcelas = GetCellStringValue(ws, row, colQtParcelas);
ParseParcelas(qtParcelas, out var parcelaAtual, out var totalParcelas); ParseParcelas(qtParcelas, out var parcelaAtual, out var totalParcelas);
var valorCheio = GetCellDecimalValue(ws, row, colValorCheio);
var desconto = GetCellDecimalValue(ws, row, colDesconto);
var valorComDesconto = GetCellDecimalValue(ws, row, colValorComDesconto);
var parcelamento = new ParcelamentoLine var parcelamento = new ParcelamentoLine
{ {
AnoRef = anoRef, AnoRef = anoRef,
@ -113,9 +150,9 @@ public sealed class ParcelamentosImportService
QtParcelas = string.IsNullOrWhiteSpace(qtParcelas) ? null : qtParcelas.Trim(), QtParcelas = string.IsNullOrWhiteSpace(qtParcelas) ? null : qtParcelas.Trim(),
ParcelaAtual = parcelaAtual, ParcelaAtual = parcelaAtual,
TotalParcelas = totalParcelas, TotalParcelas = totalParcelas,
ValorCheio = TryDecimal(GetCellStringValue(ws, row, colValorCheio)), ValorCheio = valorCheio,
Desconto = TryDecimal(GetCellStringValue(ws, row, colDesconto)), Desconto = desconto,
ValorComDesconto = TryDecimal(GetCellStringValue(ws, row, colValorComDesconto)), ValorComDesconto = valorComDesconto,
UpdatedAt = DateTime.UtcNow UpdatedAt = DateTime.UtcNow
}; };
@ -145,7 +182,8 @@ public sealed class ParcelamentosImportService
.Where(x => x.ParcelamentoLineId == existingEntity.Id) .Where(x => x.ParcelamentoLineId == existingEntity.Id)
.ExecuteDeleteAsync(cancellationToken); .ExecuteDeleteAsync(cancellationToken);
var monthValues = BuildMonthValuesFromMap(ws, row, monthMap, existingEntity.Id, summary); var monthValues = BuildMonthValuesFromMap(ws, row, monthMap, existingEntity.Id, summary, out var rawMonths);
AuditParcelamentoRow(row, valorCheio, desconto, valorComDesconto, rawMonths, summary);
if (monthValues.Count > 0) if (monthValues.Count > 0)
{ {
await _db.ParcelamentoMonthValues.AddRangeAsync(monthValues, cancellationToken); await _db.ParcelamentoMonthValues.AddRangeAsync(monthValues, cancellationToken);
@ -157,7 +195,8 @@ public sealed class ParcelamentosImportService
} }
parcelamento.CreatedAt = DateTime.UtcNow; parcelamento.CreatedAt = DateTime.UtcNow;
parcelamento.MonthValues = BuildMonthValuesFromMap(ws, row, monthMap, parcelamento.Id, summary); parcelamento.MonthValues = BuildMonthValuesFromMap(ws, row, monthMap, parcelamento.Id, summary, out var rawMonthsNew);
AuditParcelamentoRow(row, valorCheio, desconto, valorComDesconto, rawMonthsNew, summary);
summary.Inseridos++; summary.Inseridos++;
await _db.ParcelamentoLines.AddAsync(parcelamento, cancellationToken); await _db.ParcelamentoLines.AddAsync(parcelamento, cancellationToken);
@ -178,42 +217,21 @@ public sealed class ParcelamentosImportService
}); });
} }
private static List<(int Column, int Year, int Month)> BuildFixedMonthMap()
{
var map = new List<(int Column, int Year, int Month)>
{
(11, 2025, 12)
};
for (int month = 1; month <= 12; month++)
{
map.Add((11 + month, 2026, month));
}
for (int month = 1; month <= 6; month++)
{
map.Add((23 + month, 2027, month));
}
return map;
}
private static List<ParcelamentoMonthValue> BuildMonthValuesFromMap( private static List<ParcelamentoMonthValue> BuildMonthValuesFromMap(
IXLWorksheet ws, IXLWorksheet ws,
int row, int row,
List<(int Column, int Year, int Month)> monthMap, List<(int Column, int Year, int Month)> monthMap,
Guid parcelamentoId, Guid parcelamentoId,
ParcelamentosImportSummaryDto summary) ParcelamentosImportSummaryDto summary,
out Dictionary<string, decimal?> rawValues)
{ {
var monthValues = new List<ParcelamentoMonthValue>(); var monthValues = new List<ParcelamentoMonthValue>();
rawValues = new Dictionary<string, decimal?>();
foreach (var (column, year, month) in monthMap) foreach (var (column, year, month) in monthMap)
{ {
var valueStr = GetCellStringValue(ws, row, column); var value = GetCellDecimalValue(ws, row, column);
var value = TryDecimal(valueStr); rawValues[$"C{column}:{year}-{month:00}"] = value;
if (!value.HasValue) if (!value.HasValue) continue;
{
continue;
}
monthValues.Add(new ParcelamentoMonthValue monthValues.Add(new ParcelamentoMonthValue
{ {
@ -228,6 +246,236 @@ public sealed class ParcelamentosImportService
return monthValues; return monthValues;
} }
private static List<(int Column, int Year, int Month)> BuildDynamicMonthMap(
IXLWorksheet ws,
int headerRow,
int colValorComDesconto,
int lastCol)
{
var header = ws.Row(headerRow);
var monthHeaders = new List<(int Col, int Month)>();
foreach (var cell in header.CellsUsed())
{
var col = cell.Address.ColumnNumber;
if (colValorComDesconto > 0 && col <= colValorComDesconto) continue;
var month = ToMonthNumber(cell.GetString());
if (month.HasValue)
{
monthHeaders.Add((col, month.Value));
}
}
monthHeaders = monthHeaders.OrderBy(x => x.Col).ToList();
if (monthHeaders.Count == 0) return new List<(int Column, int Year, int Month)>();
var firstJanIdx = monthHeaders.FindIndex(x => x.Month == 1);
if (firstJanIdx < 0) return new List<(int Column, int Year, int Month)>();
var block1 = ExtractMonthBlock(monthHeaders, firstJanIdx);
var block2 = block1.NextIndex < monthHeaders.Count
? ExtractMonthBlock(monthHeaders, block1.NextIndex)
: (new List<(int Col, int Month)>(), block1.NextIndex);
var yearRow = headerRow - 1;
var year1 = block1.Block.Count > 0
? FindYearForBlock(ws, yearRow, block1.Block.First().Col, block1.Block.Last().Col, lastCol)
: null;
var year2 = block2.Block.Count > 0
? FindYearForBlock(ws, yearRow, block2.Block.First().Col, block2.Block.Last().Col, lastCol)
: null;
var map = new List<(int Column, int Year, int Month)>();
if (year1.HasValue)
{
foreach (var (col, month) in block1.Block)
map.Add((col, year1.Value, month));
}
if (year2.HasValue)
{
foreach (var (col, month) in block2.Block)
map.Add((col, year2.Value, month));
}
return map;
}
private static (List<(int Col, int Month)> Block, int NextIndex) ExtractMonthBlock(
List<(int Col, int Month)> monthHeaders,
int startIndex)
{
var block = new List<(int Col, int Month)>();
var idx = startIndex;
for (int m = 1; m <= 12; m++)
{
while (idx < monthHeaders.Count && monthHeaders[idx].Month != m)
idx++;
if (idx >= monthHeaders.Count) break;
block.Add(monthHeaders[idx]);
idx++;
}
return (block, idx);
}
private static int? FindYearForBlock(IXLWorksheet ws, int yearRow, int startCol, int endCol, int lastCol)
{
if (yearRow < 1) return null;
for (int col = startCol; col <= endCol; col++)
{
var year = TryParseYear(GetCellStringValue(ws, yearRow, col));
if (year.HasValue) return year;
}
int? found = null;
var bestDistance = int.MaxValue;
for (int col = 1; col <= lastCol; col++)
{
var year = TryParseYear(GetCellStringValue(ws, yearRow, col));
if (!year.HasValue) continue;
var dist = Math.Abs(col - startCol);
if (dist < bestDistance)
{
bestDistance = dist;
found = year;
}
}
return found;
}
private static int? TryParseYear(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return null;
var digits = new string(s.Where(char.IsDigit).ToArray());
if (digits.Length == 4 && int.TryParse(digits, out var year)) return year;
if (digits.Length > 4)
{
for (int i = 0; i <= digits.Length - 4; i++)
{
if (int.TryParse(digits.Substring(i, 4), out var y) && y >= 2000 && y <= 2100)
return y;
}
}
return null;
}
private static bool TryFindParcelamentosHeader(
IXLWorksheet ws,
out int headerRow,
out Dictionary<string, int> map,
out int lastCol)
{
headerRow = 0;
map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var firstRow = ws.FirstRowUsed()?.RowNumber() ?? 1;
var lastRow = ws.LastRowUsed()?.RowNumber() ?? firstRow;
lastCol = ws.LastColumnUsed()?.ColumnNumber() ?? 1;
for (int r = firstRow; r <= lastRow; r++)
{
var row = ws.Row(r);
if (!row.CellsUsed().Any()) continue;
var headerMap = BuildHeaderMap(row);
if (!HasParcelamentosHeaders(row, headerMap)) continue;
headerRow = r;
map = headerMap;
return true;
}
return false;
}
private static bool HasParcelamentosHeaders(IXLRow row, Dictionary<string, int> map)
{
var hasLinha = GetColAny(map, "LINHA") > 0;
var hasCliente = GetColAny(map, "CLIENTE") > 0;
var hasQt = GetColAny(map, "QT PARCELAS", "QT. PARCELAS", "QTPARCELAS") > 0;
var hasCheio = GetColAny(map, "VALOR CHEIO", "VALORCHEIO") > 0;
var hasDesc = GetColAny(map, "DESCONTO") > 0;
var hasComDesc = GetColAny(map, "VALOR C/ DESCONTO", "VALOR C\\ DESCONTO", "VALORCOMDESCONTO", "VALOR C DESCONTO") > 0;
var hasMonth = row.CellsUsed().Any(c => ToMonthNumber(c.GetString()).HasValue);
return hasLinha && hasCliente && hasQt && hasCheio && hasDesc && hasComDesc && hasMonth;
}
private static bool IsValidParcelamentoRow(string? linhaValue, string? qtParcelas)
{
if (string.IsNullOrWhiteSpace(linhaValue)) return false;
var digits = OnlyDigits(linhaValue);
if (string.IsNullOrWhiteSpace(digits) || digits.Length < 8) return false;
return !string.IsNullOrWhiteSpace(qtParcelas);
}
private static decimal? GetCellDecimalValue(IXLWorksheet ws, int row, int col)
{
if (col <= 0) return null;
var cell = ws.Cell(row, col);
if (cell.IsEmpty()) return null;
if (cell.DataType == XLDataType.Number)
{
if (cell.TryGetValue<double>(out var dbl))
return Convert.ToDecimal(dbl);
}
if (cell.TryGetValue<decimal>(out var dec))
return dec;
var s = cell.GetValue<string>();
return TryDecimal(s);
}
private static void AuditParcelamentoRow(
int row,
decimal? valorCheio,
decimal? desconto,
decimal? valorComDesconto,
Dictionary<string, decimal?> monthValues,
ParcelamentosImportSummaryDto summary)
{
var issues = new List<string>();
if (valorCheio.HasValue && desconto.HasValue && desconto.Value > 0 && valorComDesconto.HasValue
&& Math.Abs(valorCheio.Value - valorComDesconto.Value) < 0.01m)
{
issues.Add("ValorCheio == ValorComDesconto com desconto > 0");
}
if (valorCheio.HasValue && desconto.HasValue && valorComDesconto.HasValue)
{
var esperado = valorCheio.Value - desconto.Value;
if (Math.Abs(esperado - valorComDesconto.Value) > 0.05m)
{
issues.Add("ValorComDesconto difere de ValorCheio - Desconto");
}
}
if (valorCheio.HasValue && monthValues.Values.Any(v => v.HasValue && v.Value == valorCheio.Value))
{
issues.Add("ValorCheio igual a valor mensal (possível shift de coluna)");
}
var monthNonNull = monthValues.Values.Where(v => v.HasValue).Select(v => v!.Value).ToList();
if (monthNonNull.Count >= 4 && monthNonNull.Distinct().Count() == 1)
{
issues.Add("Meses iguais (possível parse/shift)");
}
if (issues.Count == 0) return;
var mapped = $"ValorCheio={valorCheio} Desconto={desconto} ValorComDesconto={valorComDesconto}";
var raw = string.Join(" ", monthValues.Select(k => $"{k.Key}={k.Value}"));
Console.WriteLine($"[AUDITORIA PARCELAMENTOS] Linha {row} | {string.Join(" | ", issues)} | {mapped} | {raw}");
summary.Erros.Add(new ParcelamentosImportErrorDto
{
LinhaExcel = row,
Motivo = $"Auditoria: {string.Join(" | ", issues)}",
Valor = mapped
});
}
private static string GetCellStringValue(IXLWorksheet ws, int row, int col) private static string GetCellStringValue(IXLWorksheet ws, int row, int col)
{ {
if (col <= 0) return ""; if (col <= 0) return "";

View File

@ -113,7 +113,7 @@ public class VigenciaNotificationBackgroundService : BackgroundService
var userByEmail = users var userByEmail = users
.Where(u => !string.IsNullOrWhiteSpace(u.Email)) .Where(u => !string.IsNullOrWhiteSpace(u.Email))
.ToDictionary(u => u.Email.Trim().ToLowerInvariant(), u => u.Id); .ToDictionary(u => u.Email!.Trim().ToLowerInvariant(), u => u.Id);
var vigencias = await db.VigenciaLines.AsNoTracking() var vigencias = await db.VigenciaLines.AsNoTracking()
.Where(v => v.DtTerminoFidelizacao != null) .Where(v => v.DtTerminoFidelizacao != null)

View File

@ -6,7 +6,7 @@
"Key": "vI8/oEYEWN5sBDTisNuZFjZAl+YFvXEJ96POb73/eoq3NaFPkOFXyPRdf/HWGAFnUsF3e3QpYL6Wl4Bc2v+l3g==", "Key": "vI8/oEYEWN5sBDTisNuZFjZAl+YFvXEJ96POb73/eoq3NaFPkOFXyPRdf/HWGAFnUsF3e3QpYL6Wl4Bc2v+l3g==",
"Issuer": "LineGestao", "Issuer": "LineGestao",
"Audience": "LineGestao", "Audience": "LineGestao",
"ExpiresMinutes": 120 "ExpiresMinutes": 180
}, },
"Notifications": { "Notifications": {
"CheckIntervalMinutes": 60, "CheckIntervalMinutes": 60,