line-gestao-api/Controllers/MuregController.cs

383 lines
15 KiB
C#

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<ActionResult<PagedResult<MuregListDto>>> 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<MuregListDto>
{
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<ActionResult<MuregDetailDto>> 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<ActionResult<List<string>>> 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<ActionResult<MuregDetailDto>> 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<IActionResult> 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();
}
// ==========================================================
// ✅ POST: /api/mureg/import-excel (mantido)
// ==========================================================
[HttpPost("import-excel")]
[Consumes("multipart/form-data")]
[RequestSizeLimit(50_000_000)]
public async Task<IActionResult> 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;
}
}
}