411 lines
16 KiB
C#
411 lines
16 KiB
C#
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.Linq;
|
|
|
|
namespace line_gestao_api.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/user-data")]
|
|
public class UserDataController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
public UserDataController(AppDbContext db) => _db = db;
|
|
|
|
// ==========================================================
|
|
// GET /api/user-data (LINHAS - Tabela Interna)
|
|
// ==========================================================
|
|
[HttpGet]
|
|
public async Task<ActionResult<PagedResult<UserDataListDto>>> GetAll(
|
|
[FromQuery] string? search,
|
|
[FromQuery] string? client, // Filtro por cliente
|
|
[FromQuery] string? tipo, // PF/PJ
|
|
[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.UserDatas.AsNoTracking();
|
|
|
|
// Filtro exato por cliente (quando abre o card)
|
|
if (!string.IsNullOrWhiteSpace(client))
|
|
{
|
|
var c = client.Trim();
|
|
q = q.Where(x => x.Cliente == c);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var t = tipo.Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
|
|
}
|
|
|
|
// Busca global
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.Trim();
|
|
q = q.Where(x =>
|
|
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Nome ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.RazaoSocial ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Cpf ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Cnpj ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Email ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Celular ?? "", $"%{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),
|
|
"linha" => desc ? q.OrderByDescending(x => x.Linha) : q.OrderBy(x => x.Linha),
|
|
"cliente" => desc ? q.OrderByDescending(x => x.Cliente) : q.OrderBy(x => x.Cliente),
|
|
_ => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item),
|
|
};
|
|
|
|
var items = await q
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(x => new UserDataListDto
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
Linha = x.Linha,
|
|
Cliente = x.Cliente,
|
|
TipoPessoa = x.TipoPessoa,
|
|
Nome = x.Nome,
|
|
RazaoSocial = x.RazaoSocial,
|
|
Cnpj = x.Cnpj,
|
|
Cpf = x.Cpf,
|
|
Rg = x.Rg,
|
|
DataNascimento = x.DataNascimento != null ? x.DataNascimento.Value.ToString("yyyy-MM-dd") : null,
|
|
Email = x.Email,
|
|
Endereco = x.Endereco,
|
|
Celular = x.Celular,
|
|
TelefoneFixo = x.TelefoneFixo
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(new PagedResult<UserDataListDto>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
Items = items
|
|
});
|
|
}
|
|
|
|
// ==========================================================
|
|
// GET /api/user-data/groups (CARDS + KPIs GERAIS)
|
|
// ==========================================================
|
|
[HttpGet("groups")]
|
|
public async Task<ActionResult<UserDataGroupResponse>> GetGroups(
|
|
[FromQuery] string? search,
|
|
[FromQuery] string? tipo,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 10,
|
|
[FromQuery] string? sortBy = "cliente",
|
|
[FromQuery] string? sortDir = "asc")
|
|
{
|
|
page = page < 1 ? 1 : page;
|
|
pageSize = pageSize < 1 ? 10 : pageSize;
|
|
|
|
var q = _db.UserDatas.AsNoTracking()
|
|
.Where(x => x.Cliente != null && x.Cliente != "");
|
|
|
|
if (!string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var t = tipo.Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.Trim();
|
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%"));
|
|
}
|
|
|
|
// ✅ 1. CÁLCULO DOS KPIS GERAIS (Baseado em todos os dados filtrados, sem paginação)
|
|
var kpis = new UserDataKpisDto
|
|
{
|
|
TotalRegistros = await q.CountAsync(),
|
|
ClientesUnicos = await q.Select(x => x.Cliente).Distinct().CountAsync(),
|
|
ComCpf = await q.CountAsync(x => x.Cpf != null && x.Cpf != ""),
|
|
ComCnpj = await q.CountAsync(x => x.Cnpj != null && x.Cnpj != ""),
|
|
ComEmail = await q.CountAsync(x => x.Email != null && x.Email != "")
|
|
};
|
|
|
|
// ✅ 2. AGRUPAMENTO (Para os Cards)
|
|
var grouped = q
|
|
.GroupBy(x => x.Cliente!)
|
|
.Select(g => new UserDataClientGroupDto
|
|
{
|
|
Cliente = g.Key,
|
|
TotalRegistros = g.Count(),
|
|
ComCpf = g.Count(x => x.Cpf != null && x.Cpf != ""),
|
|
ComCnpj = g.Count(x => x.Cnpj != null && x.Cnpj != ""),
|
|
ComEmail = g.Count(x => x.Email != null && x.Email != "")
|
|
});
|
|
|
|
var totalGroups = await grouped.CountAsync();
|
|
|
|
// Ordenação
|
|
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
|
grouped = desc ? grouped.OrderByDescending(x => x.Cliente) : grouped.OrderBy(x => x.Cliente);
|
|
|
|
// Paginação dos Grupos
|
|
var items = await grouped
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
|
|
return Ok(new UserDataGroupResponse
|
|
{
|
|
Data = new PagedResult<UserDataClientGroupDto>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = totalGroups, // Total de Clientes
|
|
Items = items
|
|
},
|
|
Kpis = kpis // KPIs Totais
|
|
});
|
|
}
|
|
|
|
[HttpGet("clients")]
|
|
public async Task<ActionResult<List<string>>> GetClients([FromQuery] string? tipo)
|
|
{
|
|
var q = _db.UserDatas.AsNoTracking();
|
|
|
|
if (!string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var t = tipo.Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") q = q.Where(x => x.TipoPessoa == t);
|
|
}
|
|
|
|
return await q
|
|
.Where(x => !string.IsNullOrEmpty(x.Cliente))
|
|
.Select(x => x.Cliente!)
|
|
.Distinct()
|
|
.OrderBy(x => x)
|
|
.ToListAsync();
|
|
}
|
|
|
|
[HttpGet("{id:guid}")]
|
|
public async Task<ActionResult<UserDataDetailDto>> GetById(Guid id)
|
|
{
|
|
var x = await _db.UserDatas.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
|
|
return Ok(new UserDataDetailDto
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
Linha = x.Linha,
|
|
Cliente = x.Cliente,
|
|
TipoPessoa = x.TipoPessoa,
|
|
Nome = x.Nome,
|
|
RazaoSocial = x.RazaoSocial,
|
|
Cnpj = x.Cnpj,
|
|
Cpf = x.Cpf,
|
|
Rg = x.Rg,
|
|
Email = x.Email,
|
|
Celular = x.Celular,
|
|
Endereco = x.Endereco,
|
|
TelefoneFixo = x.TelefoneFixo,
|
|
DataNascimento = x.DataNascimento
|
|
});
|
|
}
|
|
|
|
[HttpPost]
|
|
[Authorize(Roles = "admin")]
|
|
public async Task<ActionResult<UserDataDetailDto>> Create([FromBody] CreateUserDataRequest req)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
|
|
var linha = TrimOrNull(req.Linha);
|
|
var cliente = TrimOrNull(req.Cliente);
|
|
var nome = TrimOrNull(req.Nome);
|
|
var razao = TrimOrNull(req.RazaoSocial);
|
|
var cpf = NullIfEmptyDigits(req.Cpf);
|
|
var cnpj = NullIfEmptyDigits(req.Cnpj);
|
|
var tipo = NormalizeTipo(req.TipoPessoa, cpf, cnpj, razao, nome);
|
|
|
|
if (string.IsNullOrWhiteSpace(cliente) && !string.IsNullOrWhiteSpace(linha))
|
|
{
|
|
var linhaDigits = OnlyDigits(linha);
|
|
MobileLine? mobile = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(linhaDigits))
|
|
{
|
|
mobile = await _db.MobileLines.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => x.Linha == linhaDigits);
|
|
}
|
|
else
|
|
{
|
|
var raw = linha.Trim();
|
|
mobile = await _db.MobileLines.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => EF.Functions.ILike(x.Linha ?? "", raw));
|
|
}
|
|
|
|
if (mobile != null)
|
|
{
|
|
cliente = mobile.Cliente;
|
|
if (string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var skil = (mobile.Skil ?? "").Trim().ToUpperInvariant();
|
|
if (skil.Contains("JUR")) tipo = "PJ";
|
|
else if (skil.Contains("FIS")) tipo = "PF";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(cliente))
|
|
{
|
|
cliente = !string.IsNullOrWhiteSpace(razao) ? razao : nome;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(nome) && tipo == "PF") nome = cliente;
|
|
if (string.IsNullOrWhiteSpace(razao) && tipo == "PJ") razao = cliente;
|
|
|
|
var item = req.Item;
|
|
if (!item.HasValue || item.Value <= 0)
|
|
{
|
|
var maxItem = await _db.UserDatas.MaxAsync(x => (int?)x.Item) ?? 0;
|
|
item = maxItem + 1;
|
|
}
|
|
|
|
var e = new UserData
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = item.Value,
|
|
Linha = linha,
|
|
Cliente = cliente,
|
|
TipoPessoa = tipo ?? "PF",
|
|
Nome = nome,
|
|
RazaoSocial = razao,
|
|
Cnpj = cnpj,
|
|
Cpf = cpf,
|
|
Rg = TrimOrNull(req.Rg),
|
|
Email = TrimOrNull(req.Email),
|
|
Endereco = TrimOrNull(req.Endereco),
|
|
Celular = TrimOrNull(req.Celular),
|
|
TelefoneFixo = TrimOrNull(req.TelefoneFixo),
|
|
DataNascimento = req.DataNascimento.HasValue ? ToUtc(req.DataNascimento.Value) : null,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
_db.UserDatas.Add(e);
|
|
await _db.SaveChangesAsync();
|
|
|
|
return CreatedAtAction(nameof(GetById), new { id = e.Id }, new UserDataDetailDto
|
|
{
|
|
Id = e.Id,
|
|
Item = e.Item,
|
|
Linha = e.Linha,
|
|
Cliente = e.Cliente,
|
|
TipoPessoa = e.TipoPessoa,
|
|
Nome = e.Nome,
|
|
RazaoSocial = e.RazaoSocial,
|
|
Cnpj = e.Cnpj,
|
|
Cpf = e.Cpf,
|
|
Rg = e.Rg,
|
|
Email = e.Email,
|
|
Celular = e.Celular,
|
|
Endereco = e.Endereco,
|
|
TelefoneFixo = e.TelefoneFixo,
|
|
DataNascimento = e.DataNascimento
|
|
});
|
|
}
|
|
|
|
[HttpPut("{id:guid}")]
|
|
[Authorize(Roles = "admin")]
|
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateUserDataRequest req)
|
|
{
|
|
var x = await _db.UserDatas.FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
|
|
if (req.Item.HasValue) x.Item = req.Item.Value;
|
|
if (req.Linha != null) x.Linha = TrimOrNull(req.Linha);
|
|
if (req.Cliente != null) x.Cliente = TrimOrNull(req.Cliente);
|
|
if (req.TipoPessoa != null) x.TipoPessoa = NormalizeTipo(req.TipoPessoa, req.Cpf, req.Cnpj, req.RazaoSocial, req.Nome) ?? x.TipoPessoa;
|
|
if (req.Nome != null) x.Nome = TrimOrNull(req.Nome);
|
|
if (req.RazaoSocial != null) x.RazaoSocial = TrimOrNull(req.RazaoSocial);
|
|
if (req.Cnpj != null) x.Cnpj = NullIfEmptyDigits(req.Cnpj);
|
|
if (req.Cpf != null) x.Cpf = NullIfEmptyDigits(req.Cpf);
|
|
if (req.Rg != null) x.Rg = TrimOrNull(req.Rg);
|
|
if (req.Email != null) x.Email = TrimOrNull(req.Email);
|
|
if (req.Endereco != null) x.Endereco = TrimOrNull(req.Endereco);
|
|
if (req.Celular != null) x.Celular = TrimOrNull(req.Celular);
|
|
if (req.TelefoneFixo != null) x.TelefoneFixo = TrimOrNull(req.TelefoneFixo);
|
|
|
|
if (req.DataNascimento.HasValue)
|
|
{
|
|
x.DataNascimento = ToUtc(req.DataNascimento.Value);
|
|
}
|
|
|
|
x.UpdatedAt = DateTime.UtcNow;
|
|
|
|
await _db.SaveChangesAsync();
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpDelete("{id:guid}")]
|
|
[Authorize(Roles = "admin")]
|
|
public async Task<IActionResult> Delete(Guid id)
|
|
{
|
|
var x = await _db.UserDatas.FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
|
|
_db.UserDatas.Remove(x);
|
|
await _db.SaveChangesAsync();
|
|
return NoContent();
|
|
}
|
|
|
|
private static string? TrimOrNull(string? s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
return s.Trim();
|
|
}
|
|
|
|
private static DateTime ToUtc(DateTime dt)
|
|
{
|
|
return dt.Kind == DateTimeKind.Utc ? dt
|
|
: (dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : DateTime.SpecifyKind(dt, DateTimeKind.Utc));
|
|
}
|
|
|
|
private static string? NormalizeTipo(string? tipo, string? cpf, string? cnpj, string? razao, string? nome)
|
|
{
|
|
var t = (tipo ?? "").Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") return t;
|
|
if (!string.IsNullOrWhiteSpace(cnpj) || !string.IsNullOrWhiteSpace(razao)) return "PJ";
|
|
if (!string.IsNullOrWhiteSpace(cpf) || !string.IsNullOrWhiteSpace(nome)) return "PF";
|
|
return null;
|
|
}
|
|
|
|
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 "";
|
|
return new string(s.Where(char.IsDigit).ToArray());
|
|
}
|
|
}
|
|
}
|