328 lines
13 KiB
C#
328 lines
13 KiB
C#
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/relatorios")]
|
|
[Authorize]
|
|
public class RelatoriosController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
|
|
public RelatoriosController(AppDbContext db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
[HttpGet("dashboard")]
|
|
public async Task<ActionResult<RelatoriosDashboardDto>> GetDashboard()
|
|
{
|
|
var todayUtcStart = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Utc);
|
|
var tomorrowUtcStart = todayUtcStart.AddDays(1);
|
|
var last30UtcStart = todayUtcStart.AddDays(-30);
|
|
var limit30ExclusiveUtcStart = todayUtcStart.AddDays(31);
|
|
|
|
var minUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
|
|
// =========================
|
|
// GERAL (MobileLines)
|
|
// =========================
|
|
var qLines = _db.MobileLines.AsNoTracking();
|
|
|
|
var totalLinhas = await qLines.CountAsync();
|
|
|
|
var clientesUnicos = await qLines
|
|
.Where(x => x.Cliente != null && x.Cliente != "")
|
|
.Select(x => x.Cliente!)
|
|
.Distinct()
|
|
.CountAsync();
|
|
|
|
var ativos = await qLines.CountAsync(x =>
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%ativo%"));
|
|
|
|
var bloqueadosPerdaRoubo = await qLines.CountAsync(x =>
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%perda%") ||
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%roubo%"));
|
|
|
|
var bloqueados120Dias = await qLines.CountAsync(x =>
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%bloque%") &&
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%120%") &&
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%dia%"));
|
|
|
|
var bloqueadosOutros = await qLines.CountAsync(x =>
|
|
EF.Functions.ILike((x.Status ?? "").Trim(), "%bloque%") &&
|
|
!(EF.Functions.ILike((x.Status ?? "").Trim(), "%120%") && EF.Functions.ILike((x.Status ?? "").Trim(), "%dia%")) &&
|
|
!(EF.Functions.ILike((x.Status ?? "").Trim(), "%perda%") || EF.Functions.ILike((x.Status ?? "").Trim(), "%roubo%"))
|
|
);
|
|
|
|
var bloqueados = bloqueadosPerdaRoubo + bloqueados120Dias + bloqueadosOutros;
|
|
|
|
var reservas = await qLines.CountAsync(x =>
|
|
EF.Functions.ILike((x.Cliente ?? "").Trim(), "%RESERVA%") ||
|
|
EF.Functions.ILike((x.Usuario ?? "").Trim(), "%RESERVA%") ||
|
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%RESERVA%"));
|
|
|
|
var topClientes = await qLines
|
|
.Where(x => x.Cliente != null && x.Cliente != "")
|
|
.GroupBy(x => x.Cliente!)
|
|
.Select(g => new TopClienteDto
|
|
{
|
|
Cliente = g.Key,
|
|
Linhas = g.Count()
|
|
})
|
|
.OrderByDescending(x => x.Linhas)
|
|
.ThenBy(x => x.Cliente)
|
|
.Take(10)
|
|
.ToListAsync();
|
|
|
|
// =========================
|
|
// MUREG
|
|
// =========================
|
|
var qMureg = _db.MuregLines.AsNoTracking().Include(x => x.MobileLine);
|
|
|
|
var totalMuregs = await qMureg.CountAsync();
|
|
|
|
var muregsUltimos30 = await qMureg.CountAsync(x =>
|
|
x.DataDaMureg != null &&
|
|
x.DataDaMureg.Value >= last30UtcStart &&
|
|
x.DataDaMureg.Value < tomorrowUtcStart);
|
|
|
|
var muregsRecentes = await qMureg
|
|
.OrderByDescending(x => x.DataDaMureg ?? minUtc)
|
|
.ThenByDescending(x => x.Item)
|
|
.Take(10)
|
|
.Select(x => new MuregRecenteDto
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
LinhaAntiga = x.LinhaAntiga,
|
|
LinhaNova = x.LinhaNova,
|
|
ICCID = x.ICCID,
|
|
DataDaMureg = x.DataDaMureg,
|
|
Cliente = x.MobileLine != null ? x.MobileLine.Cliente : null,
|
|
MobileLineId = x.MobileLineId
|
|
})
|
|
.ToListAsync();
|
|
|
|
var serieMureg12 = await BuildSerieUltimos12Meses_Mureg(todayUtcStart);
|
|
|
|
// =========================
|
|
// TROCA DE NÚMERO
|
|
// =========================
|
|
var qTroca = _db.TrocaNumeroLines.AsNoTracking();
|
|
|
|
var totalTrocas = await qTroca.CountAsync();
|
|
|
|
var trocasUltimos30 = await qTroca.CountAsync(x =>
|
|
x.DataTroca != null &&
|
|
x.DataTroca.Value >= last30UtcStart &&
|
|
x.DataTroca.Value < tomorrowUtcStart);
|
|
|
|
var trocasRecentes = await qTroca
|
|
.OrderByDescending(x => x.DataTroca ?? minUtc)
|
|
.ThenByDescending(x => x.Item)
|
|
.Take(10)
|
|
.Select(x => new TrocaRecenteDto
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
LinhaAntiga = x.LinhaAntiga,
|
|
LinhaNova = x.LinhaNova,
|
|
ICCID = x.ICCID,
|
|
DataTroca = x.DataTroca,
|
|
Motivo = x.Motivo
|
|
})
|
|
.ToListAsync();
|
|
|
|
var serieTroca12 = await BuildSerieUltimos12Meses_Troca(todayUtcStart);
|
|
|
|
// =========================
|
|
// VIGÊNCIA
|
|
// =========================
|
|
var qVig = _db.VigenciaLines.AsNoTracking();
|
|
|
|
var totalVig = await qVig.CountAsync();
|
|
|
|
var vigVencidos = await qVig.CountAsync(x =>
|
|
x.DtTerminoFidelizacao != null && x.DtTerminoFidelizacao.Value < todayUtcStart);
|
|
|
|
var vigAVencer30 = await qVig.CountAsync(x =>
|
|
x.DtTerminoFidelizacao != null &&
|
|
x.DtTerminoFidelizacao.Value >= todayUtcStart &&
|
|
x.DtTerminoFidelizacao.Value < limit30ExclusiveUtcStart);
|
|
|
|
// ✅ NOVO: série próximos 12 meses (mês/ano)
|
|
var serieVigProx12 = await BuildSerieProximos12Meses_VigenciaEncerramentos(todayUtcStart);
|
|
|
|
// ✅ NOVO: buckets de supervisão
|
|
var vigBuckets = await BuildVigenciaBuckets(todayUtcStart);
|
|
|
|
// =========================
|
|
// USER DATA
|
|
// =========================
|
|
var qUserData = _db.UserDatas.AsNoTracking();
|
|
|
|
var userDataRegistros = await qUserData.CountAsync();
|
|
var userDataComCpf = await qUserData.CountAsync(x => x.Cpf != null && x.Cpf != "");
|
|
var userDataComEmail = await qUserData.CountAsync(x => x.Email != null && x.Email != "");
|
|
|
|
// =========================
|
|
// RESPOSTA
|
|
// =========================
|
|
var dto = new RelatoriosDashboardDto
|
|
{
|
|
Kpis = new DashboardKpisDto
|
|
{
|
|
TotalLinhas = totalLinhas,
|
|
ClientesUnicos = clientesUnicos,
|
|
Ativos = ativos,
|
|
|
|
Bloqueados = bloqueados,
|
|
BloqueadosPerdaRoubo = bloqueadosPerdaRoubo,
|
|
Bloqueados120Dias = bloqueados120Dias,
|
|
BloqueadosOutros = bloqueadosOutros,
|
|
|
|
Reservas = reservas,
|
|
|
|
TotalMuregs = totalMuregs,
|
|
MuregsUltimos30Dias = muregsUltimos30,
|
|
|
|
TotalVigenciaLinhas = totalVig,
|
|
VigenciaVencidos = vigVencidos,
|
|
VigenciaAVencer30 = vigAVencer30,
|
|
|
|
TotalTrocas = totalTrocas,
|
|
TrocasUltimos30Dias = trocasUltimos30,
|
|
|
|
UserDataRegistros = userDataRegistros,
|
|
UserDataComCpf = userDataComCpf,
|
|
UserDataComEmail = userDataComEmail
|
|
},
|
|
TopClientes = topClientes,
|
|
SerieMuregUltimos12Meses = serieMureg12,
|
|
SerieTrocaUltimos12Meses = serieTroca12,
|
|
MuregsRecentes = muregsRecentes,
|
|
TrocasRecentes = trocasRecentes,
|
|
|
|
SerieVigenciaEncerramentosProx12Meses = serieVigProx12,
|
|
VigenciaBuckets = vigBuckets
|
|
};
|
|
|
|
return Ok(dto);
|
|
}
|
|
|
|
// =========================
|
|
// Helpers
|
|
// =========================
|
|
private async Task<List<SerieMesDto>> BuildSerieUltimos12Meses_Mureg(DateTime todayUtcDate)
|
|
{
|
|
var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc)
|
|
.AddMonths(-11);
|
|
|
|
var raw = await _db.MuregLines.AsNoTracking()
|
|
.Where(x => x.DataDaMureg != null && x.DataDaMureg.Value >= start)
|
|
.GroupBy(x => new { x.DataDaMureg!.Value.Year, x.DataDaMureg!.Value.Month })
|
|
.Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() })
|
|
.ToListAsync();
|
|
|
|
return Fill12Months(start, raw.Select(r => (r.Year, r.Month, r.Total)));
|
|
}
|
|
|
|
private async Task<List<SerieMesDto>> BuildSerieUltimos12Meses_Troca(DateTime todayUtcDate)
|
|
{
|
|
var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc)
|
|
.AddMonths(-11);
|
|
|
|
var raw = await _db.TrocaNumeroLines.AsNoTracking()
|
|
.Where(x => x.DataTroca != null && x.DataTroca.Value >= start)
|
|
.GroupBy(x => new { x.DataTroca!.Value.Year, x.DataTroca!.Value.Month })
|
|
.Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() })
|
|
.ToListAsync();
|
|
|
|
return Fill12Months(start, raw.Select(r => (r.Year, r.Month, r.Total)));
|
|
}
|
|
|
|
// ✅ série próximos 12 meses (vigência encerrando)
|
|
private async Task<List<SerieMesDto>> BuildSerieProximos12Meses_VigenciaEncerramentos(DateTime todayUtcDate)
|
|
{
|
|
var start = new DateTime(todayUtcDate.Year, todayUtcDate.Month, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
var end = start.AddMonths(12);
|
|
|
|
var raw = await _db.VigenciaLines.AsNoTracking()
|
|
.Where(x => x.DtTerminoFidelizacao != null &&
|
|
x.DtTerminoFidelizacao.Value >= start &&
|
|
x.DtTerminoFidelizacao.Value < end)
|
|
.GroupBy(x => new { x.DtTerminoFidelizacao!.Value.Year, x.DtTerminoFidelizacao!.Value.Month })
|
|
.Select(g => new { g.Key.Year, g.Key.Month, Total = g.Count() })
|
|
.ToListAsync();
|
|
|
|
return Fill12MonthsForward(start, raw.Select(r => (r.Year, r.Month, r.Total)));
|
|
}
|
|
|
|
// ✅ buckets de supervisão de vigência
|
|
private async Task<VigenciaBucketsDto> BuildVigenciaBuckets(DateTime todayUtcDate)
|
|
{
|
|
// Importante: DtTerminoFidelizacao pode ser null
|
|
var rows = await _db.VigenciaLines.AsNoTracking()
|
|
.Where(x => x.DtTerminoFidelizacao != null)
|
|
.Select(x => x.DtTerminoFidelizacao!.Value)
|
|
.ToListAsync();
|
|
|
|
int vencidos = 0, a0_30 = 0, a31_60 = 0, a61_90 = 0, acima90 = 0;
|
|
|
|
foreach (var dt in rows)
|
|
{
|
|
var days = (dt.Date - todayUtcDate).Days;
|
|
|
|
if (days < 0) vencidos++;
|
|
else if (days <= 30) a0_30++;
|
|
else if (days <= 60) a31_60++;
|
|
else if (days <= 90) a61_90++;
|
|
else acima90++;
|
|
}
|
|
|
|
return new VigenciaBucketsDto
|
|
{
|
|
Vencidos = vencidos,
|
|
AVencer0a30 = a0_30,
|
|
AVencer31a60 = a31_60,
|
|
AVencer61a90 = a61_90,
|
|
Acima90 = acima90
|
|
};
|
|
}
|
|
|
|
private static List<SerieMesDto> Fill12Months(DateTime startMonth, IEnumerable<(int year, int month, int total)> raw)
|
|
{
|
|
var dict = raw.ToDictionary(x => $"{x.year:D4}-{x.month:D2}", x => x.total);
|
|
|
|
var list = new List<SerieMesDto>(12);
|
|
for (int i = 0; i < 12; i++)
|
|
{
|
|
var dt = startMonth.AddMonths(i);
|
|
var key = $"{dt.Year:D4}-{dt.Month:D2}";
|
|
list.Add(new SerieMesDto { Mes = key, Total = dict.TryGetValue(key, out var v) ? v : 0 });
|
|
}
|
|
return list;
|
|
}
|
|
|
|
// ✅ para frente (começa no mês atual e vai +11)
|
|
private static List<SerieMesDto> Fill12MonthsForward(DateTime startMonth, IEnumerable<(int year, int month, int total)> raw)
|
|
{
|
|
var dict = raw.ToDictionary(x => $"{x.year:D4}-{x.month:D2}", x => x.total);
|
|
|
|
var list = new List<SerieMesDto>(12);
|
|
for (int i = 0; i < 12; i++)
|
|
{
|
|
var dt = startMonth.AddMonths(i);
|
|
var key = $"{dt.Year:D4}-{dt.Month:D2}";
|
|
list.Add(new SerieMesDto { Mes = key, Total = dict.TryGetValue(key, out var v) ? v : 0 });
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
}
|