using System.Globalization; using System.Security.Claims; 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/solicitacoes-linhas")] [Authorize] public class SolicitacoesLinhasController : ControllerBase { private const string TipoAlteracaoFranquia = "ALTERACAO_FRANQUIA"; private const string TipoBloqueio = "BLOQUEIO"; private readonly AppDbContext _db; public SolicitacoesLinhasController(AppDbContext db) { _db = db; } [HttpPost] [Authorize(Roles = "sysadmin,gestor,cliente")] public async Task> Create([FromBody] CreateSolicitacaoLinhaRequestDto req) { if (req.LineId == Guid.Empty) { return BadRequest(new { message = "Linha inválida para solicitação." }); } var line = await _db.MobileLines .AsNoTracking() .FirstOrDefaultAsync(x => x.Id == req.LineId); if (line == null) { return NotFound(new { message = "Linha não encontrada." }); } var tipoSolicitacao = NormalizeTipoSolicitacao(req.TipoSolicitacao); if (tipoSolicitacao == null) { return BadRequest(new { message = "Tipo de solicitação inválido. Use 'alteracao-franquia' ou 'bloqueio'." }); } decimal? franquiaLineNova = null; if (tipoSolicitacao == TipoAlteracaoFranquia) { if (!req.FranquiaLineNova.HasValue) { return BadRequest(new { message = "Informe a nova franquia para solicitar alteração." }); } franquiaLineNova = decimal.Round(req.FranquiaLineNova.Value, 2, MidpointRounding.AwayFromZero); if (franquiaLineNova < 0) { return BadRequest(new { message = "A nova franquia não pode ser negativa." }); } } var solicitanteNome = ResolveSolicitanteNome(); var usuarioLinha = NormalizeOptionalText(line.Usuario) ?? solicitanteNome; var linha = NormalizeOptionalText(line.Linha) ?? "-"; var mensagem = tipoSolicitacao == TipoAlteracaoFranquia ? $"O Usuário \"{usuarioLinha}\" solicitou alteração da linha \"{linha}\" \"{FormatFranquia(line.FranquiaLine)}\" -> \"{FormatFranquia(franquiaLineNova)}\"" : $"O Usuário \"{usuarioLinha}\" solicitou bloqueio da linha \"{linha}\""; var solicitacao = new SolicitacaoLinha { TenantId = line.TenantId, MobileLineId = line.Id, Linha = NormalizeOptionalText(line.Linha), UsuarioLinha = NormalizeOptionalText(line.Usuario), TipoSolicitacao = tipoSolicitacao, FranquiaLineAtual = line.FranquiaLine, FranquiaLineNova = franquiaLineNova, SolicitanteUserId = ResolveSolicitanteUserId(), SolicitanteNome = solicitanteNome, Mensagem = mensagem, Status = "PENDENTE", CreatedAt = DateTime.UtcNow }; _db.SolicitacaoLinhas.Add(solicitacao); await _db.SaveChangesAsync(); var tenantNome = await _db.Tenants .AsNoTracking() .Where(t => t.Id == solicitacao.TenantId) .Select(t => t.NomeOficial) .FirstOrDefaultAsync(); return Ok(ToDto(solicitacao, tenantNome)); } [HttpGet] [Authorize(Roles = "sysadmin,gestor")] public async Task>> List( [FromQuery] string? search, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { page = page < 1 ? 1 : page; pageSize = pageSize < 1 ? 20 : Math.Min(pageSize, 200); var query = from solicitacao in _db.SolicitacaoLinhas.AsNoTracking() join tenant in _db.Tenants.AsNoTracking() on solicitacao.TenantId equals tenant.Id into tenantJoin from tenant in tenantJoin.DefaultIfEmpty() select new { Solicitacao = solicitacao, TenantNome = tenant != null ? tenant.NomeOficial : null }; if (!string.IsNullOrWhiteSpace(search)) { var term = search.Trim(); query = query.Where(x => EF.Functions.ILike(x.Solicitacao.Linha ?? "", $"%{term}%") || EF.Functions.ILike(x.Solicitacao.UsuarioLinha ?? "", $"%{term}%") || EF.Functions.ILike(x.Solicitacao.SolicitanteNome ?? "", $"%{term}%") || EF.Functions.ILike(x.Solicitacao.Mensagem ?? "", $"%{term}%")); } var total = await query.CountAsync(); var items = await query .OrderByDescending(x => x.Solicitacao.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(x => new SolicitacaoLinhaListDto { Id = x.Solicitacao.Id, TenantId = x.Solicitacao.TenantId, TenantNome = x.TenantNome, MobileLineId = x.Solicitacao.MobileLineId, Linha = x.Solicitacao.Linha, UsuarioLinha = x.Solicitacao.UsuarioLinha, TipoSolicitacao = x.Solicitacao.TipoSolicitacao, FranquiaLineAtual = x.Solicitacao.FranquiaLineAtual, FranquiaLineNova = x.Solicitacao.FranquiaLineNova, SolicitanteNome = x.Solicitacao.SolicitanteNome, Mensagem = x.Solicitacao.Mensagem, Status = x.Solicitacao.Status, CreatedAt = x.Solicitacao.CreatedAt }) .ToListAsync(); return Ok(new PagedResult { Page = page, PageSize = pageSize, Total = total, Items = items }); } private static string? NormalizeTipoSolicitacao(string? tipoSolicitacao) { var value = (tipoSolicitacao ?? string.Empty).Trim().ToLowerInvariant(); return value switch { "alteracao-franquia" => TipoAlteracaoFranquia, "alteracao_franquia" => TipoAlteracaoFranquia, "alteracaofranquia" => TipoAlteracaoFranquia, "franquia" => TipoAlteracaoFranquia, "bloqueio" => TipoBloqueio, "solicitar-bloqueio" => TipoBloqueio, _ => null }; } private string ResolveSolicitanteNome() { var fromClaim = User.FindFirstValue("name"); if (!string.IsNullOrWhiteSpace(fromClaim)) { return fromClaim.Trim(); } var fromIdentity = User.Identity?.Name; if (!string.IsNullOrWhiteSpace(fromIdentity)) { return fromIdentity.Trim(); } var fromEmail = User.FindFirstValue(ClaimTypes.Email) ?? User.FindFirstValue("email"); if (!string.IsNullOrWhiteSpace(fromEmail)) { return fromEmail.Trim(); } return "Usuário"; } private Guid? ResolveSolicitanteUserId() { var raw = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"); return Guid.TryParse(raw, out var parsed) ? parsed : null; } private static string? NormalizeOptionalText(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } private static string FormatFranquia(decimal? value) { return value.HasValue ? value.Value.ToString("0.##", CultureInfo.GetCultureInfo("pt-BR")) : "-"; } private static SolicitacaoLinhaListDto ToDto(SolicitacaoLinha solicitacao, string? tenantNome) { return new SolicitacaoLinhaListDto { Id = solicitacao.Id, TenantId = solicitacao.TenantId, TenantNome = tenantNome, MobileLineId = solicitacao.MobileLineId, Linha = solicitacao.Linha, UsuarioLinha = solicitacao.UsuarioLinha, TipoSolicitacao = solicitacao.TipoSolicitacao, FranquiaLineAtual = solicitacao.FranquiaLineAtual, FranquiaLineNova = solicitacao.FranquiaLineNova, SolicitanteNome = solicitacao.SolicitanteNome, Mensagem = solicitacao.Mensagem, Status = solicitacao.Status, CreatedAt = solicitacao.CreatedAt }; } }