Merge dfa34e0f5f into bde42cbe52
This commit is contained in:
commit
50f6bdf368
|
|
@ -0,0 +1,25 @@
|
||||||
|
using line_gestao_api.Dtos;
|
||||||
|
using line_gestao_api.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/dashboard/geral")]
|
||||||
|
public class DashboardGeralController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly GeralDashboardInsightsService _service;
|
||||||
|
|
||||||
|
public DashboardGeralController(GeralDashboardInsightsService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("insights")]
|
||||||
|
public async Task<ActionResult<GeralDashboardInsightsDto>> GetInsights()
|
||||||
|
{
|
||||||
|
var dto = await _service.GetInsightsAsync();
|
||||||
|
return Ok(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -400,6 +400,7 @@ namespace line_gestao_api.Controllers
|
||||||
Skeelo = req.Skeelo,
|
Skeelo = req.Skeelo,
|
||||||
VivoNewsPlus = req.VivoNewsPlus,
|
VivoNewsPlus = req.VivoNewsPlus,
|
||||||
VivoTravelMundo = req.VivoTravelMundo,
|
VivoTravelMundo = req.VivoTravelMundo,
|
||||||
|
VivoSync = req.VivoSync,
|
||||||
VivoGestaoDispositivo = req.VivoGestaoDispositivo,
|
VivoGestaoDispositivo = req.VivoGestaoDispositivo,
|
||||||
ValorContratoVivo = req.ValorContratoVivo,
|
ValorContratoVivo = req.ValorContratoVivo,
|
||||||
FranquiaLine = req.FranquiaLine,
|
FranquiaLine = req.FranquiaLine,
|
||||||
|
|
@ -408,6 +409,7 @@ namespace line_gestao_api.Controllers
|
||||||
ValorContratoLine = req.ValorContratoLine,
|
ValorContratoLine = req.ValorContratoLine,
|
||||||
Desconto = req.Desconto,
|
Desconto = req.Desconto,
|
||||||
Lucro = req.Lucro,
|
Lucro = req.Lucro,
|
||||||
|
TipoDeChip = req.TipoDeChip?.Trim(),
|
||||||
|
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
|
|
@ -460,6 +462,7 @@ namespace line_gestao_api.Controllers
|
||||||
x.Skeelo = req.Skeelo;
|
x.Skeelo = req.Skeelo;
|
||||||
x.VivoNewsPlus = req.VivoNewsPlus;
|
x.VivoNewsPlus = req.VivoNewsPlus;
|
||||||
x.VivoTravelMundo = req.VivoTravelMundo;
|
x.VivoTravelMundo = req.VivoTravelMundo;
|
||||||
|
x.VivoSync = req.VivoSync;
|
||||||
x.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
|
x.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
|
||||||
x.ValorContratoVivo = req.ValorContratoVivo;
|
x.ValorContratoVivo = req.ValorContratoVivo;
|
||||||
x.FranquiaLine = req.FranquiaLine;
|
x.FranquiaLine = req.FranquiaLine;
|
||||||
|
|
@ -477,6 +480,7 @@ namespace line_gestao_api.Controllers
|
||||||
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
||||||
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
||||||
x.VencConta = req.VencConta?.Trim();
|
x.VencConta = req.VencConta?.Trim();
|
||||||
|
x.TipoDeChip = req.TipoDeChip?.Trim();
|
||||||
|
|
||||||
ApplyReservaRule(x);
|
ApplyReservaRule(x);
|
||||||
x.UpdatedAt = DateTime.UtcNow;
|
x.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
@ -576,6 +580,7 @@ namespace line_gestao_api.Controllers
|
||||||
Skeelo = TryDecimal(GetCellByHeaderAny(ws, r, map, "SKEELO")),
|
Skeelo = TryDecimal(GetCellByHeaderAny(ws, r, map, "SKEELO")),
|
||||||
VivoNewsPlus = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO NEWS PLUS")),
|
VivoNewsPlus = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO NEWS PLUS")),
|
||||||
VivoTravelMundo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO TRAVEL MUNDO")),
|
VivoTravelMundo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO TRAVEL MUNDO")),
|
||||||
|
VivoSync = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO SYNC")),
|
||||||
VivoGestaoDispositivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
VivoGestaoDispositivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
||||||
ValorContratoVivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO")),
|
ValorContratoVivo = TryDecimal(GetCellByHeaderAny(ws, r, map, "VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO")),
|
||||||
FranquiaLine = TryDecimal(GetCellByHeaderAny(ws, r, map, "FRANQUIA LINE", "FRAQUIA LINE")),
|
FranquiaLine = TryDecimal(GetCellByHeaderAny(ws, r, map, "FRANQUIA LINE", "FRAQUIA LINE")),
|
||||||
|
|
@ -593,6 +598,7 @@ namespace line_gestao_api.Controllers
|
||||||
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
||||||
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
||||||
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA"),
|
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA"),
|
||||||
|
TipoDeChip = GetCellByHeaderAny(ws, r, map, "TIPO DE CHIP", "TIPO CHIP"),
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
};
|
};
|
||||||
|
|
@ -1520,9 +1526,9 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
private async Task ImportResumoTabela1(IXLWorksheet ws, DateTime now)
|
private async Task ImportResumoTabela1(IXLWorksheet ws, DateTime now)
|
||||||
{
|
{
|
||||||
const int headerRow = 5;
|
var lastRowUsed = ws.LastRowUsed()?.RowNumber() ?? 1;
|
||||||
const int totalRow = 72;
|
var headerRow = FindHeaderRowForMacrophonyPlans(ws, 1, lastRowUsed);
|
||||||
var lastRow = Math.Min(totalRow - 1, ws.LastRowUsed()?.RowNumber() ?? totalRow - 1);
|
if (headerRow == 0) return;
|
||||||
|
|
||||||
var map = BuildHeaderMap(ws.Row(headerRow));
|
var map = BuildHeaderMap(ws.Row(headerRow));
|
||||||
var colPlano = GetCol(map, "PLANO CONTRATO");
|
var colPlano = GetCol(map, "PLANO CONTRATO");
|
||||||
|
|
@ -1533,8 +1539,15 @@ namespace line_gestao_api.Controllers
|
||||||
var colValorTotal = GetCol(map, "VALOR TOTAL");
|
var colValorTotal = GetCol(map, "VALOR TOTAL");
|
||||||
|
|
||||||
var buffer = new List<ResumoMacrophonyPlan>(200);
|
var buffer = new List<ResumoMacrophonyPlan>(200);
|
||||||
|
string? lastPlanoContrato = null;
|
||||||
|
decimal? lastGb = null;
|
||||||
|
var dataStarted = false;
|
||||||
|
var emptyDataStreak = 0;
|
||||||
|
int? totalRowIndex = null;
|
||||||
|
var missingPlanoCount = 0;
|
||||||
|
var missingGbCount = 0;
|
||||||
|
|
||||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
for (int r = headerRow + 1; r <= lastRowUsed; r++)
|
||||||
{
|
{
|
||||||
var plano = GetCellString(ws, r, colPlano);
|
var plano = GetCellString(ws, r, colPlano);
|
||||||
var gb = GetCellString(ws, r, colGb);
|
var gb = GetCellString(ws, r, colGb);
|
||||||
|
|
@ -1543,27 +1556,90 @@ namespace line_gestao_api.Controllers
|
||||||
var totalLinhas = GetCellString(ws, r, colTotalLinhas);
|
var totalLinhas = GetCellString(ws, r, colTotalLinhas);
|
||||||
var valorTotal = GetCellString(ws, r, colValorTotal);
|
var valorTotal = GetCellString(ws, r, colValorTotal);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(plano)
|
var isPlanoTotal = !string.IsNullOrWhiteSpace(plano)
|
||||||
|
&& NormalizeHeader(plano) == NormalizeHeader("TOTAL");
|
||||||
|
|
||||||
|
if (isPlanoTotal)
|
||||||
|
{
|
||||||
|
totalRowIndex = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasAnyValue = !(string.IsNullOrWhiteSpace(plano)
|
||||||
&& string.IsNullOrWhiteSpace(gb)
|
&& string.IsNullOrWhiteSpace(gb)
|
||||||
&& string.IsNullOrWhiteSpace(valorInd)
|
&& string.IsNullOrWhiteSpace(valorInd)
|
||||||
&& string.IsNullOrWhiteSpace(franquia)
|
&& string.IsNullOrWhiteSpace(franquia)
|
||||||
&& string.IsNullOrWhiteSpace(totalLinhas)
|
&& string.IsNullOrWhiteSpace(totalLinhas)
|
||||||
&& string.IsNullOrWhiteSpace(valorTotal))
|
&& string.IsNullOrWhiteSpace(valorTotal));
|
||||||
|
|
||||||
|
if (!hasAnyValue)
|
||||||
|
{
|
||||||
|
if (dataStarted)
|
||||||
|
{
|
||||||
|
emptyDataStreak++;
|
||||||
|
if (emptyDataStreak >= 2) break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyDataStreak = 0;
|
||||||
|
|
||||||
|
var franquiaValue = TryDecimal(franquia);
|
||||||
|
var totalLinhasValue = TryNullableInt(totalLinhas);
|
||||||
|
var isDataRow = franquiaValue.HasValue || totalLinhasValue.HasValue;
|
||||||
|
if (isDataRow) dataStarted = true;
|
||||||
|
if (!isDataRow && dataStarted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isDataRow)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var planoNormalized = NormalizeHeader(plano);
|
||||||
|
if (!string.IsNullOrWhiteSpace(plano)
|
||||||
|
&& planoNormalized != NormalizeHeader("PLANO CONTRATO")
|
||||||
|
&& planoNormalized != NormalizeHeader("TOTAL"))
|
||||||
|
{
|
||||||
|
lastPlanoContrato = plano.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gbValue = TryDecimal(gb);
|
||||||
|
if (gbValue.HasValue)
|
||||||
|
{
|
||||||
|
lastGb = gbValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedPlano = isDataRow
|
||||||
|
? (string.IsNullOrWhiteSpace(plano) ? lastPlanoContrato : plano.Trim())
|
||||||
|
: (string.IsNullOrWhiteSpace(plano) ? null : plano.Trim());
|
||||||
|
|
||||||
|
var resolvedGb = isDataRow
|
||||||
|
? (gbValue ?? lastGb)
|
||||||
|
: gbValue;
|
||||||
|
|
||||||
|
if (isDataRow && string.IsNullOrWhiteSpace(resolvedPlano))
|
||||||
|
{
|
||||||
|
missingPlanoCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDataRow && !resolvedGb.HasValue)
|
||||||
|
{
|
||||||
|
missingGbCount++;
|
||||||
|
}
|
||||||
|
|
||||||
var vivoTravelCell = ws.Cell(r, 8).GetString();
|
var vivoTravelCell = ws.Cell(r, 8).GetString();
|
||||||
var vivoTravel = !string.IsNullOrWhiteSpace(vivoTravelCell)
|
var vivoTravel = !string.IsNullOrWhiteSpace(vivoTravelCell)
|
||||||
&& vivoTravelCell.Contains("VIVO TRAVEL", StringComparison.OrdinalIgnoreCase);
|
&& vivoTravelCell.Contains("VIVO TRAVEL", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
buffer.Add(new ResumoMacrophonyPlan
|
buffer.Add(new ResumoMacrophonyPlan
|
||||||
{
|
{
|
||||||
PlanoContrato = string.IsNullOrWhiteSpace(plano) ? null : plano.Trim(),
|
PlanoContrato = string.IsNullOrWhiteSpace(resolvedPlano) ? null : resolvedPlano,
|
||||||
Gb = TryDecimal(gb),
|
Gb = resolvedGb,
|
||||||
ValorIndividualComSvas = TryDecimal(valorInd),
|
ValorIndividualComSvas = TryDecimal(valorInd),
|
||||||
FranquiaGb = TryDecimal(franquia),
|
FranquiaGb = franquiaValue,
|
||||||
TotalLinhas = TryNullableInt(totalLinhas),
|
TotalLinhas = totalLinhasValue,
|
||||||
ValorTotal = TryDecimal(valorTotal),
|
ValorTotal = TryDecimal(valorTotal),
|
||||||
VivoTravel = vivoTravel,
|
VivoTravel = vivoTravel,
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
|
|
@ -1577,11 +1653,21 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (missingPlanoCount > 0 || missingGbCount > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Import RESUMO/MACROPHONY: {missingPlanoCount} linhas sem PLANO CONTRATO e {missingGbCount} linhas sem GB.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalRowIndex == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var total = new ResumoMacrophonyTotal
|
var total = new ResumoMacrophonyTotal
|
||||||
{
|
{
|
||||||
FranquiaGbTotal = TryDecimal(GetCellString(ws, totalRow, colFranquiaGb)),
|
FranquiaGbTotal = TryDecimal(GetCellString(ws, totalRowIndex.Value, colFranquiaGb)),
|
||||||
TotalLinhasTotal = TryNullableInt(GetCellString(ws, totalRow, colTotalLinhas)),
|
TotalLinhasTotal = TryNullableInt(GetCellString(ws, totalRowIndex.Value, colTotalLinhas)),
|
||||||
ValorTotal = TryDecimal(GetCellString(ws, totalRow, colValorTotal)),
|
ValorTotal = TryDecimal(GetCellString(ws, totalRowIndex.Value, colValorTotal)),
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
};
|
};
|
||||||
|
|
@ -1590,6 +1676,27 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int FindHeaderRowForMacrophonyPlans(IXLWorksheet ws, int startRow, int lastRow)
|
||||||
|
{
|
||||||
|
for (int r = startRow; r <= lastRow; r++)
|
||||||
|
{
|
||||||
|
var row = ws.Row(r);
|
||||||
|
if (!row.CellsUsed().Any()) continue;
|
||||||
|
|
||||||
|
var map = BuildHeaderMap(row);
|
||||||
|
var hasPlano = GetCol(map, "PLANO CONTRATO") > 0;
|
||||||
|
var hasGb = GetCol(map, "GB") > 0;
|
||||||
|
var hasTotalLinhas = GetColAny(map, "TOTAL DE LINHAS", "TOTAL LINHAS") > 0;
|
||||||
|
|
||||||
|
if (hasPlano && hasGb && hasTotalLinhas)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportResumoTabela2(IXLWorksheet ws, DateTime now)
|
private async Task ImportResumoTabela2(IXLWorksheet ws, DateTime now)
|
||||||
{
|
{
|
||||||
const int headerRow = 5;
|
const int headerRow = 5;
|
||||||
|
|
@ -1714,9 +1821,9 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
private async Task ImportResumoTabela4(IXLWorksheet ws, DateTime now)
|
private async Task ImportResumoTabela4(IXLWorksheet ws, DateTime now)
|
||||||
{
|
{
|
||||||
const int headerRow = 74;
|
var lastRowUsed = ws.LastRowUsed()?.RowNumber() ?? 1;
|
||||||
const int totalRow = 81;
|
var headerRow = FindHeaderRowForPlanoContratoResumo(ws, 1, lastRowUsed);
|
||||||
var lastRow = Math.Min(totalRow - 1, ws.LastRowUsed()?.RowNumber() ?? totalRow - 1);
|
if (headerRow == 0) return;
|
||||||
|
|
||||||
var map = BuildHeaderMap(ws.Row(headerRow));
|
var map = BuildHeaderMap(ws.Row(headerRow));
|
||||||
var colPlano = GetCol(map, "PLANO CONTRATO");
|
var colPlano = GetCol(map, "PLANO CONTRATO");
|
||||||
|
|
@ -1725,10 +1832,17 @@ namespace line_gestao_api.Controllers
|
||||||
var colFranquiaGb = GetColAny(map, "FRANQUIA GB", "FRAQUIA GB");
|
var colFranquiaGb = GetColAny(map, "FRANQUIA GB", "FRAQUIA GB");
|
||||||
var colTotalLinhas = GetColAny(map, "TOTAL DE LINHAS", "TOTAL LINHAS");
|
var colTotalLinhas = GetColAny(map, "TOTAL DE LINHAS", "TOTAL LINHAS");
|
||||||
var colValorTotal = GetCol(map, "VALOR TOTAL");
|
var colValorTotal = GetCol(map, "VALOR TOTAL");
|
||||||
|
var colCliente = GetCol(map, "CLIENTE");
|
||||||
|
var colQtdLinhas = GetColAny(map, "QTD DE LINHAS", "QTD. DE LINHAS", "QTD LINHAS");
|
||||||
|
|
||||||
var buffer = new List<ResumoPlanoContratoResumo>(200);
|
var buffer = new List<ResumoPlanoContratoResumo>(200);
|
||||||
|
string? lastPlanoContrato = null;
|
||||||
|
var dataStarted = false;
|
||||||
|
var emptyDataStreak = 0;
|
||||||
|
int? totalRowIndex = null;
|
||||||
|
var missingPlanoCount = 0;
|
||||||
|
|
||||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
for (int r = headerRow + 1; r <= lastRowUsed; r++)
|
||||||
{
|
{
|
||||||
var plano = GetCellString(ws, r, colPlano);
|
var plano = GetCellString(ws, r, colPlano);
|
||||||
var gb = GetCellString(ws, r, colGb);
|
var gb = GetCellString(ws, r, colGb);
|
||||||
|
|
@ -1736,20 +1850,68 @@ namespace line_gestao_api.Controllers
|
||||||
var franquia = GetCellString(ws, r, colFranquiaGb);
|
var franquia = GetCellString(ws, r, colFranquiaGb);
|
||||||
var totalLinhas = GetCellString(ws, r, colTotalLinhas);
|
var totalLinhas = GetCellString(ws, r, colTotalLinhas);
|
||||||
var valorTotal = GetCellString(ws, r, colValorTotal);
|
var valorTotal = GetCellString(ws, r, colValorTotal);
|
||||||
|
var cliente = GetCellString(ws, r, colCliente);
|
||||||
|
var qtdLinhas = GetCellString(ws, r, colQtdLinhas);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(plano)
|
var isPlanoTotal = !string.IsNullOrWhiteSpace(plano)
|
||||||
|
&& NormalizeHeader(plano) == NormalizeHeader("TOTAL");
|
||||||
|
|
||||||
|
if (isPlanoTotal)
|
||||||
|
{
|
||||||
|
totalRowIndex = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasAnyValue = !(string.IsNullOrWhiteSpace(plano)
|
||||||
&& string.IsNullOrWhiteSpace(gb)
|
&& string.IsNullOrWhiteSpace(gb)
|
||||||
&& string.IsNullOrWhiteSpace(valorInd)
|
&& string.IsNullOrWhiteSpace(valorInd)
|
||||||
&& string.IsNullOrWhiteSpace(franquia)
|
&& string.IsNullOrWhiteSpace(franquia)
|
||||||
&& string.IsNullOrWhiteSpace(totalLinhas)
|
&& string.IsNullOrWhiteSpace(totalLinhas)
|
||||||
&& string.IsNullOrWhiteSpace(valorTotal))
|
&& string.IsNullOrWhiteSpace(valorTotal)
|
||||||
|
&& string.IsNullOrWhiteSpace(cliente)
|
||||||
|
&& string.IsNullOrWhiteSpace(qtdLinhas));
|
||||||
|
|
||||||
|
if (!hasAnyValue)
|
||||||
{
|
{
|
||||||
|
if (dataStarted)
|
||||||
|
{
|
||||||
|
emptyDataStreak++;
|
||||||
|
if (emptyDataStreak >= 2) break;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emptyDataStreak = 0;
|
||||||
|
|
||||||
|
var isDataRow = !string.IsNullOrWhiteSpace(cliente) || TryNullableInt(qtdLinhas).HasValue;
|
||||||
|
|
||||||
|
var planoNormalized = NormalizeHeader(plano);
|
||||||
|
if (!string.IsNullOrWhiteSpace(plano)
|
||||||
|
&& planoNormalized != NormalizeHeader("PLANO CONTRATO")
|
||||||
|
&& planoNormalized != NormalizeHeader("TOTAL"))
|
||||||
|
{
|
||||||
|
lastPlanoContrato = plano.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDataRow && dataStarted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDataRow) dataStarted = true;
|
||||||
|
|
||||||
|
var resolvedPlano = isDataRow
|
||||||
|
? (string.IsNullOrWhiteSpace(plano) ? lastPlanoContrato : plano.Trim())
|
||||||
|
: (string.IsNullOrWhiteSpace(plano) ? null : plano.Trim());
|
||||||
|
|
||||||
|
if (isDataRow && string.IsNullOrWhiteSpace(resolvedPlano))
|
||||||
|
{
|
||||||
|
missingPlanoCount++;
|
||||||
|
}
|
||||||
|
|
||||||
buffer.Add(new ResumoPlanoContratoResumo
|
buffer.Add(new ResumoPlanoContratoResumo
|
||||||
{
|
{
|
||||||
PlanoContrato = string.IsNullOrWhiteSpace(plano) ? null : plano.Trim(),
|
PlanoContrato = string.IsNullOrWhiteSpace(resolvedPlano) ? null : resolvedPlano,
|
||||||
Gb = TryDecimal(gb),
|
Gb = TryDecimal(gb),
|
||||||
ValorIndividualComSvas = TryDecimal(valorInd),
|
ValorIndividualComSvas = TryDecimal(valorInd),
|
||||||
FranquiaGb = TryDecimal(franquia),
|
FranquiaGb = TryDecimal(franquia),
|
||||||
|
|
@ -1766,9 +1928,19 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (missingPlanoCount > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Import RESUMO/PLANO CONTRATO: {missingPlanoCount} linhas de dados ficaram sem PLANO CONTRATO.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalRowIndex == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var total = new ResumoPlanoContratoTotal
|
var total = new ResumoPlanoContratoTotal
|
||||||
{
|
{
|
||||||
ValorTotal = TryDecimal(ws.Cell(totalRow, 7).GetString()),
|
ValorTotal = TryDecimal(GetCellString(ws, totalRowIndex.Value, colValorTotal)),
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
};
|
};
|
||||||
|
|
@ -1777,6 +1949,27 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int FindHeaderRowForPlanoContratoResumo(IXLWorksheet ws, int startRow, int lastRow)
|
||||||
|
{
|
||||||
|
for (int r = startRow; r <= lastRow; r++)
|
||||||
|
{
|
||||||
|
var row = ws.Row(r);
|
||||||
|
if (!row.CellsUsed().Any()) continue;
|
||||||
|
|
||||||
|
var map = BuildHeaderMap(row);
|
||||||
|
var hasPlano = GetCol(map, "PLANO CONTRATO") > 0;
|
||||||
|
var hasCliente = GetCol(map, "CLIENTE") > 0;
|
||||||
|
var hasQtd = GetColAny(map, "QTD DE LINHAS", "QTD. DE LINHAS", "QTD LINHAS") > 0;
|
||||||
|
|
||||||
|
if (hasPlano && hasCliente && hasQtd)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportResumoTabela5(IXLWorksheet ws, DateTime now)
|
private async Task ImportResumoTabela5(IXLWorksheet ws, DateTime now)
|
||||||
{
|
{
|
||||||
const int headerRow = 83;
|
const int headerRow = 83;
|
||||||
|
|
@ -1814,65 +2007,111 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
private async Task ImportResumoTabela6(IXLWorksheet ws, DateTime now)
|
private async Task ImportResumoTabela6(IXLWorksheet ws, DateTime now)
|
||||||
{
|
{
|
||||||
const int headerRow = 91;
|
var lastRowUsed = ws.LastRowUsed()?.RowNumber() ?? 1;
|
||||||
const int totalRow = 139;
|
var sectionRow = FindSectionRow(ws, "LINHAS NA RESERVA");
|
||||||
var lastRow = Math.Min(totalRow - 1, ws.LastRowUsed()?.RowNumber() ?? totalRow - 1);
|
if (sectionRow == 0) return;
|
||||||
|
|
||||||
|
var headerRow = FindHeaderRowForReserva(ws, sectionRow + 1, lastRowUsed);
|
||||||
|
if (headerRow == 0) return;
|
||||||
|
|
||||||
var map = BuildHeaderMap(ws.Row(headerRow));
|
var map = BuildHeaderMap(ws.Row(headerRow));
|
||||||
var colDdd = GetCol(map, "DDD");
|
var colDdd = GetCol(map, "DDD");
|
||||||
var colFranquiaGb = GetColAny(map, "FRANQUIA GB", "FRAQUIA GB");
|
var colFranquiaGb = GetColAny(map, "FRANQUIA GB", "FRAQUIA GB");
|
||||||
var colQtdLinhas = GetColAny(map, "QTD. DE LINHAS", "QTD DE LINHAS", "QTD. LINHAS");
|
var colQtdLinhas = GetColAny(map, "QTD. DE LINHAS", "QTD DE LINHAS", "QTD. LINHAS", "QTDLINHAS");
|
||||||
var colTotal = GetCol(map, "TOTAL");
|
var colTotal = GetCol(map, "TOTAL");
|
||||||
|
|
||||||
var buffer = new List<ResumoReservaLine>(200);
|
var buffer = new List<ResumoReservaLine>(200);
|
||||||
decimal? lastTotal = null;
|
string? lastDddValid = null;
|
||||||
|
var dataStarted = false;
|
||||||
|
var emptyRowStreak = 0;
|
||||||
|
int? totalRowIndex = null;
|
||||||
|
|
||||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
for (int r = headerRow + 1; r <= lastRowUsed; r++)
|
||||||
{
|
{
|
||||||
var ddd = GetCellString(ws, r, colDdd);
|
var ddd = GetCellString(ws, r, colDdd);
|
||||||
var franquia = GetCellString(ws, r, colFranquiaGb);
|
var franquia = GetCellString(ws, r, colFranquiaGb);
|
||||||
var qtdLinhas = GetCellString(ws, r, colQtdLinhas);
|
var qtdLinhas = GetCellString(ws, r, colQtdLinhas);
|
||||||
var total = GetCellString(ws, r, colTotal);
|
var total = GetCellString(ws, r, colTotal);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(ddd)
|
var hasAnyValue = !(string.IsNullOrWhiteSpace(ddd)
|
||||||
&& string.IsNullOrWhiteSpace(franquia)
|
&& string.IsNullOrWhiteSpace(franquia)
|
||||||
&& string.IsNullOrWhiteSpace(qtdLinhas)
|
&& string.IsNullOrWhiteSpace(qtdLinhas)
|
||||||
&& string.IsNullOrWhiteSpace(total))
|
&& string.IsNullOrWhiteSpace(total));
|
||||||
|
|
||||||
|
if (!hasAnyValue)
|
||||||
{
|
{
|
||||||
|
if (dataStarted)
|
||||||
|
{
|
||||||
|
emptyRowStreak++;
|
||||||
|
if (emptyRowStreak >= 2) break;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emptyRowStreak = 0;
|
||||||
|
|
||||||
|
var franquiaValue = TryDecimal(franquia);
|
||||||
|
var qtdValue = TryNullableInt(qtdLinhas);
|
||||||
|
var isDataRow = franquiaValue.HasValue || qtdValue.HasValue;
|
||||||
|
var dddCandidate = NullIfEmptyDigits(ddd);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(dddCandidate))
|
||||||
|
{
|
||||||
|
lastDddValid = dddCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTotalRow = !isDataRow && !string.IsNullOrWhiteSpace(total);
|
||||||
|
if (isTotalRow)
|
||||||
|
{
|
||||||
|
totalRowIndex = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDataRow && dataStarted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDataRow) dataStarted = true;
|
||||||
|
|
||||||
|
var resolvedDdd = isDataRow
|
||||||
|
? (dddCandidate ?? lastDddValid)
|
||||||
|
: dddCandidate;
|
||||||
|
|
||||||
var totalValue = TryDecimal(total);
|
var totalValue = TryDecimal(total);
|
||||||
if (!totalValue.HasValue && lastTotal.HasValue)
|
|
||||||
{
|
|
||||||
totalValue = lastTotal;
|
|
||||||
}
|
|
||||||
else if (totalValue.HasValue)
|
|
||||||
{
|
|
||||||
lastTotal = totalValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Add(new ResumoReservaLine
|
buffer.Add(new ResumoReservaLine
|
||||||
{
|
{
|
||||||
Ddd = string.IsNullOrWhiteSpace(ddd) ? null : ddd.Trim(),
|
Ddd = string.IsNullOrWhiteSpace(resolvedDdd) ? null : resolvedDdd,
|
||||||
FranquiaGb = TryDecimal(franquia),
|
FranquiaGb = franquiaValue,
|
||||||
QtdLinhas = TryNullableInt(qtdLinhas),
|
QtdLinhas = qtdValue,
|
||||||
Total = totalValue,
|
Total = totalValue,
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var missingDddCount = buffer.Count(x => x.Ddd == null && (x.FranquiaGb.HasValue || x.QtdLinhas.HasValue));
|
||||||
|
if (missingDddCount > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Import RESUMO/RESERVA: {missingDddCount} linhas de dados ficaram sem DDD.");
|
||||||
|
}
|
||||||
|
|
||||||
if (buffer.Count > 0)
|
if (buffer.Count > 0)
|
||||||
{
|
{
|
||||||
await _db.ResumoReservaLines.AddRangeAsync(buffer);
|
await _db.ResumoReservaLines.AddRangeAsync(buffer);
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (totalRowIndex == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var totalEntity = new ResumoReservaTotal
|
var totalEntity = new ResumoReservaTotal
|
||||||
{
|
{
|
||||||
QtdLinhasTotal = TryNullableInt(GetCellString(ws, totalRow, colQtdLinhas)),
|
QtdLinhasTotal = TryNullableInt(GetCellString(ws, totalRowIndex.Value, colQtdLinhas)),
|
||||||
Total = TryDecimal(GetCellString(ws, totalRow, colTotal)),
|
Total = TryDecimal(GetCellString(ws, totalRowIndex.Value, colTotal)),
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
};
|
};
|
||||||
|
|
@ -1881,6 +2120,43 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int FindSectionRow(IXLWorksheet ws, string sectionName)
|
||||||
|
{
|
||||||
|
var normalizedTarget = NormalizeHeader(sectionName);
|
||||||
|
foreach (var row in ws.RowsUsed())
|
||||||
|
{
|
||||||
|
foreach (var cell in row.CellsUsed())
|
||||||
|
{
|
||||||
|
var key = NormalizeHeader(cell.GetString());
|
||||||
|
if (string.IsNullOrWhiteSpace(key)) continue;
|
||||||
|
if (key.Contains(normalizedTarget)) return row.RowNumber();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FindHeaderRowForReserva(IXLWorksheet ws, int startRow, int lastRow)
|
||||||
|
{
|
||||||
|
for (int r = startRow; r <= lastRow; r++)
|
||||||
|
{
|
||||||
|
var row = ws.Row(r);
|
||||||
|
if (!row.CellsUsed().Any()) continue;
|
||||||
|
|
||||||
|
var map = BuildHeaderMap(row);
|
||||||
|
var hasDdd = GetCol(map, "DDD") > 0;
|
||||||
|
var hasFranquia = GetColAny(map, "FRANQUIA GB", "FRAQUIA GB") > 0;
|
||||||
|
var hasQtd = GetColAny(map, "QTD. DE LINHAS", "QTD DE LINHAS", "QTD. LINHAS", "QTDLINHAS") > 0;
|
||||||
|
|
||||||
|
if (hasDdd && hasFranquia && hasQtd)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportControleRecebidosSheet(IXLWorksheet ws, int year)
|
private async Task ImportControleRecebidosSheet(IXLWorksheet ws, int year)
|
||||||
{
|
{
|
||||||
var buffer = new List<ControleRecebidoLine>(500);
|
var buffer = new List<ControleRecebidoLine>(500);
|
||||||
|
|
@ -2159,6 +2435,7 @@ namespace line_gestao_api.Controllers
|
||||||
Skeelo = x.Skeelo,
|
Skeelo = x.Skeelo,
|
||||||
VivoNewsPlus = x.VivoNewsPlus,
|
VivoNewsPlus = x.VivoNewsPlus,
|
||||||
VivoTravelMundo = x.VivoTravelMundo,
|
VivoTravelMundo = x.VivoTravelMundo,
|
||||||
|
VivoSync = x.VivoSync,
|
||||||
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
||||||
ValorContratoVivo = x.ValorContratoVivo,
|
ValorContratoVivo = x.ValorContratoVivo,
|
||||||
FranquiaLine = x.FranquiaLine,
|
FranquiaLine = x.FranquiaLine,
|
||||||
|
|
@ -2175,7 +2452,8 @@ namespace line_gestao_api.Controllers
|
||||||
Solicitante = x.Solicitante,
|
Solicitante = x.Solicitante,
|
||||||
DataEntregaOpera = x.DataEntregaOpera,
|
DataEntregaOpera = x.DataEntregaOpera,
|
||||||
DataEntregaCliente = x.DataEntregaCliente,
|
DataEntregaCliente = x.DataEntregaCliente,
|
||||||
VencConta = x.VencConta
|
VencConta = x.VencConta,
|
||||||
|
TipoDeChip = x.TipoDeChip
|
||||||
};
|
};
|
||||||
|
|
||||||
private static void ApplyReservaRule(MobileLine x)
|
private static void ApplyReservaRule(MobileLine x)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ namespace line_gestao_api.Dtos
|
||||||
public decimal? Skeelo { get; set; }
|
public decimal? Skeelo { get; set; }
|
||||||
public decimal? VivoNewsPlus { get; set; }
|
public decimal? VivoNewsPlus { get; set; }
|
||||||
public decimal? VivoTravelMundo { get; set; }
|
public decimal? VivoTravelMundo { get; set; }
|
||||||
|
public decimal? VivoSync { get; set; }
|
||||||
public decimal? VivoGestaoDispositivo { get; set; }
|
public decimal? VivoGestaoDispositivo { get; set; }
|
||||||
public decimal? ValorContratoVivo { get; set; }
|
public decimal? ValorContratoVivo { get; set; }
|
||||||
|
|
||||||
|
|
@ -65,5 +66,10 @@ namespace line_gestao_api.Dtos
|
||||||
// ==========================
|
// ==========================
|
||||||
public decimal? Desconto { get; set; }
|
public decimal? Desconto { get; set; }
|
||||||
public decimal? Lucro { get; set; }
|
public decimal? Lucro { get; set; }
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Identificação adicional
|
||||||
|
// ==========================
|
||||||
|
public string? TipoDeChip { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Dtos
|
||||||
|
{
|
||||||
|
public class GeralDashboardInsightsDto
|
||||||
|
{
|
||||||
|
public GeralDashboardKpisDto Kpis { get; set; } = new();
|
||||||
|
public GeralDashboardChartsDto Charts { get; set; } = new();
|
||||||
|
public List<GeralDashboardClientGroupDto> ClientGroups { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardKpisDto
|
||||||
|
{
|
||||||
|
public int TotalLinhas { get; set; }
|
||||||
|
public int TotalAtivas { get; set; }
|
||||||
|
public GeralDashboardVivoKpiDto Vivo { get; set; } = new();
|
||||||
|
public GeralDashboardTravelKpiDto TravelMundo { get; set; } = new();
|
||||||
|
public GeralDashboardAdditionalKpiDto Adicionais { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardVivoKpiDto
|
||||||
|
{
|
||||||
|
public int QtdLinhas { get; set; }
|
||||||
|
public decimal TotalBaseMensal { get; set; }
|
||||||
|
public decimal TotalAdicionaisMensal { get; set; }
|
||||||
|
public decimal TotalGeralMensal { get; set; }
|
||||||
|
public decimal MediaPorLinha { get; set; }
|
||||||
|
public decimal? MinPorLinha { get; set; }
|
||||||
|
public decimal? MaxPorLinha { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardTravelKpiDto
|
||||||
|
{
|
||||||
|
public int ComTravel { get; set; }
|
||||||
|
public int SemTravel { get; set; }
|
||||||
|
public decimal TotalValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardAdditionalKpiDto
|
||||||
|
{
|
||||||
|
public int TotalLinesWithAnyPaidAdditional { get; set; }
|
||||||
|
public int TotalLinesWithNoPaidAdditional { get; set; }
|
||||||
|
public List<GeralDashboardServiceKpiDto> ServicesPaid { get; set; } = new();
|
||||||
|
public List<GeralDashboardServiceKpiDto> ServicesNotPaid { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardServiceKpiDto
|
||||||
|
{
|
||||||
|
public string ServiceName { get; set; } = string.Empty;
|
||||||
|
public int CountLines { get; set; }
|
||||||
|
public decimal TotalValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardChartsDto
|
||||||
|
{
|
||||||
|
public GeralDashboardChartDto LinhasPorFranquia { get; set; } = new();
|
||||||
|
public GeralDashboardChartDto AdicionaisPagosPorServico { get; set; } = new();
|
||||||
|
public GeralDashboardChartDto TravelMundo { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardChartDto
|
||||||
|
{
|
||||||
|
public List<string> Labels { get; set; } = new();
|
||||||
|
public List<int> Values { get; set; } = new();
|
||||||
|
public List<decimal>? Totals { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardClientGroupDto
|
||||||
|
{
|
||||||
|
public string Cliente { get; set; } = string.Empty;
|
||||||
|
public int TotalLinhas { get; set; }
|
||||||
|
public int Ativos { get; set; }
|
||||||
|
public int Bloqueados { get; set; }
|
||||||
|
public int LinhasVivo { get; set; }
|
||||||
|
public List<GeralDashboardTagDto> TagsBase { get; set; } = new();
|
||||||
|
public List<GeralDashboardTagDto> TagsExtras { get; set; } = new();
|
||||||
|
public List<GeralDashboardClientServiceDto> AdicionaisPorServico { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardClientServiceDto
|
||||||
|
{
|
||||||
|
public string ServiceName { get; set; } = string.Empty;
|
||||||
|
public int PaidCount { get; set; }
|
||||||
|
public int NotPaidCount { get; set; }
|
||||||
|
public decimal TotalValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardTagDto
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
public decimal? Skeelo { get; set; }
|
public decimal? Skeelo { get; set; }
|
||||||
public decimal? VivoNewsPlus { get; set; }
|
public decimal? VivoNewsPlus { get; set; }
|
||||||
public decimal? VivoTravelMundo { get; set; }
|
public decimal? VivoTravelMundo { get; set; }
|
||||||
|
public decimal? VivoSync { get; set; }
|
||||||
public decimal? VivoGestaoDispositivo { get; set; }
|
public decimal? VivoGestaoDispositivo { get; set; }
|
||||||
public decimal? ValorContratoVivo { get; set; }
|
public decimal? ValorContratoVivo { get; set; }
|
||||||
|
|
||||||
|
|
@ -53,6 +54,7 @@
|
||||||
public DateTime? DataEntregaOpera { get; set; }
|
public DateTime? DataEntregaOpera { get; set; }
|
||||||
public DateTime? DataEntregaCliente { get; set; }
|
public DateTime? DataEntregaCliente { get; set; }
|
||||||
public string? VencConta { get; set; }
|
public string? VencConta { get; set; }
|
||||||
|
public string? TipoDeChip { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ UPDATE REQUEST (SEM Id)
|
// ✅ UPDATE REQUEST (SEM Id)
|
||||||
|
|
@ -72,6 +74,7 @@
|
||||||
public decimal? Skeelo { get; set; }
|
public decimal? Skeelo { get; set; }
|
||||||
public decimal? VivoNewsPlus { get; set; }
|
public decimal? VivoNewsPlus { get; set; }
|
||||||
public decimal? VivoTravelMundo { get; set; }
|
public decimal? VivoTravelMundo { get; set; }
|
||||||
|
public decimal? VivoSync { get; set; }
|
||||||
public decimal? VivoGestaoDispositivo { get; set; }
|
public decimal? VivoGestaoDispositivo { get; set; }
|
||||||
public decimal? ValorContratoVivo { get; set; }
|
public decimal? ValorContratoVivo { get; set; }
|
||||||
|
|
||||||
|
|
@ -92,6 +95,7 @@
|
||||||
public DateTime? DataEntregaOpera { get; set; }
|
public DateTime? DataEntregaOpera { get; set; }
|
||||||
public DateTime? DataEntregaCliente { get; set; }
|
public DateTime? DataEntregaCliente { get; set; }
|
||||||
public string? VencConta { get; set; }
|
public string? VencConta { get; set; }
|
||||||
|
public string? TipoDeChip { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImportResultDto
|
public class ImportResultDto
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ namespace line_gestao_api.Models
|
||||||
public decimal? Skeelo { get; set; }
|
public decimal? Skeelo { get; set; }
|
||||||
public decimal? VivoNewsPlus { get; set; }
|
public decimal? VivoNewsPlus { get; set; }
|
||||||
public decimal? VivoTravelMundo { get; set; }
|
public decimal? VivoTravelMundo { get; set; }
|
||||||
|
public decimal? VivoSync { get; set; }
|
||||||
public decimal? VivoGestaoDispositivo { get; set; }
|
public decimal? VivoGestaoDispositivo { get; set; }
|
||||||
public decimal? ValorContratoVivo { get; set; }
|
public decimal? ValorContratoVivo { get; set; }
|
||||||
|
|
||||||
|
|
@ -59,6 +60,8 @@ namespace line_gestao_api.Models
|
||||||
|
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string? VencConta { get; set; }
|
public string? VencConta { get; set; }
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? TipoDeChip { get; set; }
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,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<ParcelamentosImportService>();
|
builder.Services.AddScoped<ParcelamentosImportService>();
|
||||||
|
builder.Services.AddScoped<GeralDashboardInsightsService>();
|
||||||
|
|
||||||
builder.Services.AddIdentityCore<ApplicationUser>(options =>
|
builder.Services.AddIdentityCore<ApplicationUser>(options =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,917 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
using line_gestao_api.Dtos;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Services
|
||||||
|
{
|
||||||
|
public class GeralDashboardInsightsService
|
||||||
|
{
|
||||||
|
private static readonly CultureInfo PtBr = new("pt-BR");
|
||||||
|
|
||||||
|
private const string ServiceGestaoVozDados = "GESTÃO VOZ E DADOS";
|
||||||
|
private const string ServiceSkeelo = "SKEELO";
|
||||||
|
private const string ServiceVivoNewsPlus = "VIVO NEWS PLUS";
|
||||||
|
private const string ServiceVivoTravelMundo = "VIVO TRAVEL MUNDO";
|
||||||
|
private const string ServiceVivoGestaoDispositivo = "VIVO GESTÃO DISPOSITIVO";
|
||||||
|
|
||||||
|
private readonly AppDbContext _db;
|
||||||
|
|
||||||
|
public GeralDashboardInsightsService(AppDbContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GeralDashboardInsightsDto> GetInsightsAsync()
|
||||||
|
{
|
||||||
|
var qLines = _db.MobileLines.AsNoTracking();
|
||||||
|
|
||||||
|
var totals = await qLines
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(g => new TotalsProjection
|
||||||
|
{
|
||||||
|
TotalLinhas = g.Count(),
|
||||||
|
TotalAtivas = g.Count(x => (x.Status ?? "").ToLower().Contains("ativo")),
|
||||||
|
TotalBloqueados = g.Count(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("bloque") ||
|
||||||
|
(x.Status ?? "").ToLower().Contains("perda") ||
|
||||||
|
(x.Status ?? "").ToLower().Contains("roubo")),
|
||||||
|
VivoLinhas = g.Count(x =>
|
||||||
|
x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null),
|
||||||
|
VivoBaseTotal = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.ValorPlanoVivo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
VivoAdicionaisTotal = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.GestaoVozDados ?? 0m) + (x.Skeelo ?? 0m) + (x.VivoNewsPlus ?? 0m) +
|
||||||
|
(x.VivoTravelMundo ?? 0m) + (x.VivoGestaoDispositivo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
VivoMinBase = g.Where(x =>
|
||||||
|
x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
.Select(x => x.ValorPlanoVivo ?? 0m)
|
||||||
|
.DefaultIfEmpty()
|
||||||
|
.Min(),
|
||||||
|
VivoMaxBase = g.Where(x =>
|
||||||
|
x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
.Select(x => x.ValorPlanoVivo ?? 0m)
|
||||||
|
.DefaultIfEmpty()
|
||||||
|
.Max(),
|
||||||
|
TravelCom = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
x.VivoTravelMundo != null),
|
||||||
|
TravelSem = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
x.VivoTravelMundo == null),
|
||||||
|
TravelTotal = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoTravelMundo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
PaidGestaoVozDados = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.GestaoVozDados ?? 0m) > 0m),
|
||||||
|
PaidSkeelo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.Skeelo ?? 0m) > 0m),
|
||||||
|
PaidNews = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoNewsPlus ?? 0m) > 0m),
|
||||||
|
PaidTravel = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoTravelMundo ?? 0m) > 0m),
|
||||||
|
PaidGestaoDispositivo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) > 0m),
|
||||||
|
NotPaidGestaoVozDados = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.GestaoVozDados ?? 0m) <= 0m),
|
||||||
|
NotPaidSkeelo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.Skeelo ?? 0m) <= 0m),
|
||||||
|
NotPaidNews = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoNewsPlus ?? 0m) <= 0m),
|
||||||
|
NotPaidTravel = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoTravelMundo ?? 0m) <= 0m),
|
||||||
|
NotPaidGestaoDispositivo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) <= 0m),
|
||||||
|
TotalLinesWithAnyPaidAdditional = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
((x.GestaoVozDados ?? 0m) > 0m ||
|
||||||
|
(x.Skeelo ?? 0m) > 0m ||
|
||||||
|
(x.VivoNewsPlus ?? 0m) > 0m ||
|
||||||
|
(x.VivoTravelMundo ?? 0m) > 0m ||
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) > 0m)),
|
||||||
|
TotalLinesWithNoPaidAdditional = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.GestaoVozDados ?? 0m) <= 0m &&
|
||||||
|
(x.Skeelo ?? 0m) <= 0m &&
|
||||||
|
(x.VivoNewsPlus ?? 0m) <= 0m &&
|
||||||
|
(x.VivoTravelMundo ?? 0m) <= 0m &&
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) <= 0m),
|
||||||
|
TotalGestaoVozDados = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.GestaoVozDados ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalSkeelo = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.Skeelo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalNews = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoNewsPlus ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalTravel = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoTravelMundo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalGestaoDispositivo = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoGestaoDispositivo ?? 0m)
|
||||||
|
: 0m)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var franquiasRaw = await qLines
|
||||||
|
.Select(x => new FranquiaProjection { FranquiaVivo = x.FranquiaVivo, PlanoContrato = x.PlanoContrato })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var linhasPorFranquia = BuildFranquiaBuckets(franquiasRaw);
|
||||||
|
|
||||||
|
var clientGroupsRaw = await qLines
|
||||||
|
.Where(x => x.Cliente != null && x.Cliente != "")
|
||||||
|
.GroupBy(x => x.Cliente!)
|
||||||
|
.Select(g => new ClientGroupProjection
|
||||||
|
{
|
||||||
|
Cliente = g.Key,
|
||||||
|
TotalLinhas = g.Count(),
|
||||||
|
Ativos = g.Count(x => (x.Status ?? "").ToLower().Contains("ativo")),
|
||||||
|
Bloqueados = g.Count(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("bloque") ||
|
||||||
|
(x.Status ?? "").ToLower().Contains("perda") ||
|
||||||
|
(x.Status ?? "").ToLower().Contains("roubo")),
|
||||||
|
LinhasVivo = g.Count(x =>
|
||||||
|
x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null),
|
||||||
|
TravelCom = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
x.VivoTravelMundo != null),
|
||||||
|
TravelSem = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
x.VivoTravelMundo == null),
|
||||||
|
PaidGestaoVozDados = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.GestaoVozDados ?? 0m) > 0m),
|
||||||
|
PaidSkeelo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.Skeelo ?? 0m) > 0m),
|
||||||
|
PaidNews = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoNewsPlus ?? 0m) > 0m),
|
||||||
|
PaidTravel = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoTravelMundo ?? 0m) > 0m),
|
||||||
|
PaidGestaoDispositivo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) > 0m),
|
||||||
|
NotPaidGestaoVozDados = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.GestaoVozDados ?? 0m) <= 0m),
|
||||||
|
NotPaidSkeelo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.Skeelo ?? 0m) <= 0m),
|
||||||
|
NotPaidNews = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoNewsPlus ?? 0m) <= 0m),
|
||||||
|
NotPaidTravel = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoTravelMundo ?? 0m) <= 0m),
|
||||||
|
NotPaidGestaoDispositivo = g.Count(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null) &&
|
||||||
|
(x.VivoGestaoDispositivo ?? 0m) <= 0m),
|
||||||
|
TotalGestaoVozDados = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.GestaoVozDados ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalSkeelo = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.Skeelo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalNews = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoNewsPlus ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalTravel = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoTravelMundo ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
TotalGestaoDispositivo = g.Sum(x =>
|
||||||
|
(x.ValorPlanoVivo != null ||
|
||||||
|
x.FranquiaVivo != null ||
|
||||||
|
x.ValorContratoVivo != null ||
|
||||||
|
x.GestaoVozDados != null ||
|
||||||
|
x.Skeelo != null ||
|
||||||
|
x.VivoNewsPlus != null ||
|
||||||
|
x.VivoTravelMundo != null ||
|
||||||
|
x.VivoGestaoDispositivo != null)
|
||||||
|
? (x.VivoGestaoDispositivo ?? 0m)
|
||||||
|
: 0m)
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.Cliente)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var dto = new GeralDashboardInsightsDto
|
||||||
|
{
|
||||||
|
Kpis = BuildKpis(totals),
|
||||||
|
Charts = BuildCharts(totals, linhasPorFranquia),
|
||||||
|
ClientGroups = BuildClientGroups(clientGroupsRaw)
|
||||||
|
};
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GeralDashboardKpisDto BuildKpis(TotalsProjection? totals)
|
||||||
|
{
|
||||||
|
if (totals == null)
|
||||||
|
{
|
||||||
|
return new GeralDashboardKpisDto
|
||||||
|
{
|
||||||
|
Vivo = new GeralDashboardVivoKpiDto(),
|
||||||
|
TravelMundo = new GeralDashboardTravelKpiDto(),
|
||||||
|
Adicionais = new GeralDashboardAdditionalKpiDto()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalGeralMensal = totals.VivoBaseTotal + totals.VivoAdicionaisTotal;
|
||||||
|
var media = totals.VivoLinhas > 0 ? totalGeralMensal / totals.VivoLinhas : 0m;
|
||||||
|
|
||||||
|
return new GeralDashboardKpisDto
|
||||||
|
{
|
||||||
|
TotalLinhas = totals.TotalLinhas,
|
||||||
|
TotalAtivas = totals.TotalAtivas,
|
||||||
|
Vivo = new GeralDashboardVivoKpiDto
|
||||||
|
{
|
||||||
|
QtdLinhas = totals.VivoLinhas,
|
||||||
|
TotalBaseMensal = totals.VivoBaseTotal,
|
||||||
|
TotalAdicionaisMensal = totals.VivoAdicionaisTotal,
|
||||||
|
TotalGeralMensal = totalGeralMensal,
|
||||||
|
MediaPorLinha = media,
|
||||||
|
MinPorLinha = totals.VivoLinhas > 0 ? totals.VivoMinBase : null,
|
||||||
|
MaxPorLinha = totals.VivoLinhas > 0 ? totals.VivoMaxBase : null
|
||||||
|
},
|
||||||
|
TravelMundo = new GeralDashboardTravelKpiDto
|
||||||
|
{
|
||||||
|
ComTravel = totals.TravelCom,
|
||||||
|
SemTravel = totals.TravelSem,
|
||||||
|
TotalValue = totals.TravelTotal
|
||||||
|
},
|
||||||
|
Adicionais = new GeralDashboardAdditionalKpiDto
|
||||||
|
{
|
||||||
|
TotalLinesWithAnyPaidAdditional = totals.TotalLinesWithAnyPaidAdditional,
|
||||||
|
TotalLinesWithNoPaidAdditional = totals.TotalLinesWithNoPaidAdditional,
|
||||||
|
ServicesPaid = new List<GeralDashboardServiceKpiDto>
|
||||||
|
{
|
||||||
|
new() { ServiceName = ServiceGestaoVozDados, CountLines = totals.PaidGestaoVozDados, TotalValue = totals.TotalGestaoVozDados },
|
||||||
|
new() { ServiceName = ServiceSkeelo, CountLines = totals.PaidSkeelo, TotalValue = totals.TotalSkeelo },
|
||||||
|
new() { ServiceName = ServiceVivoNewsPlus, CountLines = totals.PaidNews, TotalValue = totals.TotalNews },
|
||||||
|
new() { ServiceName = ServiceVivoTravelMundo, CountLines = totals.PaidTravel, TotalValue = totals.TotalTravel },
|
||||||
|
new() { ServiceName = ServiceVivoGestaoDispositivo, CountLines = totals.PaidGestaoDispositivo, TotalValue = totals.TotalGestaoDispositivo }
|
||||||
|
},
|
||||||
|
ServicesNotPaid = new List<GeralDashboardServiceKpiDto>
|
||||||
|
{
|
||||||
|
new() { ServiceName = ServiceGestaoVozDados, CountLines = totals.NotPaidGestaoVozDados, TotalValue = 0m },
|
||||||
|
new() { ServiceName = ServiceSkeelo, CountLines = totals.NotPaidSkeelo, TotalValue = 0m },
|
||||||
|
new() { ServiceName = ServiceVivoNewsPlus, CountLines = totals.NotPaidNews, TotalValue = 0m },
|
||||||
|
new() { ServiceName = ServiceVivoTravelMundo, CountLines = totals.NotPaidTravel, TotalValue = 0m },
|
||||||
|
new() { ServiceName = ServiceVivoGestaoDispositivo, CountLines = totals.NotPaidGestaoDispositivo, TotalValue = 0m }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GeralDashboardChartsDto BuildCharts(TotalsProjection? totals, FranquiaBuckets franquias)
|
||||||
|
{
|
||||||
|
var adicionaisLabels = new List<string>
|
||||||
|
{
|
||||||
|
ServiceGestaoVozDados,
|
||||||
|
ServiceSkeelo,
|
||||||
|
ServiceVivoNewsPlus,
|
||||||
|
ServiceVivoTravelMundo,
|
||||||
|
ServiceVivoGestaoDispositivo
|
||||||
|
};
|
||||||
|
|
||||||
|
var adicionaisValues = totals == null
|
||||||
|
? new List<int> { 0, 0, 0, 0, 0 }
|
||||||
|
: new List<int>
|
||||||
|
{
|
||||||
|
totals.PaidGestaoVozDados,
|
||||||
|
totals.PaidSkeelo,
|
||||||
|
totals.PaidNews,
|
||||||
|
totals.PaidTravel,
|
||||||
|
totals.PaidGestaoDispositivo
|
||||||
|
};
|
||||||
|
|
||||||
|
var adicionaisTotals = totals == null
|
||||||
|
? new List<decimal> { 0m, 0m, 0m, 0m, 0m }
|
||||||
|
: new List<decimal>
|
||||||
|
{
|
||||||
|
totals.TotalGestaoVozDados,
|
||||||
|
totals.TotalSkeelo,
|
||||||
|
totals.TotalNews,
|
||||||
|
totals.TotalTravel,
|
||||||
|
totals.TotalGestaoDispositivo
|
||||||
|
};
|
||||||
|
|
||||||
|
return new GeralDashboardChartsDto
|
||||||
|
{
|
||||||
|
LinhasPorFranquia = new GeralDashboardChartDto
|
||||||
|
{
|
||||||
|
Labels = franquias.Labels,
|
||||||
|
Values = franquias.Values
|
||||||
|
},
|
||||||
|
AdicionaisPagosPorServico = new GeralDashboardChartDto
|
||||||
|
{
|
||||||
|
Labels = adicionaisLabels,
|
||||||
|
Values = adicionaisValues,
|
||||||
|
Totals = adicionaisTotals
|
||||||
|
},
|
||||||
|
TravelMundo = new GeralDashboardChartDto
|
||||||
|
{
|
||||||
|
Labels = new List<string> { "Com", "Sem" },
|
||||||
|
Values = totals == null
|
||||||
|
? new List<int> { 0, 0 }
|
||||||
|
: new List<int> { totals.TravelCom, totals.TravelSem }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GeralDashboardClientGroupDto> BuildClientGroups(IEnumerable<ClientGroupProjection> rows)
|
||||||
|
{
|
||||||
|
var list = new List<GeralDashboardClientGroupDto>();
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var baseTags = new List<GeralDashboardTagDto>
|
||||||
|
{
|
||||||
|
new() { Label = "LINHAS", Value = row.TotalLinhas.ToString(PtBr) },
|
||||||
|
new() { Label = "ATIVAS", Value = row.Ativos.ToString(PtBr) },
|
||||||
|
new() { Label = "BLOQUEADAS", Value = row.Bloqueados.ToString(PtBr) },
|
||||||
|
new() { Label = "LINHAS VIVO", Value = row.LinhasVivo.ToString(PtBr) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var extras = new List<GeralDashboardTagDto>
|
||||||
|
{
|
||||||
|
new() { Label = "TRAVEL MUNDO", Value = row.TravelCom.ToString(PtBr) },
|
||||||
|
new() { Label = "SEM TRAVEL", Value = row.TravelSem.ToString(PtBr) },
|
||||||
|
new() { Label = ServiceGestaoVozDados, Value = row.PaidGestaoVozDados.ToString(PtBr) },
|
||||||
|
new() { Label = $"R$ {ServiceGestaoVozDados}", Value = FormatCurrency(row.TotalGestaoVozDados) },
|
||||||
|
new() { Label = ServiceSkeelo, Value = row.PaidSkeelo.ToString(PtBr) },
|
||||||
|
new() { Label = $"R$ {ServiceSkeelo}", Value = FormatCurrency(row.TotalSkeelo) },
|
||||||
|
new() { Label = ServiceVivoNewsPlus, Value = row.PaidNews.ToString(PtBr) },
|
||||||
|
new() { Label = $"R$ {ServiceVivoNewsPlus}", Value = FormatCurrency(row.TotalNews) },
|
||||||
|
new() { Label = ServiceVivoTravelMundo, Value = row.PaidTravel.ToString(PtBr) },
|
||||||
|
new() { Label = $"R$ {ServiceVivoTravelMundo}", Value = FormatCurrency(row.TotalTravel) },
|
||||||
|
new() { Label = ServiceVivoGestaoDispositivo, Value = row.PaidGestaoDispositivo.ToString(PtBr) },
|
||||||
|
new() { Label = $"R$ {ServiceVivoGestaoDispositivo}", Value = FormatCurrency(row.TotalGestaoDispositivo) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceDetails = new List<GeralDashboardClientServiceDto>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ServiceName = ServiceGestaoVozDados,
|
||||||
|
PaidCount = row.PaidGestaoVozDados,
|
||||||
|
NotPaidCount = row.NotPaidGestaoVozDados,
|
||||||
|
TotalValue = row.TotalGestaoVozDados
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ServiceName = ServiceSkeelo,
|
||||||
|
PaidCount = row.PaidSkeelo,
|
||||||
|
NotPaidCount = row.NotPaidSkeelo,
|
||||||
|
TotalValue = row.TotalSkeelo
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ServiceName = ServiceVivoNewsPlus,
|
||||||
|
PaidCount = row.PaidNews,
|
||||||
|
NotPaidCount = row.NotPaidNews,
|
||||||
|
TotalValue = row.TotalNews
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ServiceName = ServiceVivoTravelMundo,
|
||||||
|
PaidCount = row.PaidTravel,
|
||||||
|
NotPaidCount = row.NotPaidTravel,
|
||||||
|
TotalValue = row.TotalTravel
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ServiceName = ServiceVivoGestaoDispositivo,
|
||||||
|
PaidCount = row.PaidGestaoDispositivo,
|
||||||
|
NotPaidCount = row.NotPaidGestaoDispositivo,
|
||||||
|
TotalValue = row.TotalGestaoDispositivo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
list.Add(new GeralDashboardClientGroupDto
|
||||||
|
{
|
||||||
|
Cliente = row.Cliente,
|
||||||
|
TotalLinhas = row.TotalLinhas,
|
||||||
|
Ativos = row.Ativos,
|
||||||
|
Bloqueados = row.Bloqueados,
|
||||||
|
LinhasVivo = row.LinhasVivo,
|
||||||
|
TagsBase = baseTags,
|
||||||
|
TagsExtras = extras,
|
||||||
|
AdicionaisPorServico = serviceDetails
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FranquiaBuckets BuildFranquiaBuckets(IEnumerable<FranquiaProjection> rows)
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, FranquiaBucket>();
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var label = NormalizeFranquiaLabel(row.FranquiaVivo, row.PlanoContrato);
|
||||||
|
var key = label;
|
||||||
|
if (!map.TryGetValue(key, out var bucket))
|
||||||
|
{
|
||||||
|
bucket = new FranquiaBucket { Label = label, SortKey = BuildFranquiaSortKey(label) };
|
||||||
|
map[key] = bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket.Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ordered = map.Values
|
||||||
|
.OrderBy(x => x.SortKey)
|
||||||
|
.ThenBy(x => x.Label)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new FranquiaBuckets
|
||||||
|
{
|
||||||
|
Labels = ordered.Select(x => x.Label).ToList(),
|
||||||
|
Values = ordered.Select(x => x.Count).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeFranquiaLabel(decimal? value, string? planoContrato)
|
||||||
|
{
|
||||||
|
if (value.HasValue && value.Value > 0)
|
||||||
|
{
|
||||||
|
return NormalizeNumericFranquia(value.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed = ParseFranquiaFromPlano(planoContrato);
|
||||||
|
return string.IsNullOrWhiteSpace(parsed) ? "Sem Franquia" : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ParseFranquiaFromPlano(string? planoContrato)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(planoContrato)) return null;
|
||||||
|
|
||||||
|
var match = Regex.Match(planoContrato, @"(?<val>\d+(?:[.,]\d+)?)\s*(?<unit>GB|MB)", RegexOptions.IgnoreCase);
|
||||||
|
if (!match.Success) return null;
|
||||||
|
|
||||||
|
var valueRaw = match.Groups["val"].Value.Replace(",", ".");
|
||||||
|
if (!decimal.TryParse(valueRaw, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) return null;
|
||||||
|
|
||||||
|
var unit = match.Groups["unit"].Value.ToUpperInvariant();
|
||||||
|
if (unit == "GB")
|
||||||
|
{
|
||||||
|
return $"{TrimDecimal(value)}GB";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{Math.Round(value):0}MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeNumericFranquia(decimal value)
|
||||||
|
{
|
||||||
|
if (value < 1m)
|
||||||
|
{
|
||||||
|
var mb = Math.Round(value * 1000m);
|
||||||
|
return $"{mb:0}MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value >= 100m && value < 1024m)
|
||||||
|
{
|
||||||
|
return $"{Math.Round(value):0}MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value >= 1024m)
|
||||||
|
{
|
||||||
|
var gb = value / 1024m;
|
||||||
|
return $"{TrimDecimal(gb)}GB";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{TrimDecimal(value)}GB";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal BuildFranquiaSortKey(string label)
|
||||||
|
{
|
||||||
|
if (label.Equals("Sem Franquia", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return decimal.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = Regex.Match(label, @"(?<val>\d+(?:[.,]\d+)?)\s*(?<unit>GB|MB)", RegexOptions.IgnoreCase);
|
||||||
|
if (!match.Success) return decimal.MaxValue - 1;
|
||||||
|
|
||||||
|
var valueRaw = match.Groups["val"].Value.Replace(",", ".");
|
||||||
|
if (!decimal.TryParse(valueRaw, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
|
||||||
|
{
|
||||||
|
return decimal.MaxValue - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit = match.Groups["unit"].Value.ToUpperInvariant();
|
||||||
|
return unit == "MB" ? value / 1000m : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TrimDecimal(decimal value)
|
||||||
|
{
|
||||||
|
return value % 1 == 0 ? value.ToString("0", CultureInfo.InvariantCulture) : value.ToString("0.#", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatCurrency(decimal value)
|
||||||
|
{
|
||||||
|
return value.ToString("C", PtBr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FranquiaBucket
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
public int Count { get; set; }
|
||||||
|
public decimal SortKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FranquiaBuckets
|
||||||
|
{
|
||||||
|
public List<string> Labels { get; set; } = new();
|
||||||
|
public List<int> Values { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FranquiaProjection
|
||||||
|
{
|
||||||
|
public decimal? FranquiaVivo { get; set; }
|
||||||
|
public string? PlanoContrato { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TotalsProjection
|
||||||
|
{
|
||||||
|
public int TotalLinhas { get; set; }
|
||||||
|
public int TotalAtivas { get; set; }
|
||||||
|
public int TotalBloqueados { get; set; }
|
||||||
|
public int VivoLinhas { get; set; }
|
||||||
|
public decimal VivoBaseTotal { get; set; }
|
||||||
|
public decimal VivoAdicionaisTotal { get; set; }
|
||||||
|
public decimal VivoMinBase { get; set; }
|
||||||
|
public decimal VivoMaxBase { get; set; }
|
||||||
|
public int TravelCom { get; set; }
|
||||||
|
public int TravelSem { get; set; }
|
||||||
|
public decimal TravelTotal { get; set; }
|
||||||
|
public int PaidGestaoVozDados { get; set; }
|
||||||
|
public int PaidSkeelo { get; set; }
|
||||||
|
public int PaidNews { get; set; }
|
||||||
|
public int PaidTravel { get; set; }
|
||||||
|
public int PaidGestaoDispositivo { get; set; }
|
||||||
|
public int NotPaidGestaoVozDados { get; set; }
|
||||||
|
public int NotPaidSkeelo { get; set; }
|
||||||
|
public int NotPaidNews { get; set; }
|
||||||
|
public int NotPaidTravel { get; set; }
|
||||||
|
public int NotPaidGestaoDispositivo { get; set; }
|
||||||
|
public int TotalLinesWithAnyPaidAdditional { get; set; }
|
||||||
|
public int TotalLinesWithNoPaidAdditional { get; set; }
|
||||||
|
public decimal TotalGestaoVozDados { get; set; }
|
||||||
|
public decimal TotalSkeelo { get; set; }
|
||||||
|
public decimal TotalNews { get; set; }
|
||||||
|
public decimal TotalTravel { get; set; }
|
||||||
|
public decimal TotalGestaoDispositivo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ClientGroupProjection
|
||||||
|
{
|
||||||
|
public string Cliente { get; set; } = string.Empty;
|
||||||
|
public int TotalLinhas { get; set; }
|
||||||
|
public int Ativos { get; set; }
|
||||||
|
public int Bloqueados { get; set; }
|
||||||
|
public int LinhasVivo { get; set; }
|
||||||
|
public int TravelCom { get; set; }
|
||||||
|
public int TravelSem { get; set; }
|
||||||
|
public int PaidGestaoVozDados { get; set; }
|
||||||
|
public int PaidSkeelo { get; set; }
|
||||||
|
public int PaidNews { get; set; }
|
||||||
|
public int PaidTravel { get; set; }
|
||||||
|
public int PaidGestaoDispositivo { get; set; }
|
||||||
|
public int NotPaidGestaoVozDados { get; set; }
|
||||||
|
public int NotPaidSkeelo { get; set; }
|
||||||
|
public int NotPaidNews { get; set; }
|
||||||
|
public int NotPaidTravel { get; set; }
|
||||||
|
public int NotPaidGestaoDispositivo { get; set; }
|
||||||
|
public decimal TotalGestaoVozDados { get; set; }
|
||||||
|
public decimal TotalSkeelo { get; set; }
|
||||||
|
public decimal TotalNews { get; set; }
|
||||||
|
public decimal TotalTravel { get; set; }
|
||||||
|
public decimal TotalGestaoDispositivo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
using line_gestao_api.Models;
|
||||||
|
using line_gestao_api.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Tests
|
||||||
|
{
|
||||||
|
public class GeralDashboardInsightsServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task GetInsightsAsync_ReturnsZerosWhenNoLines()
|
||||||
|
{
|
||||||
|
var tenantId = Guid.NewGuid();
|
||||||
|
var provider = new TestTenantProvider(tenantId);
|
||||||
|
var db = BuildContext(provider);
|
||||||
|
|
||||||
|
var service = new GeralDashboardInsightsService(db);
|
||||||
|
var result = await service.GetInsightsAsync();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(0, result.Kpis.TotalLinhas);
|
||||||
|
Assert.Equal(0, result.Kpis.Vivo.QtdLinhas);
|
||||||
|
Assert.NotNull(result.Charts.LinhasPorFranquia);
|
||||||
|
Assert.NotNull(result.ClientGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetInsightsAsync_RespectsTenantIsolation()
|
||||||
|
{
|
||||||
|
var tenantA = Guid.NewGuid();
|
||||||
|
var tenantB = Guid.NewGuid();
|
||||||
|
var provider = new TestTenantProvider(tenantA);
|
||||||
|
var db = BuildContext(provider);
|
||||||
|
|
||||||
|
db.MobileLines.AddRange(
|
||||||
|
new MobileLine
|
||||||
|
{
|
||||||
|
TenantId = tenantA,
|
||||||
|
Cliente = "Cliente A",
|
||||||
|
Status = "Ativo",
|
||||||
|
ValorPlanoVivo = 100m,
|
||||||
|
GestaoVozDados = 10m
|
||||||
|
},
|
||||||
|
new MobileLine
|
||||||
|
{
|
||||||
|
TenantId = tenantB,
|
||||||
|
Cliente = "Cliente B",
|
||||||
|
Status = "Ativo",
|
||||||
|
ValorPlanoVivo = 200m,
|
||||||
|
GestaoVozDados = 20m
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var service = new GeralDashboardInsightsService(db);
|
||||||
|
var result = await service.GetInsightsAsync();
|
||||||
|
|
||||||
|
Assert.Equal(1, result.Kpis.TotalLinhas);
|
||||||
|
Assert.Equal(1, result.Kpis.Vivo.QtdLinhas);
|
||||||
|
Assert.Equal("Cliente A", result.ClientGroups.Single().Cliente);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetInsightsAsync_ReturnsNonNullCollections()
|
||||||
|
{
|
||||||
|
var tenantId = Guid.NewGuid();
|
||||||
|
var provider = new TestTenantProvider(tenantId);
|
||||||
|
var db = BuildContext(provider);
|
||||||
|
|
||||||
|
db.MobileLines.Add(new MobileLine
|
||||||
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
|
Cliente = "Cliente X",
|
||||||
|
Status = "Ativo",
|
||||||
|
ValorPlanoVivo = 120m,
|
||||||
|
Skeelo = 5m,
|
||||||
|
VivoTravelMundo = 0m
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var service = new GeralDashboardInsightsService(db);
|
||||||
|
var result = await service.GetInsightsAsync();
|
||||||
|
|
||||||
|
Assert.NotNull(result.Kpis.Adicionais.ServicesPaid);
|
||||||
|
Assert.NotNull(result.Kpis.Adicionais.ServicesNotPaid);
|
||||||
|
Assert.NotEmpty(result.ClientGroups);
|
||||||
|
Assert.NotEmpty(result.Charts.AdicionaisPagosPorServico.Labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AppDbContext BuildContext(TestTenantProvider provider)
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||||
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
return new AppDbContext(options, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestTenantProvider : ITenantProvider
|
||||||
|
{
|
||||||
|
public TestTenantProvider(Guid tenantId)
|
||||||
|
{
|
||||||
|
TenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? TenantId { get; private set; }
|
||||||
|
|
||||||
|
public void SetTenantId(Guid? tenantId)
|
||||||
|
{
|
||||||
|
TenantId = tenantId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\line-gestao-api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -16,6 +16,13 @@
|
||||||
<EmbeddedResource Remove="NovaPasta\**" />
|
<EmbeddedResource Remove="NovaPasta\**" />
|
||||||
<None Remove="NovaPasta\**" />
|
<None Remove="NovaPasta\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="line-gestao-api.Tests\**" />
|
||||||
|
<Content Remove="line-gestao-api.Tests\**" />
|
||||||
|
<EmbeddedResource Remove="line-gestao-api.Tests\**" />
|
||||||
|
<None Remove="line-gestao-api.Tests\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Controllers\WeatherForecastController.cs" />
|
<Compile Remove="Controllers\WeatherForecastController.cs" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue