using line_gestao_api.Data; using line_gestao_api.Dtos; using line_gestao_api.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace line_gestao_api.Controllers { [ApiController] [Route("api/mureg")] public class MuregController : ControllerBase { private readonly AppDbContext _db; public MuregController(AppDbContext db) { _db = db; } public class ImportExcelForm { public IFormFile File { get; set; } = default!; } // ========================================================== // ✅ GET: /api/mureg (paginação, busca e ordenação) // Cliente vem da GERAL (MobileLines) // ========================================================== [HttpGet] public async Task>> GetAll( [FromQuery] string? search, [FromQuery] string? client, [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? sortBy = "item", [FromQuery] string? sortDir = "asc") { page = page < 1 ? 1 : page; pageSize = pageSize < 1 ? 10 : pageSize; var q = _db.MuregLines .AsNoTracking() .Include(x => x.MobileLine) .AsQueryable(); if (!string.IsNullOrWhiteSpace(client)) { var c = client.Trim(); q = q.Where(x => EF.Functions.ILike((x.MobileLine.Cliente ?? ""), c)); } if (!string.IsNullOrWhiteSpace(search)) { var s = search.Trim(); q = q.Where(x => EF.Functions.ILike((x.LinhaAntiga ?? ""), $"%{s}%") || EF.Functions.ILike((x.LinhaNova ?? ""), $"%{s}%") || EF.Functions.ILike((x.ICCID ?? ""), $"%{s}%") || EF.Functions.ILike((x.MobileLine.Cliente ?? ""), $"%{s}%") || EF.Functions.ILike((x.MobileLine.Usuario ?? ""), $"%{s}%") || EF.Functions.ILike((x.MobileLine.Skil ?? ""), $"%{s}%") || EF.Functions.ILike(x.Item.ToString(), $"%{s}%")); } var total = await q.CountAsync(); var sb = (sortBy ?? "item").Trim().ToLowerInvariant(); var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase); q = sb switch { "item" => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item), "linhaantiga" => desc ? q.OrderByDescending(x => x.LinhaAntiga ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.LinhaAntiga ?? "").ThenBy(x => x.Item), "linhanova" => desc ? q.OrderByDescending(x => x.LinhaNova ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.LinhaNova ?? "").ThenBy(x => x.Item), "iccid" => desc ? q.OrderByDescending(x => x.ICCID ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.ICCID ?? "").ThenBy(x => x.Item), "datadamureg" => desc ? q.OrderByDescending(x => x.DataDaMureg).ThenBy(x => x.Item) : q.OrderBy(x => x.DataDaMureg).ThenBy(x => x.Item), "cliente" => desc ? q.OrderByDescending(x => x.MobileLine.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.MobileLine.Cliente ?? "").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 MuregListDto { Id = x.Id, Item = x.Item, LinhaAntiga = x.LinhaAntiga, LinhaNova = x.LinhaNova, ICCID = x.ICCID, DataDaMureg = x.DataDaMureg, Cliente = x.MobileLine.Cliente, MobileLineId = x.MobileLineId }) .ToListAsync(); return Ok(new PagedResult { Page = page, PageSize = pageSize, Total = total, Items = items }); } // ========================================================== // ✅ GET: /api/mureg/{id} // Detalhe para modal (puxa dados da GERAL) // ========================================================== [HttpGet("{id:guid}")] public async Task> GetById(Guid id) { var x = await _db.MuregLines .AsNoTracking() .Include(a => a.MobileLine) .FirstOrDefaultAsync(a => a.Id == id); if (x == null) return NotFound(); return Ok(new MuregDetailDto { Id = x.Id, Item = x.Item, LinhaAntiga = x.LinhaAntiga, LinhaNova = x.LinhaNova, ICCID = x.ICCID, DataDaMureg = x.DataDaMureg, MobileLineId = x.MobileLineId, Cliente = x.MobileLine?.Cliente, Usuario = x.MobileLine?.Usuario, Skil = x.MobileLine?.Skil, LinhaAtualNaGeral = x.MobileLine?.Linha, ChipNaGeral = x.MobileLine?.Chip, ContaNaGeral = x.MobileLine?.Conta, StatusNaGeral = x.MobileLine?.Status }); } // ========================================================== // ✅ GET: /api/mureg/clients (filtro no front) // ========================================================== [HttpGet("clients")] public async Task>> GetClients() { var clients = await _db.MuregLines .AsNoTracking() .Include(x => x.MobileLine) .Where(x => x.MobileLine.Cliente != null && x.MobileLine.Cliente != "") .Select(x => x.MobileLine.Cliente!) .Distinct() .OrderBy(x => x) .ToListAsync(); return Ok(clients); } // ========================================================== // ✅ POST: /api/mureg // Cria MUREG manualmente (usando MobileLineId) // - MobileLines (GERAL) prevalece: Cliente/Usuário/Skil etc vêm da GERAL // - Se LinhaNova vier, reflete na GERAL (Linha e Chip=ICCID se vier) // ========================================================== public class CreateMuregDto { public int Item { get; set; } public Guid MobileLineId { get; set; } public string? LinhaAntiga { get; set; } // opcional (snapshot) public string? LinhaNova { get; set; } // opcional public string? ICCID { get; set; } // opcional public DateTime? DataDaMureg { get; set; } // opcional } [HttpPost] public async Task> Create([FromBody] CreateMuregDto req) { if (req.MobileLineId == Guid.Empty) return BadRequest(new { message = "mobileLineId é obrigatório." }); // linha canônica (GERAL) var mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == req.MobileLineId); if (mobile == null) return BadRequest(new { message = "MobileLine não encontrada (mobileLineId inválido)." }); // normaliza números string? linhaNova = NullIfEmptyDigits(req.LinhaNova); string? iccid = NullIfEmptyDigits(req.ICCID); string? linhaAntiga = NullIfEmptyDigits(req.LinhaAntiga); // snapshot: se LinhaAntiga não veio, captura da GERAL var linhaAntigaSnapshot = !string.IsNullOrWhiteSpace(linhaAntiga) ? linhaAntiga : mobile.Linha; // conflito: LinhaNova já existe em outra MobileLine if (!string.IsNullOrWhiteSpace(linhaNova)) { var exists = await _db.MobileLines.AsNoTracking() .AnyAsync(x => x.Linha == linhaNova && x.Id != mobile.Id); if (exists) return Conflict(new { message = $"Conflito: a LinhaNova {linhaNova} já existe em outra linha da GERAL." }); } // item: se não vier, gera sequencial int item = req.Item; if (item <= 0) { var maxItem = await _db.MuregLines.MaxAsync(x => (int?)x.Item) ?? 0; item = maxItem + 1; } var now = DateTime.UtcNow; var entity = new MuregLine { Id = Guid.NewGuid(), Item = item, MobileLineId = mobile.Id, LinhaAntiga = linhaAntigaSnapshot, LinhaNova = linhaNova, ICCID = iccid, DataDaMureg = ToUtc(req.DataDaMureg), CreatedAt = now, UpdatedAt = now }; _db.MuregLines.Add(entity); // ✅ reflete na GERAL (prevalece) if (!string.IsNullOrWhiteSpace(linhaNova)) mobile.Linha = linhaNova; if (!string.IsNullOrWhiteSpace(iccid)) mobile.Chip = iccid; mobile.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); // devolve detalhe return CreatedAtAction(nameof(GetById), new { id = entity.Id }, new MuregDetailDto { Id = entity.Id, Item = entity.Item, LinhaAntiga = entity.LinhaAntiga, LinhaNova = entity.LinhaNova, ICCID = entity.ICCID, DataDaMureg = entity.DataDaMureg, MobileLineId = entity.MobileLineId, Cliente = mobile.Cliente, Usuario = mobile.Usuario, Skil = mobile.Skil, LinhaAtualNaGeral = mobile.Linha, ChipNaGeral = mobile.Chip, ContaNaGeral = mobile.Conta, StatusNaGeral = mobile.Status }); } // ========================================================== // ✅ PUT: /api/mureg/{id} // Atualiza o registro MUREG e (se LinhaNova/ICCID vierem) reflete na GERAL // ========================================================== public class UpdateMuregDto { public int? Item { get; set; } public Guid? MobileLineId { get; set; } // opcional mudar vínculo public string? LinhaAntiga { get; set; } public string? LinhaNova { get; set; } public string? ICCID { get; set; } public DateTime? DataDaMureg { get; set; } } [HttpPut("{id:guid}")] public async Task Update(Guid id, [FromBody] UpdateMuregDto req) { var entity = await _db.MuregLines.FirstOrDefaultAsync(x => x.Id == id); if (entity == null) return NotFound(); // troca vínculo (se mandou) if (req.MobileLineId.HasValue && req.MobileLineId.Value != Guid.Empty && req.MobileLineId.Value != entity.MobileLineId) { var mobileNew = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == req.MobileLineId.Value); if (mobileNew == null) return BadRequest(new { message = "MobileLineId inválido." }); entity.MobileLineId = mobileNew.Id; } // carrega MobileLine atual vinculada var mobile = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == entity.MobileLineId); if (mobile == null) return BadRequest(new { message = "MobileLine vinculada não encontrada." }); // normaliza string? linhaNova = req.LinhaNova != null ? NullIfEmptyDigits(req.LinhaNova) : null; string? iccid = req.ICCID != null ? NullIfEmptyDigits(req.ICCID) : null; string? linhaAntiga = req.LinhaAntiga != null ? NullIfEmptyDigits(req.LinhaAntiga) : null; // item if (req.Item.HasValue && req.Item.Value > 0) entity.Item = req.Item.Value; // snapshot linha antiga (se enviar null/"" não mexe; se enviar valor, atualiza) if (req.LinhaAntiga != null) entity.LinhaAntiga = string.IsNullOrWhiteSpace(linhaAntiga) ? entity.LinhaAntiga : linhaAntiga; if (req.DataDaMureg != null) entity.DataDaMureg = ToUtc(req.DataDaMureg); if (req.LinhaNova != null) entity.LinhaNova = linhaNova; // pode virar null if (req.ICCID != null) entity.ICCID = iccid; // pode virar null // conflito de LinhaNova na GERAL if (!string.IsNullOrWhiteSpace(linhaNova)) { var exists = await _db.MobileLines.AsNoTracking() .AnyAsync(x => x.Linha == linhaNova && x.Id != mobile.Id); if (exists) return Conflict(new { message = $"Conflito: a LinhaNova {linhaNova} já existe em outra linha da GERAL." }); } // ✅ reflete na GERAL (se o usuário mandou esses campos) if (req.LinhaNova != null && !string.IsNullOrWhiteSpace(linhaNova)) mobile.Linha = linhaNova; if (req.ICCID != null && !string.IsNullOrWhiteSpace(iccid)) mobile.Chip = iccid; entity.UpdatedAt = DateTime.UtcNow; mobile.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return NoContent(); } // ========================================================== // ✅ DELETE: /api/mureg/{id} // Exclui registro MUREG // ========================================================== [HttpDelete("{id:guid}")] public async Task Delete(Guid id) { var entity = await _db.MuregLines.FirstOrDefaultAsync(x => x.Id == id); if (entity == null) return NotFound(); _db.MuregLines.Remove(entity); await _db.SaveChangesAsync(); return NoContent(); } // ========================================================== // ✅ POST: /api/mureg/import-excel (mantido) // ========================================================== [HttpPost("import-excel")] [Consumes("multipart/form-data")] [RequestSizeLimit(50_000_000)] public async Task ImportExcel([FromForm] ImportExcelForm form) { return BadRequest(new { message = "Importe a planilha pela página GERAL. O MUREG será carregado automaticamente." }); } // ========================================================== // HELPERS (iguais ao LinesController, só o que precisamos aqui) // ========================================================== 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 OnlyDigits(string? s) { if (string.IsNullOrWhiteSpace(s)) return ""; var sb = new System.Text.StringBuilder(); foreach (var c in s) if (char.IsDigit(c)) sb.Append(c); return sb.ToString(); } private static string? NullIfEmptyDigits(string? s) { var d = OnlyDigits(s); return string.IsNullOrWhiteSpace(d) ? null : d; } } }