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; using System.Globalization; using System.Text; namespace line_gestao_api.Controllers { [ApiController] [Route("api/controle-recebidos")] [Authorize] public class ControleRecebidosController : ControllerBase { private readonly AppDbContext _db; public ControleRecebidosController(AppDbContext db) { _db = db; } [HttpGet] public async Task>> GetAll( [FromQuery] int? ano, [FromQuery] bool? isResumo, [FromQuery] string? search, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? sortBy = "item", [FromQuery] string? sortDir = "asc") { page = page < 1 ? 1 : page; pageSize = pageSize < 1 ? 20 : pageSize; var q = _db.ControleRecebidoLines.AsNoTracking(); if (ano.HasValue) q = q.Where(x => x.Ano == ano.Value); if (isResumo.HasValue) q = q.Where(x => x.IsResumo == isResumo.Value); if (!string.IsNullOrWhiteSpace(search)) { var s = search.Trim(); var hasDateSearch = TryParseSearchDateUtcStart(s, out var searchDateStartUtc); var searchDateEndUtc = hasDateSearch ? searchDateStartUtc.AddDays(1) : DateTime.MinValue; var hasDecimalSearch = TryParseSearchDecimal(s, out var searchDecimal); var hasIntSearch = int.TryParse(OnlyDigits(s), out var searchInt); var searchResumo = s.Equals("resumo", StringComparison.OrdinalIgnoreCase); var searchDetalhado = s.Equals("detalhado", StringComparison.OrdinalIgnoreCase) || s.Equals("detalhe", StringComparison.OrdinalIgnoreCase); q = q.Where(x => EF.Functions.ILike(x.NotaFiscal ?? "", $"%{s}%") || EF.Functions.ILike(x.Chip ?? "", $"%{s}%") || EF.Functions.ILike(x.Serial ?? "", $"%{s}%") || EF.Functions.ILike(x.ConteudoDaNf ?? "", $"%{s}%") || EF.Functions.ILike(x.NumeroDaLinha ?? "", $"%{s}%") || EF.Functions.ILike(x.Item.ToString(), $"%{s}%") || EF.Functions.ILike(x.Ano.ToString(), $"%{s}%") || (hasIntSearch && ((x.Quantidade ?? 0) == searchInt)) || (hasDecimalSearch && (((x.ValorUnit ?? 0m) == searchDecimal) || ((x.ValorDaNf ?? 0m) == searchDecimal))) || (searchResumo && x.IsResumo) || (searchDetalhado && !x.IsResumo) || (hasDateSearch && ((x.DataDaNf != null && x.DataDaNf.Value >= searchDateStartUtc && x.DataDaNf.Value < searchDateEndUtc) || (x.DataDoRecebimento != null && x.DataDoRecebimento.Value >= searchDateStartUtc && x.DataDoRecebimento.Value < searchDateEndUtc)))); } var total = await q.CountAsync(); var sb = (sortBy ?? "item").Trim().ToLowerInvariant(); var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase); q = sb switch { "ano" => desc ? q.OrderByDescending(x => x.Ano).ThenBy(x => x.Item) : q.OrderBy(x => x.Ano).ThenBy(x => x.Item), "notafiscal" => desc ? q.OrderByDescending(x => x.NotaFiscal ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.NotaFiscal ?? "").ThenBy(x => x.Item), "datadanf" => desc ? q.OrderByDescending(x => x.DataDaNf).ThenBy(x => x.Item) : q.OrderBy(x => x.DataDaNf).ThenBy(x => x.Item), "datadorecebimento" => desc ? q.OrderByDescending(x => x.DataDoRecebimento).ThenBy(x => x.Item) : q.OrderBy(x => x.DataDoRecebimento).ThenBy(x => x.Item), "valordanf" => desc ? q.OrderByDescending(x => x.ValorDaNf ?? 0).ThenBy(x => x.Item) : q.OrderBy(x => x.ValorDaNf ?? 0).ThenBy(x => x.Item), "quantidade" => desc ? q.OrderByDescending(x => x.Quantidade ?? 0).ThenBy(x => x.Item) : q.OrderBy(x => x.Quantidade ?? 0).ThenBy(x => x.Item), _ => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item) }; var items = await q .Skip((page - 1) * pageSize) .Take(pageSize) .Select(x => new ControleRecebidoListDto { Id = x.Id, Ano = x.Ano, Item = x.Item, NotaFiscal = x.NotaFiscal, Chip = x.Chip, Serial = x.Serial, ConteudoDaNf = x.ConteudoDaNf, NumeroDaLinha = x.NumeroDaLinha, ValorUnit = x.ValorUnit, ValorDaNf = x.ValorDaNf, DataDaNf = x.DataDaNf, DataDoRecebimento = x.DataDoRecebimento, Quantidade = x.Quantidade, IsResumo = x.IsResumo }) .ToListAsync(); return Ok(new PagedResult { Page = page, PageSize = pageSize, Total = total, Items = items }); } [HttpGet("{id:guid}")] public async Task> GetById(Guid id) { var x = await _db.ControleRecebidoLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); return Ok(ToDetailDto(x)); } [HttpPost] public async Task> Create([FromBody] CreateControleRecebidoDto req) { var now = DateTime.UtcNow; var ano = req.Ano ?? (req.DataDaNf?.Year ?? DateTime.UtcNow.Year); var item = req.Item ?? 0; if (item <= 0) { var maxItem = await _db.ControleRecebidoLines .AsNoTracking() .Where(x => x.Ano == ano) .MaxAsync(x => (int?)x.Item) ?? 0; item = maxItem + 1; } var quantidade = req.Quantidade; var valorUnit = req.ValorUnit; var valorDaNf = req.ValorDaNf; if (!valorDaNf.HasValue && valorUnit.HasValue && quantidade.HasValue) { valorDaNf = Math.Round(valorUnit.Value * quantidade.Value, 2); } else if (!valorUnit.HasValue && valorDaNf.HasValue && quantidade.HasValue && quantidade.Value > 0) { valorUnit = Math.Round(valorDaNf.Value / quantidade.Value, 2); } var e = new ControleRecebidoLine { Id = Guid.NewGuid(), Ano = ano, Item = item, NotaFiscal = TrimOrNull(req.NotaFiscal), Chip = NullIfEmptyDigits(req.Chip), Serial = TrimOrNull(req.Serial), ConteudoDaNf = TrimOrNull(req.ConteudoDaNf), NumeroDaLinha = NullIfEmptyDigits(req.NumeroDaLinha), ValorUnit = valorUnit, ValorDaNf = valorDaNf, DataDaNf = ToUtc(req.DataDaNf), DataDoRecebimento = ToUtc(req.DataDoRecebimento), Quantidade = quantidade, IsResumo = req.IsResumo ?? false, CreatedAt = now, UpdatedAt = now }; _db.ControleRecebidoLines.Add(e); await _db.SaveChangesAsync(); return CreatedAtAction(nameof(GetById), new { id = e.Id }, ToDetailDto(e)); } [HttpPut("{id:guid}")] [Authorize(Roles = "admin")] public async Task Update(Guid id, [FromBody] UpdateControleRecebidoRequest req) { var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); if (req.Ano.HasValue) x.Ano = req.Ano.Value; if (req.Item.HasValue) x.Item = req.Item.Value; x.NotaFiscal = TrimOrNull(req.NotaFiscal); x.Chip = NullIfEmptyDigits(req.Chip); x.Serial = TrimOrNull(req.Serial); x.ConteudoDaNf = TrimOrNull(req.ConteudoDaNf); x.NumeroDaLinha = NullIfEmptyDigits(req.NumeroDaLinha); x.ValorUnit = req.ValorUnit; x.ValorDaNf = req.ValorDaNf; x.DataDaNf = ToUtc(req.DataDaNf); x.DataDoRecebimento = ToUtc(req.DataDoRecebimento); x.Quantidade = req.Quantidade; if (req.IsResumo.HasValue) x.IsResumo = req.IsResumo.Value; x.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return NoContent(); } [HttpDelete("{id:guid}")] [Authorize(Roles = "admin")] public async Task Delete(Guid id) { var x = await _db.ControleRecebidoLines.FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); _db.ControleRecebidoLines.Remove(x); await _db.SaveChangesAsync(); return NoContent(); } private static ControleRecebidoDetailDto ToDetailDto(ControleRecebidoLine x) => new() { Id = x.Id, Ano = x.Ano, Item = x.Item, NotaFiscal = x.NotaFiscal, Chip = x.Chip, Serial = x.Serial, ConteudoDaNf = x.ConteudoDaNf, NumeroDaLinha = x.NumeroDaLinha, ValorUnit = x.ValorUnit, ValorDaNf = x.ValorDaNf, DataDaNf = x.DataDaNf, DataDoRecebimento = x.DataDoRecebimento, Quantidade = x.Quantidade, IsResumo = x.IsResumo, CreatedAt = x.CreatedAt, UpdatedAt = x.UpdatedAt }; private static DateTime? ToUtc(DateTime? dt) { if (dt == null) return null; var v = dt.Value; return v.Kind == DateTimeKind.Utc ? v : (v.Kind == DateTimeKind.Local ? v.ToUniversalTime() : DateTime.SpecifyKind(v, DateTimeKind.Utc)); } private static string? TrimOrNull(string? s) { if (string.IsNullOrWhiteSpace(s)) return null; return s.Trim(); } private static string? NullIfEmptyDigits(string? s) { var d = OnlyDigits(s); return string.IsNullOrWhiteSpace(d) ? null : d; } private static string OnlyDigits(string? s) { if (string.IsNullOrWhiteSpace(s)) return ""; var sb = new StringBuilder(); foreach (var c in s) { if (char.IsDigit(c)) sb.Append(c); } return sb.ToString(); } private static bool TryParseSearchDateUtcStart(string value, out DateTime utcStart) { utcStart = default; if (string.IsNullOrWhiteSpace(value)) return false; var s = value.Trim(); DateTime parsed; if (DateTime.TryParse(s, new CultureInfo("pt-BR"), DateTimeStyles.None, out parsed) || DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsed)) { utcStart = DateTime.SpecifyKind(parsed.Date, DateTimeKind.Utc); return true; } return false; } private static bool TryParseSearchDecimal(string value, out decimal parsed) { parsed = 0m; if (string.IsNullOrWhiteSpace(value)) return false; var s = value.Trim().Replace("R$", "", StringComparison.OrdinalIgnoreCase).Trim(); return decimal.TryParse(s, NumberStyles.Any, new CultureInfo("pt-BR"), out parsed) || decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out parsed); } } }