line-gestao-api/Controllers/NotificationsController.cs

391 lines
15 KiB
C#

using ClosedXML.Excel;
using line_gestao_api.Data;
using line_gestao_api.Dtos;
using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Controllers;
[ApiController]
[Route("api/notifications")]
[Authorize]
public class NotificationsController : ControllerBase
{
private readonly AppDbContext _db;
public NotificationsController(AppDbContext db)
{
_db = db;
}
[HttpGet]
[HttpGet("/notifications")]
public async Task<ActionResult<List<NotificationDto>>> GetNotifications()
{
var items = await (
from notification in _db.Notifications.AsNoTracking()
join vigencia in _db.VigenciaLines.AsNoTracking()
on notification.VigenciaLineId equals vigencia.Id into vigencias
from vigencia in vigencias.DefaultIfEmpty()
let vigenciaByLinha = _db.VigenciaLines.AsNoTracking()
.Where(v => notification.Linha != null && v.Linha == notification.Linha)
.OrderByDescending(v => v.UpdatedAt)
.FirstOrDefault()
orderby notification.Data descending
select new NotificationDto
{
Id = notification.Id,
Tipo = notification.Tipo,
Titulo = notification.Titulo,
Mensagem = notification.Mensagem,
Data = notification.Data,
ReferenciaData = notification.ReferenciaData,
DiasParaVencer = notification.DiasParaVencer,
Lida = notification.Lida,
LidaEm = notification.LidaEm,
VigenciaLineId = notification.VigenciaLineId,
Cliente = notification.Cliente
?? (vigencia != null ? vigencia.Cliente : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Cliente : null),
Linha = notification.Linha
?? (vigencia != null ? vigencia.Linha : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Linha : null),
Conta = (vigencia != null ? vigencia.Conta : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Conta : null),
Usuario = notification.Usuario
?? (vigencia != null ? vigencia.Usuario : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Usuario : null),
PlanoContrato = (vigencia != null ? vigencia.PlanoContrato : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.PlanoContrato : null),
DtEfetivacaoServico = (vigencia != null ? vigencia.DtEfetivacaoServico : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.DtEfetivacaoServico : null),
DtTerminoFidelizacao = (vigencia != null ? vigencia.DtTerminoFidelizacao : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.DtTerminoFidelizacao : null)
})
.ToListAsync();
var todayUtc = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Utc);
foreach (var item in items)
{
ApplyEffectiveType(item, todayUtc);
}
return Ok(items);
}
[HttpPatch("{id:guid}/read")]
[HttpPatch("/notifications/{id:guid}/read")]
public async Task<IActionResult> MarkAsRead(Guid id)
{
var notification = await _db.Notifications
.FirstOrDefaultAsync(n => n.Id == id);
if (notification is null)
{
return NotFound();
}
if (!notification.Lida)
{
notification.Lida = true;
notification.LidaEm = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
return NoContent();
}
[HttpPatch("{id:guid}/unread")]
[HttpPatch("/notifications/{id:guid}/unread")]
public async Task<IActionResult> MarkAsUnread(Guid id)
{
var notification = await _db.Notifications
.FirstOrDefaultAsync(n => n.Id == id);
if (notification is null)
{
return NotFound();
}
if (notification.Lida)
{
notification.Lida = false;
notification.LidaEm = null;
await _db.SaveChangesAsync();
}
return NoContent();
}
[HttpPatch("read-all")]
[HttpPatch("/notifications/read-all")]
public async Task<IActionResult> MarkAllAsRead(
[FromQuery] string? filter,
[FromBody] NotificationSelectionRequest? request)
{
var utcNow = DateTime.UtcNow;
var query = ApplySelectionAndFilter(_db.Notifications, filter, request?.NotificationIds)
.Where(n => !n.Lida);
await query.ExecuteUpdateAsync(updates => updates
.SetProperty(n => n.Lida, true)
.SetProperty(n => n.LidaEm, utcNow));
return NoContent();
}
[HttpPatch("unread-all")]
[HttpPatch("/notifications/unread-all")]
public async Task<IActionResult> MarkAllAsUnread(
[FromQuery] string? filter,
[FromBody] NotificationSelectionRequest? request)
{
var query = ApplySelectionAndFilter(_db.Notifications, filter, request?.NotificationIds)
.Where(n => n.Lida);
await query.ExecuteUpdateAsync(updates => updates
.SetProperty(n => n.Lida, false)
.SetProperty(n => n.LidaEm, (DateTime?)null));
return NoContent();
}
[HttpGet("export")]
[HttpGet("/notifications/export")]
public async Task<IActionResult> ExportNotifications([FromQuery] string? filter)
{
var query = ApplySelectionAndFilter(_db.Notifications.AsNoTracking(), filter, null);
return await ExportNotificationsAsync(query, filter);
}
[HttpPost("export")]
[HttpPost("/notifications/export")]
public async Task<IActionResult> ExportNotifications(
[FromQuery] string? filter,
[FromBody] NotificationSelectionRequest? request)
{
var query = ApplySelectionAndFilter(_db.Notifications.AsNoTracking(), filter, request?.NotificationIds);
return await ExportNotificationsAsync(query, filter);
}
private async Task<IActionResult> ExportNotificationsAsync(IQueryable<Notification> query, string? filter)
{
var rows = await (
from notification in query
join vigencia in _db.VigenciaLines.AsNoTracking()
on notification.VigenciaLineId equals vigencia.Id into vigencias
from vigencia in vigencias.DefaultIfEmpty()
let vigenciaByLinha = _db.VigenciaLines.AsNoTracking()
.Where(v => notification.Linha != null && v.Linha == notification.Linha)
.OrderByDescending(v => v.UpdatedAt)
.FirstOrDefault()
orderby notification.ReferenciaData descending, notification.Data descending
select new NotificationExportRow(
(vigencia != null ? vigencia.Conta : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Conta : null),
notification.Linha
?? (vigencia != null ? vigencia.Linha : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Linha : null),
notification.Cliente
?? (vigencia != null ? vigencia.Cliente : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Cliente : null),
notification.Usuario
?? (vigencia != null ? vigencia.Usuario : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.Usuario : null),
(vigencia != null ? vigencia.PlanoContrato : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.PlanoContrato : null),
(vigencia != null ? vigencia.DtEfetivacaoServico : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.DtEfetivacaoServico : null),
notification.ReferenciaData
?? (vigencia != null ? vigencia.DtTerminoFidelizacao : null)
?? (vigenciaByLinha != null ? vigenciaByLinha.DtTerminoFidelizacao : null),
notification.Tipo))
.ToListAsync();
var todayUtc = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Utc);
rows = rows
.Select(r => r with { Tipo = ResolveEffectiveType(r.Tipo, r.DataReferencia, todayUtc) })
.ToList();
using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Notificacoes");
var normalizedFilter = NormalizeFilter(filter);
var headers = new[]
{
"CONTA",
"LINHA",
"Cliente",
"Usuário",
"PLANO CONTRATO",
"DATA INICIO",
normalizedFilter is "vencidas" or "vencido" ? "DATA VENCIMENTO" : "DATA A VENCER",
"Status"
};
for (var i = 0; i < headers.Length; i++)
{
worksheet.Cell(1, i + 1).Value = headers[i];
}
var headerBackground = XLColor.FromHtml("#E8EEFC");
var headerText = XLColor.FromHtml("#1E3A8A");
var borderColor = XLColor.FromHtml("#DBE2EF");
var zebraBackground = XLColor.FromHtml("#F8FAFC");
var bodyText = XLColor.FromHtml("#0F172A");
var headerRange = worksheet.Range(1, 1, 1, headers.Length);
headerRange.Style.Font.Bold = true;
headerRange.Style.Font.FontName = "Segoe UI";
headerRange.Style.Font.FontSize = 11;
headerRange.Style.Font.FontColor = headerText;
headerRange.Style.Fill.BackgroundColor = headerBackground;
headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
for (var i = 0; i < rows.Count; i++)
{
var row = rows[i];
var rowIndex = i + 2;
worksheet.Cell(rowIndex, 1).Value = row.Conta ?? string.Empty;
worksheet.Cell(rowIndex, 2).Value = row.Linha ?? string.Empty;
worksheet.Cell(rowIndex, 3).Value = (row.Cliente ?? string.Empty).ToUpperInvariant();
worksheet.Cell(rowIndex, 4).Value = (row.Usuario ?? string.Empty).ToUpperInvariant();
worksheet.Cell(rowIndex, 5).Value = row.PlanoContrato ?? string.Empty;
worksheet.Cell(rowIndex, 6).Value = row.DataInicio;
worksheet.Cell(rowIndex, 7).Value = row.DataReferencia;
worksheet.Cell(rowIndex, 8).Value = row.Tipo.ToUpperInvariant();
var rowRange = worksheet.Range(rowIndex, 1, rowIndex, headers.Length);
rowRange.Style.Fill.BackgroundColor = i % 2 == 0 ? XLColor.White : zebraBackground;
rowRange.Style.Font.FontName = "Segoe UI";
rowRange.Style.Font.FontSize = 10.5;
rowRange.Style.Font.FontColor = bodyText;
rowRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
worksheet.Cell(rowIndex, 8).Style.Font.Bold = true;
worksheet.Cell(rowIndex, 8).Style.Font.FontColor = row.Tipo.Equals("Vencido", StringComparison.OrdinalIgnoreCase)
? XLColor.FromHtml("#B91C1C")
: XLColor.FromHtml("#047857");
}
var tableRange = worksheet.Range(1, 1, Math.Max(2, rows.Count + 1), headers.Length);
tableRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
tableRange.Style.Border.OutsideBorderColor = borderColor;
tableRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
tableRange.Style.Border.InsideBorderColor = borderColor;
tableRange.SetAutoFilter();
worksheet.Column(6).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
worksheet.Column(7).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
worksheet.Column(8).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
worksheet.Column(6).Style.DateFormat.Format = "dd/MM/yyyy";
worksheet.Column(7).Style.DateFormat.Format = "dd/MM/yyyy";
worksheet.SheetView.FreezeRows(1);
worksheet.Columns(1, headers.Length).AdjustToContents();
var minWidths = new[] { 14d, 14d, 22d, 20d, 20d, 14d, 14d, 12d };
for (var i = 0; i < minWidths.Length; i++)
{
var col = worksheet.Column(i + 1);
if (col.Width < minWidths[i])
{
col.Width = minWidths[i];
}
}
using var stream = new MemoryStream();
workbook.SaveAs(stream);
stream.Position = 0;
var fileName = $"notificacoes-{DateTime.UtcNow:yyyyMMddHHmmss}.xlsx";
return File(
stream.ToArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileName);
}
private static IQueryable<Notification> ApplySelectionAndFilter(
IQueryable<Notification> query,
string? filter,
IReadOnlyCollection<Guid>? notificationIds)
{
query = ApplyFilter(query, filter);
if (notificationIds is { Count: > 0 })
{
query = query.Where(n => notificationIds.Contains(n.Id));
}
return query;
}
private static IQueryable<Notification> ApplyFilter(IQueryable<Notification> query, string? filter)
{
var normalized = NormalizeFilter(filter);
return normalized switch
{
"a-vencer" or "avencer" => query.Where(n => n.Tipo == "AVencer"),
"vencidas" or "vencido" => query.Where(n => n.Tipo == "Vencido"),
_ => query
};
}
private static string? NormalizeFilter(string? filter)
{
return filter?.Trim().ToLowerInvariant();
}
private static void ApplyEffectiveType(NotificationDto item, DateTime todayUtc)
{
item.Tipo = ResolveEffectiveType(item.Tipo, item.DtTerminoFidelizacao ?? item.ReferenciaData, todayUtc);
var effectiveDate = item.DtTerminoFidelizacao ?? item.ReferenciaData;
if (!effectiveDate.HasValue)
{
return;
}
var endDateUtc = DateTime.SpecifyKind(effectiveDate.Value.Date, DateTimeKind.Utc);
var daysUntil = (endDateUtc - todayUtc).Days;
item.DiasParaVencer = daysUntil < 0 ? 0 : daysUntil;
}
private static string ResolveEffectiveType(string currentType, DateTime? referenceDate, DateTime todayUtc)
{
if (!referenceDate.HasValue)
{
return currentType;
}
var isKnownType = currentType.Equals("AVencer", StringComparison.OrdinalIgnoreCase)
|| currentType.Equals("Vencido", StringComparison.OrdinalIgnoreCase);
if (!isKnownType)
{
return currentType;
}
var endDateUtc = DateTime.SpecifyKind(referenceDate.Value.Date, DateTimeKind.Utc);
return endDateUtc < todayUtc ? "Vencido" : "AVencer";
}
private sealed record NotificationExportRow(
string? Conta,
string? Linha,
string? Cliente,
string? Usuario,
string? PlanoContrato,
DateTime? DataInicio,
DateTime? DataReferencia,
string Tipo);
public sealed class NotificationSelectionRequest
{
public List<Guid>? NotificationIds { get; set; }
}
}