383 lines
15 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|