Feat: Aplicando Alterações/Ajustes
This commit is contained in:
parent
1f255888b0
commit
9d7306c395
|
|
@ -1,4 +1,5 @@
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.Globalization;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
|
|
@ -22,17 +23,20 @@ public class AuthController : ControllerBase
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
private readonly ITenantProvider _tenantProvider;
|
private readonly ITenantProvider _tenantProvider;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly ILogger<AuthController> _logger;
|
||||||
|
|
||||||
public AuthController(
|
public AuthController(
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
AppDbContext db,
|
AppDbContext db,
|
||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
IConfiguration config)
|
IConfiguration config,
|
||||||
|
ILogger<AuthController> logger)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_db = db;
|
_db = db;
|
||||||
_tenantProvider = tenantProvider;
|
_tenantProvider = tenantProvider;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
|
|
@ -75,6 +79,8 @@ public class AuthController : ControllerBase
|
||||||
if (!effectiveTenantId.HasValue)
|
if (!effectiveTenantId.HasValue)
|
||||||
return Unauthorized("Tenant inválido.");
|
return Unauthorized("Tenant inválido.");
|
||||||
|
|
||||||
|
await EnsureClientTenantDataBoundAsync(user, effectiveTenantId.Value);
|
||||||
|
|
||||||
var token = await GenerateJwtAsync(user, effectiveTenantId.Value);
|
var token = await GenerateJwtAsync(user, effectiveTenantId.Value);
|
||||||
return Ok(new AuthResponse(token));
|
return Ok(new AuthResponse(token));
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +147,8 @@ public class AuthController : ControllerBase
|
||||||
if (!effectiveTenantId.HasValue)
|
if (!effectiveTenantId.HasValue)
|
||||||
return Unauthorized("Tenant inválido.");
|
return Unauthorized("Tenant inválido.");
|
||||||
|
|
||||||
|
await EnsureClientTenantDataBoundAsync(user, effectiveTenantId.Value);
|
||||||
|
|
||||||
var token = await GenerateJwtAsync(user, effectiveTenantId.Value);
|
var token = await GenerateJwtAsync(user, effectiveTenantId.Value);
|
||||||
return Ok(new AuthResponse(token));
|
return Ok(new AuthResponse(token));
|
||||||
}
|
}
|
||||||
|
|
@ -208,4 +216,200 @@ public class AuthController : ControllerBase
|
||||||
|
|
||||||
return user.TenantId;
|
return user.TenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnsureClientTenantDataBoundAsync(ApplicationUser user, Guid tenantId)
|
||||||
|
{
|
||||||
|
var roles = await _userManager.GetRolesAsync(user);
|
||||||
|
if (!roles.Any(r => string.Equals(r, AppRoles.Cliente, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RebindMobileLinesToTenantBySourceKeyAsync(tenantId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Falha ao sincronizar linhas para tenant {TenantId} durante login de cliente.", tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> RebindMobileLinesToTenantBySourceKeyAsync(Guid tenantId)
|
||||||
|
{
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenant = await _db.Tenants
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == tenantId && t.Ativo && !t.IsSystem);
|
||||||
|
|
||||||
|
if (tenant == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(
|
||||||
|
tenant.SourceType,
|
||||||
|
SystemTenantConstants.MobileLinesClienteSourceType,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedKeys = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
AddNormalizedTenantKey(normalizedKeys, tenant.SourceKey);
|
||||||
|
AddNormalizedTenantKey(normalizedKeys, tenant.NomeOficial);
|
||||||
|
|
||||||
|
if (normalizedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates = await _db.MobileLines
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.TenantId != tenant.Id)
|
||||||
|
.Where(x => x.Cliente != null && x.Cliente != string.Empty)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasAnyTenantLine = await _db.MobileLines
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AsNoTracking()
|
||||||
|
.AnyAsync(x => x.TenantId == tenant.Id);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var reassigned = ReassignByMatcher(
|
||||||
|
candidates,
|
||||||
|
normalizedKeys,
|
||||||
|
tenant.Id,
|
||||||
|
now,
|
||||||
|
isRelaxedMatch: false);
|
||||||
|
|
||||||
|
if (reassigned == 0 && !hasAnyTenantLine)
|
||||||
|
{
|
||||||
|
reassigned = ReassignByMatcher(
|
||||||
|
candidates,
|
||||||
|
normalizedKeys,
|
||||||
|
tenant.Id,
|
||||||
|
now,
|
||||||
|
isRelaxedMatch: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reassigned > 0)
|
||||||
|
{
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return reassigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReassignByMatcher(
|
||||||
|
IReadOnlyList<MobileLine> candidates,
|
||||||
|
IReadOnlyCollection<string> normalizedKeys,
|
||||||
|
Guid tenantId,
|
||||||
|
DateTime now,
|
||||||
|
bool isRelaxedMatch)
|
||||||
|
{
|
||||||
|
if (normalizedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = isRelaxedMatch
|
||||||
|
? normalizedKeys.Where(k => k.Length >= 6).ToList()
|
||||||
|
: normalizedKeys.ToList();
|
||||||
|
|
||||||
|
if (keys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reassigned = 0;
|
||||||
|
|
||||||
|
foreach (var line in candidates)
|
||||||
|
{
|
||||||
|
var normalizedClient = NormalizeTenantKey(line.Cliente ?? string.Empty);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedClient) ||
|
||||||
|
string.Equals(normalizedClient, "RESERVA", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = !isRelaxedMatch
|
||||||
|
? keys.Contains(normalizedClient, StringComparer.Ordinal)
|
||||||
|
: keys.Any(k =>
|
||||||
|
normalizedClient.Contains(k, StringComparison.Ordinal) ||
|
||||||
|
k.Contains(normalizedClient, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.TenantId = tenantId;
|
||||||
|
line.UpdatedAt = now;
|
||||||
|
reassigned++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reassigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNormalizedTenantKey(ISet<string> keys, string? rawKey)
|
||||||
|
{
|
||||||
|
var normalized = NormalizeTenantKey(rawKey ?? string.Empty);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalized))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(normalized, "RESERVA", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.Add(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeTenantKey(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = value.Trim().Normalize(NormalizationForm.FormD);
|
||||||
|
var sb = new StringBuilder(normalized.Length);
|
||||||
|
var previousWasSpace = false;
|
||||||
|
|
||||||
|
foreach (var ch in normalized)
|
||||||
|
{
|
||||||
|
var category = CharUnicodeInfo.GetUnicodeCategory(ch);
|
||||||
|
if (category == UnicodeCategory.NonSpacingMark)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(ch))
|
||||||
|
{
|
||||||
|
if (!previousWasSpace)
|
||||||
|
{
|
||||||
|
sb.Append(' ');
|
||||||
|
previousWasSpace = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(char.ToUpperInvariant(ch));
|
||||||
|
previousWasSpace = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString().Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -528,6 +528,7 @@ namespace line_gestao_api.Controllers
|
||||||
line.Skil,
|
line.Skil,
|
||||||
line.Modalidade,
|
line.Modalidade,
|
||||||
line.VencConta,
|
line.VencConta,
|
||||||
|
line.FranquiaLine,
|
||||||
line.GestaoVozDados,
|
line.GestaoVozDados,
|
||||||
line.Skeelo,
|
line.Skeelo,
|
||||||
line.VivoNewsPlus,
|
line.VivoNewsPlus,
|
||||||
|
|
@ -592,6 +593,7 @@ namespace line_gestao_api.Controllers
|
||||||
Skil = x.Skil,
|
Skil = x.Skil,
|
||||||
Modalidade = x.Modalidade,
|
Modalidade = x.Modalidade,
|
||||||
VencConta = x.VencConta,
|
VencConta = x.VencConta,
|
||||||
|
FranquiaLine = x.FranquiaLine,
|
||||||
GestaoVozDados = x.GestaoVozDados,
|
GestaoVozDados = x.GestaoVozDados,
|
||||||
Skeelo = x.Skeelo,
|
Skeelo = x.Skeelo,
|
||||||
VivoNewsPlus = x.VivoNewsPlus,
|
VivoNewsPlus = x.VivoNewsPlus,
|
||||||
|
|
@ -660,6 +662,7 @@ namespace line_gestao_api.Controllers
|
||||||
Skil = x.Skil,
|
Skil = x.Skil,
|
||||||
Modalidade = x.Modalidade,
|
Modalidade = x.Modalidade,
|
||||||
VencConta = x.VencConta,
|
VencConta = x.VencConta,
|
||||||
|
FranquiaLine = x.FranquiaLine,
|
||||||
GestaoVozDados = x.GestaoVozDados,
|
GestaoVozDados = x.GestaoVozDados,
|
||||||
Skeelo = x.Skeelo,
|
Skeelo = x.Skeelo,
|
||||||
VivoNewsPlus = x.VivoNewsPlus,
|
VivoNewsPlus = x.VivoNewsPlus,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Linq;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using line_gestao_api.Dtos;
|
using line_gestao_api.Dtos;
|
||||||
using line_gestao_api.Services;
|
using line_gestao_api.Services;
|
||||||
|
|
@ -12,6 +14,8 @@ namespace line_gestao_api.Controllers;
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class ResumoController : ControllerBase
|
public class ResumoController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private static readonly Regex PlanGbRegex = new(@"(\d+(?:[.,]\d+)?)\s*(GB|MB)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
private readonly SpreadsheetImportAuditService _spreadsheetImportAuditService;
|
private readonly SpreadsheetImportAuditService _spreadsheetImportAuditService;
|
||||||
|
|
||||||
|
|
@ -23,6 +27,36 @@ public class ResumoController : ControllerBase
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<ResumoResponseDto>> GetResumo()
|
public async Task<ActionResult<ResumoResponseDto>> GetResumo()
|
||||||
|
{
|
||||||
|
var spreadsheetResponse = await BuildSpreadsheetResumoAsync();
|
||||||
|
var hasLiveLines = await _db.MobileLines.AsNoTracking().AnyAsync();
|
||||||
|
|
||||||
|
if (hasLiveLines)
|
||||||
|
{
|
||||||
|
var live = await BuildLiveResumoAsync();
|
||||||
|
|
||||||
|
spreadsheetResponse.MacrophonyPlans = live.MacrophonyPlans;
|
||||||
|
spreadsheetResponse.MacrophonyTotals = live.MacrophonyTotals;
|
||||||
|
spreadsheetResponse.VivoLineResumos = live.VivoLineResumos;
|
||||||
|
spreadsheetResponse.VivoLineTotals = live.VivoLineTotals;
|
||||||
|
spreadsheetResponse.PlanoContratoResumos = live.PlanoContratoResumos;
|
||||||
|
spreadsheetResponse.PlanoContratoTotal = live.PlanoContratoTotal;
|
||||||
|
spreadsheetResponse.LineTotais = live.LineTotais;
|
||||||
|
spreadsheetResponse.GbDistribuicao = live.GbDistribuicao;
|
||||||
|
spreadsheetResponse.GbDistribuicaoTotal = live.GbDistribuicaoTotal;
|
||||||
|
spreadsheetResponse.ReservaLines = live.ReservaLines;
|
||||||
|
spreadsheetResponse.ReservaPorDdd = live.ReservaPorDdd;
|
||||||
|
spreadsheetResponse.TotalGeralLinhasReserva = live.TotalGeralLinhasReserva;
|
||||||
|
spreadsheetResponse.ReservaTotal = live.ReservaTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
spreadsheetResponse.MacrophonyTotals ??= new ResumoMacrophonyTotalDto();
|
||||||
|
spreadsheetResponse.VivoLineTotals ??= new ResumoVivoLineTotalDto();
|
||||||
|
|
||||||
|
return Ok(spreadsheetResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ResumoResponseDto> BuildSpreadsheetResumoAsync()
|
||||||
{
|
{
|
||||||
var reservaLines = await _db.ResumoReservaLines.AsNoTracking()
|
var reservaLines = await _db.ResumoReservaLines.AsNoTracking()
|
||||||
.OrderBy(x => x.Ddd)
|
.OrderBy(x => x.Ddd)
|
||||||
|
|
@ -177,6 +211,302 @@ public class ResumoController : ControllerBase
|
||||||
response.VivoLineTotals ??= new ResumoVivoLineTotalDto();
|
response.VivoLineTotals ??= new ResumoVivoLineTotalDto();
|
||||||
response.VivoLineTotals.QtdLinhasTotal = canonicalTotalLinhas;
|
response.VivoLineTotals.QtdLinhasTotal = canonicalTotalLinhas;
|
||||||
|
|
||||||
return Ok(response);
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ResumoResponseDto> BuildLiveResumoAsync()
|
||||||
|
{
|
||||||
|
var allLines = _db.MobileLines.AsNoTracking();
|
||||||
|
var nonReservaLines = allLines.Where(x =>
|
||||||
|
!EF.Functions.ILike((x.Usuario ?? "").Trim(), "RESERVA") &&
|
||||||
|
!EF.Functions.ILike((x.Skil ?? "").Trim(), "RESERVA") &&
|
||||||
|
!EF.Functions.ILike((x.Cliente ?? "").Trim(), "RESERVA"));
|
||||||
|
var reservaLinesQuery = allLines.Where(x =>
|
||||||
|
EF.Functions.ILike((x.Usuario ?? "").Trim(), "RESERVA") ||
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "RESERVA") ||
|
||||||
|
EF.Functions.ILike((x.Cliente ?? "").Trim(), "RESERVA"));
|
||||||
|
|
||||||
|
var planosAgg = await nonReservaLines
|
||||||
|
.GroupBy(x => (x.PlanoContrato ?? "").Trim())
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
Plano = g.Key,
|
||||||
|
TotalLinhas = g.Count(),
|
||||||
|
FranquiaMedia = g.Average(x => (decimal?)x.FranquiaVivo),
|
||||||
|
ValorTotal = g.Sum(x => x.ValorContratoVivo ?? 0m),
|
||||||
|
TravelCount = g.Count(x => (x.VivoTravelMundo ?? 0m) > 0m)
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var planoRows = planosAgg
|
||||||
|
.Select(row =>
|
||||||
|
{
|
||||||
|
var planoLabel = string.IsNullOrWhiteSpace(row.Plano) ? "SEM PLANO" : row.Plano;
|
||||||
|
var franquiaGb = (row.FranquiaMedia ?? 0m) > 0m
|
||||||
|
? row.FranquiaMedia
|
||||||
|
: ExtractGbFromPlanName(planoLabel);
|
||||||
|
var valorIndividual = row.TotalLinhas > 0 ? row.ValorTotal / row.TotalLinhas : (decimal?)null;
|
||||||
|
|
||||||
|
return new ResumoPlanoContratoResumoDto
|
||||||
|
{
|
||||||
|
PlanoContrato = planoLabel,
|
||||||
|
Gb = franquiaGb,
|
||||||
|
FranquiaGb = franquiaGb,
|
||||||
|
TotalLinhas = row.TotalLinhas,
|
||||||
|
ValorTotal = row.ValorTotal,
|
||||||
|
ValorIndividualComSvas = valorIndividual
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.TotalLinhas ?? 0)
|
||||||
|
.ThenBy(x => x.PlanoContrato)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var macrophonyRows = planosAgg
|
||||||
|
.Select(row =>
|
||||||
|
{
|
||||||
|
var planoLabel = string.IsNullOrWhiteSpace(row.Plano) ? "SEM PLANO" : row.Plano;
|
||||||
|
var franquiaGb = (row.FranquiaMedia ?? 0m) > 0m
|
||||||
|
? row.FranquiaMedia
|
||||||
|
: ExtractGbFromPlanName(planoLabel);
|
||||||
|
var valorIndividual = row.TotalLinhas > 0 ? row.ValorTotal / row.TotalLinhas : (decimal?)null;
|
||||||
|
|
||||||
|
return new ResumoMacrophonyPlanDto
|
||||||
|
{
|
||||||
|
PlanoContrato = planoLabel,
|
||||||
|
Gb = franquiaGb,
|
||||||
|
FranquiaGb = franquiaGb,
|
||||||
|
TotalLinhas = row.TotalLinhas,
|
||||||
|
ValorTotal = row.ValorTotal,
|
||||||
|
ValorIndividualComSvas = valorIndividual,
|
||||||
|
VivoTravel = row.TravelCount > 0
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.TotalLinhas ?? 0)
|
||||||
|
.ThenBy(x => x.PlanoContrato)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var clientesAgg = await nonReservaLines
|
||||||
|
.GroupBy(x => (x.Cliente ?? "").Trim())
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
Cliente = g.Key,
|
||||||
|
QtdLinhas = g.Count(),
|
||||||
|
FranquiaTotal = g.Sum(x => x.FranquiaVivo ?? 0m),
|
||||||
|
ValorContratoVivo = g.Sum(x => x.ValorContratoVivo ?? 0m),
|
||||||
|
FranquiaLine = g.Sum(x => x.FranquiaLine ?? 0m),
|
||||||
|
ValorContratoLine = g.Sum(x => x.ValorContratoLine ?? 0m),
|
||||||
|
Lucro = g.Sum(x => x.Lucro ?? 0m)
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var clientesRows = clientesAgg
|
||||||
|
.Select(row => new ResumoVivoLineResumoDto
|
||||||
|
{
|
||||||
|
Cliente = string.IsNullOrWhiteSpace(row.Cliente) ? "SEM CLIENTE" : row.Cliente,
|
||||||
|
QtdLinhas = row.QtdLinhas,
|
||||||
|
FranquiaTotal = row.FranquiaTotal,
|
||||||
|
ValorContratoVivo = row.ValorContratoVivo,
|
||||||
|
FranquiaLine = row.FranquiaLine,
|
||||||
|
ValorContratoLine = row.ValorContratoLine,
|
||||||
|
Lucro = row.Lucro
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.QtdLinhas ?? 0)
|
||||||
|
.ThenBy(x => x.Cliente)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var totalLinhasNaoReserva = clientesRows.Sum(x => x.QtdLinhas ?? 0);
|
||||||
|
var totalFranquiaNaoReserva = clientesRows.Sum(x => x.FranquiaTotal ?? 0m);
|
||||||
|
var totalValorContratoVivo = clientesRows.Sum(x => x.ValorContratoVivo ?? 0m);
|
||||||
|
var totalFranquiaLine = clientesRows.Sum(x => x.FranquiaLine ?? 0m);
|
||||||
|
var totalValorContratoLine = clientesRows.Sum(x => x.ValorContratoLine ?? 0m);
|
||||||
|
var totalLucro = clientesRows.Sum(x => x.Lucro ?? 0m);
|
||||||
|
|
||||||
|
var pfAtivas = await nonReservaLines
|
||||||
|
.Where(x => EF.Functions.ILike((x.Status ?? "").Trim(), "%ativo%"))
|
||||||
|
.Where(x =>
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%fís%") ||
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%fis%") ||
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%pf%"))
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
QtdLinhas = g.Count(),
|
||||||
|
ValorTotal = g.Sum(x => x.ValorContratoLine ?? 0m),
|
||||||
|
LucroTotal = g.Sum(x => x.Lucro ?? 0m)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var pjAtivas = await nonReservaLines
|
||||||
|
.Where(x => EF.Functions.ILike((x.Status ?? "").Trim(), "%ativo%"))
|
||||||
|
.Where(x =>
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%jur%") ||
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "%pj%"))
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
QtdLinhas = g.Count(),
|
||||||
|
ValorTotal = g.Sum(x => x.ValorContratoLine ?? 0m),
|
||||||
|
LucroTotal = g.Sum(x => x.Lucro ?? 0m)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var totaisLine = new List<ResumoLineTotaisDto>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "PF",
|
||||||
|
QtdLinhas = pfAtivas?.QtdLinhas ?? 0,
|
||||||
|
ValorTotalLine = pfAtivas?.ValorTotal ?? 0m,
|
||||||
|
LucroTotalLine = pfAtivas?.LucroTotal ?? 0m
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "PJ",
|
||||||
|
QtdLinhas = pjAtivas?.QtdLinhas ?? 0,
|
||||||
|
ValorTotalLine = pjAtivas?.ValorTotal ?? 0m,
|
||||||
|
LucroTotalLine = pjAtivas?.LucroTotal ?? 0m
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "TOTAL",
|
||||||
|
QtdLinhas = totalLinhasNaoReserva,
|
||||||
|
ValorTotalLine = totalValorContratoLine,
|
||||||
|
LucroTotalLine = totalLucro
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var gbDistribuicao = await nonReservaLines
|
||||||
|
.Where(x => (x.FranquiaVivo ?? 0m) > 0m)
|
||||||
|
.GroupBy(x => x.FranquiaVivo ?? 0m)
|
||||||
|
.Select(g => new ResumoGbDistribuicaoDto
|
||||||
|
{
|
||||||
|
Gb = g.Key,
|
||||||
|
Qtd = g.Count(),
|
||||||
|
Soma = g.Sum(x => x.FranquiaVivo ?? 0m)
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.Gb)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var gbDistribuicaoTotal = new ResumoGbDistribuicaoTotalDto
|
||||||
|
{
|
||||||
|
TotalLinhas = gbDistribuicao.Sum(x => x.Qtd ?? 0),
|
||||||
|
SomaTotal = gbDistribuicao.Sum(x => x.Soma ?? 0m)
|
||||||
|
};
|
||||||
|
|
||||||
|
var reservaSnapshot = await reservaLinesQuery
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Linha,
|
||||||
|
x.FranquiaVivo,
|
||||||
|
x.ValorContratoVivo
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var reservaGroupRaw = reservaSnapshot
|
||||||
|
.Select(row => new
|
||||||
|
{
|
||||||
|
Ddd = ExtractDddFromLine(row.Linha) ?? "-",
|
||||||
|
FranquiaGb = row.FranquiaVivo
|
||||||
|
})
|
||||||
|
.GroupBy(x => new { x.Ddd, x.FranquiaGb })
|
||||||
|
.Select(g => new ResumoReservaLineDto
|
||||||
|
{
|
||||||
|
Ddd = g.Key.Ddd,
|
||||||
|
FranquiaGb = g.Key.FranquiaGb,
|
||||||
|
QtdLinhas = g.Count(),
|
||||||
|
Total = null
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.Ddd)
|
||||||
|
.ThenBy(x => x.FranquiaGb ?? 0m)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var reservaPorDdd = reservaGroupRaw
|
||||||
|
.GroupBy(x => x.Ddd ?? "-")
|
||||||
|
.Select(g => new ResumoReservaPorDddDto
|
||||||
|
{
|
||||||
|
Ddd = g.Key,
|
||||||
|
TotalLinhas = g.Sum(x => x.QtdLinhas ?? 0),
|
||||||
|
PorFranquia = g
|
||||||
|
.GroupBy(x => x.FranquiaGb)
|
||||||
|
.Select(fg => new ResumoReservaPorFranquiaDto
|
||||||
|
{
|
||||||
|
FranquiaGb = fg.Key,
|
||||||
|
TotalLinhas = fg.Sum(x => x.QtdLinhas ?? 0)
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.FranquiaGb ?? 0m)
|
||||||
|
.ToList()
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.Ddd)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var reservaTotalLinhas = reservaSnapshot.Count;
|
||||||
|
var reservaTotalValor = reservaSnapshot.Sum(x => x.ValorContratoVivo ?? 0m);
|
||||||
|
|
||||||
|
return new ResumoResponseDto
|
||||||
|
{
|
||||||
|
MacrophonyPlans = macrophonyRows,
|
||||||
|
MacrophonyTotals = new ResumoMacrophonyTotalDto
|
||||||
|
{
|
||||||
|
TotalLinhasTotal = totalLinhasNaoReserva,
|
||||||
|
FranquiaGbTotal = totalFranquiaNaoReserva,
|
||||||
|
ValorTotal = totalValorContratoVivo
|
||||||
|
},
|
||||||
|
VivoLineResumos = clientesRows,
|
||||||
|
VivoLineTotals = new ResumoVivoLineTotalDto
|
||||||
|
{
|
||||||
|
QtdLinhasTotal = totalLinhasNaoReserva,
|
||||||
|
FranquiaTotal = totalFranquiaNaoReserva,
|
||||||
|
ValorContratoVivo = totalValorContratoVivo,
|
||||||
|
FranquiaLine = totalFranquiaLine,
|
||||||
|
ValorContratoLine = totalValorContratoLine,
|
||||||
|
Lucro = totalLucro
|
||||||
|
},
|
||||||
|
PlanoContratoResumos = planoRows,
|
||||||
|
PlanoContratoTotal = new ResumoPlanoContratoTotalDto
|
||||||
|
{
|
||||||
|
ValorTotal = planoRows.Sum(x => x.ValorTotal ?? 0m)
|
||||||
|
},
|
||||||
|
LineTotais = totaisLine,
|
||||||
|
GbDistribuicao = gbDistribuicao,
|
||||||
|
GbDistribuicaoTotal = gbDistribuicaoTotal,
|
||||||
|
ReservaLines = reservaGroupRaw,
|
||||||
|
ReservaPorDdd = reservaPorDdd,
|
||||||
|
TotalGeralLinhasReserva = reservaTotalLinhas,
|
||||||
|
ReservaTotal = new ResumoReservaTotalDto
|
||||||
|
{
|
||||||
|
QtdLinhasTotal = reservaTotalLinhas,
|
||||||
|
Total = reservaTotalValor
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal? ExtractGbFromPlanName(string? planoContrato)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(planoContrato))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var match = PlanGbRegex.Match(planoContrato);
|
||||||
|
if (!match.Success)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var normalized = match.Groups[1].Value.Replace(',', '.');
|
||||||
|
if (!decimal.TryParse(normalized, out var rawValue))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var unit = match.Groups[2].Value.ToUpperInvariant();
|
||||||
|
return unit == "MB" ? rawValue / 1000m : rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractDddFromLine(string? linha)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(linha))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var digits = new string(linha.Where(char.IsDigit).ToArray());
|
||||||
|
if (digits.Length >= 12 && digits.StartsWith("55"))
|
||||||
|
return digits.Substring(2, 2);
|
||||||
|
if (digits.Length >= 10)
|
||||||
|
return digits.Substring(0, 2);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace line_gestao_api.Controllers;
|
namespace line_gestao_api.Controllers;
|
||||||
|
|
||||||
|
|
@ -85,6 +87,27 @@ public class SystemTenantUsersController : ControllerBase
|
||||||
"unsupported_roles");
|
"unsupported_roles");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.ClientCredentialsOnly)
|
||||||
|
{
|
||||||
|
if (tenant.IsSystem)
|
||||||
|
{
|
||||||
|
return await RejectAsync(
|
||||||
|
tenantId,
|
||||||
|
StatusCodes.Status400BadRequest,
|
||||||
|
"Credenciais de cliente não podem ser criadas no SystemTenant.",
|
||||||
|
"invalid_client_credentials_on_system_tenant");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedRoles.Count != 1 || !normalizedRoles.Contains(AppRoles.Cliente, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return await RejectAsync(
|
||||||
|
tenantId,
|
||||||
|
StatusCodes.Status400BadRequest,
|
||||||
|
"Neste fluxo, somente a role cliente é permitida.",
|
||||||
|
"invalid_roles_for_client_credentials_flow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!tenant.IsSystem && normalizedRoles.Contains(SystemTenantConstants.SystemRole))
|
if (!tenant.IsSystem && normalizedRoles.Contains(SystemTenantConstants.SystemRole))
|
||||||
{
|
{
|
||||||
return await RejectAsync(
|
return await RejectAsync(
|
||||||
|
|
@ -189,6 +212,12 @@ public class SystemTenantUsersController : ControllerBase
|
||||||
return BadRequest(addRolesResult.Errors.Select(e => e.Description).ToList());
|
return BadRequest(addRolesResult.Errors.Select(e => e.Description).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var linesReassigned = 0;
|
||||||
|
if (request.ClientCredentialsOnly)
|
||||||
|
{
|
||||||
|
linesReassigned = await RebindMobileLinesToTenantBySourceKeyAsync(tenant);
|
||||||
|
}
|
||||||
|
|
||||||
await _systemAuditService.LogAsync(
|
await _systemAuditService.LogAsync(
|
||||||
action: SystemAuditActions.CreateTenantUser,
|
action: SystemAuditActions.CreateTenantUser,
|
||||||
targetTenantId: tenantId,
|
targetTenantId: tenantId,
|
||||||
|
|
@ -196,7 +225,8 @@ public class SystemTenantUsersController : ControllerBase
|
||||||
{
|
{
|
||||||
createdUserId = user.Id,
|
createdUserId = user.Id,
|
||||||
email,
|
email,
|
||||||
roles = normalizedRoles
|
roles = normalizedRoles,
|
||||||
|
linesReassigned
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = new SystemTenantUserCreatedDto
|
var response = new SystemTenantUserCreatedDto
|
||||||
|
|
@ -223,4 +253,172 @@ public class SystemTenantUsersController : ControllerBase
|
||||||
|
|
||||||
return StatusCode(statusCode, message);
|
return StatusCode(statusCode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> RebindMobileLinesToTenantBySourceKeyAsync(Tenant tenant)
|
||||||
|
{
|
||||||
|
if (tenant.Id == Guid.Empty)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(
|
||||||
|
tenant.SourceType,
|
||||||
|
SystemTenantConstants.MobileLinesClienteSourceType,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedKeys = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
AddNormalizedTenantKey(normalizedKeys, tenant.SourceKey);
|
||||||
|
AddNormalizedTenantKey(normalizedKeys, tenant.NomeOficial);
|
||||||
|
|
||||||
|
if (normalizedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates = await _db.MobileLines
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.TenantId != tenant.Id)
|
||||||
|
.Where(x => x.Cliente != null && x.Cliente != string.Empty)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasAnyTenantLine = await _db.MobileLines
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AsNoTracking()
|
||||||
|
.AnyAsync(x => x.TenantId == tenant.Id);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var reassigned = ReassignByMatcher(
|
||||||
|
candidates,
|
||||||
|
normalizedKeys,
|
||||||
|
tenant.Id,
|
||||||
|
now,
|
||||||
|
isRelaxedMatch: false);
|
||||||
|
|
||||||
|
if (reassigned == 0 && !hasAnyTenantLine)
|
||||||
|
{
|
||||||
|
reassigned = ReassignByMatcher(
|
||||||
|
candidates,
|
||||||
|
normalizedKeys,
|
||||||
|
tenant.Id,
|
||||||
|
now,
|
||||||
|
isRelaxedMatch: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reassigned > 0)
|
||||||
|
{
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return reassigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReassignByMatcher(
|
||||||
|
IReadOnlyList<MobileLine> candidates,
|
||||||
|
IReadOnlyCollection<string> normalizedKeys,
|
||||||
|
Guid tenantId,
|
||||||
|
DateTime now,
|
||||||
|
bool isRelaxedMatch)
|
||||||
|
{
|
||||||
|
if (normalizedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = isRelaxedMatch
|
||||||
|
? normalizedKeys.Where(k => k.Length >= 6).ToList()
|
||||||
|
: normalizedKeys.ToList();
|
||||||
|
|
||||||
|
if (keys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reassigned = 0;
|
||||||
|
|
||||||
|
foreach (var line in candidates)
|
||||||
|
{
|
||||||
|
var normalizedClient = NormalizeTenantKey(line.Cliente ?? string.Empty);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedClient) ||
|
||||||
|
string.Equals(normalizedClient, "RESERVA", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = !isRelaxedMatch
|
||||||
|
? keys.Contains(normalizedClient, StringComparer.Ordinal)
|
||||||
|
: keys.Any(k =>
|
||||||
|
normalizedClient.Contains(k, StringComparison.Ordinal) ||
|
||||||
|
k.Contains(normalizedClient, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.TenantId = tenantId;
|
||||||
|
line.UpdatedAt = now;
|
||||||
|
reassigned++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reassigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNormalizedTenantKey(ISet<string> keys, string? rawKey)
|
||||||
|
{
|
||||||
|
var normalized = NormalizeTenantKey(rawKey ?? string.Empty);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalized))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(normalized, "RESERVA", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.Add(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeTenantKey(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = value.Trim().Normalize(NormalizationForm.FormD);
|
||||||
|
var sb = new StringBuilder(normalized.Length);
|
||||||
|
var previousWasSpace = false;
|
||||||
|
foreach (var ch in normalized)
|
||||||
|
{
|
||||||
|
var category = CharUnicodeInfo.GetUnicodeCategory(ch);
|
||||||
|
if (category == UnicodeCategory.NonSpacingMark)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(ch))
|
||||||
|
{
|
||||||
|
if (!previousWasSpace)
|
||||||
|
{
|
||||||
|
sb.Append(' ');
|
||||||
|
previousWasSpace = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(char.ToUpperInvariant(ch));
|
||||||
|
previousWasSpace = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString().Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,9 @@ public class UsersController : ControllerBase
|
||||||
page = page < 1 ? 1 : page;
|
page = page < 1 ? 1 : page;
|
||||||
pageSize = pageSize < 1 ? 20 : pageSize;
|
pageSize = pageSize < 1 ? 20 : pageSize;
|
||||||
|
|
||||||
var usersQuery = _userManager.Users.AsNoTracking();
|
var usersQuery = _userManager.Users
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AsNoTracking();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
{
|
{
|
||||||
|
|
@ -195,7 +197,10 @@ public class UsersController : ControllerBase
|
||||||
[Authorize(Roles = "sysadmin")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<ActionResult<UserListItemDto>> GetById(Guid id)
|
public async Task<ActionResult<UserListItemDto>> GetById(Guid id)
|
||||||
{
|
{
|
||||||
var user = await _userManager.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Id == id);
|
var user = await _userManager.Users
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == id);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -225,7 +230,9 @@ public class UsersController : ControllerBase
|
||||||
return BadRequest(new ValidationErrorResponse { Errors = errors });
|
return BadRequest(new ValidationErrorResponse { Errors = errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _userManager.Users.FirstOrDefaultAsync(u => u.Id == id);
|
var user = await _userManager.Users
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == id);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -299,11 +306,6 @@ public class UsersController : ControllerBase
|
||||||
[Authorize(Roles = "sysadmin")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<IActionResult> Delete(Guid id)
|
public async Task<IActionResult> Delete(Guid id)
|
||||||
{
|
{
|
||||||
if (_tenantProvider.TenantId == null)
|
|
||||||
{
|
|
||||||
return Unauthorized();
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentUserId = GetCurrentUserId();
|
var currentUserId = GetCurrentUserId();
|
||||||
if (currentUserId.HasValue && currentUserId.Value == id)
|
if (currentUserId.HasValue && currentUserId.Value == id)
|
||||||
{
|
{
|
||||||
|
|
@ -316,12 +318,14 @@ public class UsersController : ControllerBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = _tenantProvider.TenantId.Value;
|
var user = await _userManager.Users
|
||||||
var user = await _userManager.Users.FirstOrDefaultAsync(u => u.Id == id && u.TenantId == tenantId);
|
.IgnoreQueryFilters()
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == id);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
var tenantId = user.TenantId;
|
||||||
|
|
||||||
if (user.IsActive)
|
if (user.IsActive)
|
||||||
{
|
{
|
||||||
|
|
@ -423,6 +427,10 @@ public class UsersController : ControllerBase
|
||||||
private async Task<List<ValidationErrorDto>> ValidateUpdateAsync(Guid userId, UserUpdateRequest req)
|
private async Task<List<ValidationErrorDto>> ValidateUpdateAsync(Guid userId, UserUpdateRequest req)
|
||||||
{
|
{
|
||||||
var errors = new List<ValidationErrorDto>();
|
var errors = new List<ValidationErrorDto>();
|
||||||
|
var targetUser = await _userManager.Users
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(req.Nome) && req.Nome.Trim().Length < 2)
|
if (!string.IsNullOrWhiteSpace(req.Nome) && req.Nome.Trim().Length < 2)
|
||||||
{
|
{
|
||||||
|
|
@ -434,14 +442,15 @@ public class UsersController : ControllerBase
|
||||||
var email = req.Email.Trim().ToLowerInvariant();
|
var email = req.Email.Trim().ToLowerInvariant();
|
||||||
var normalized = _userManager.NormalizeEmail(email);
|
var normalized = _userManager.NormalizeEmail(email);
|
||||||
|
|
||||||
var tenantId = _tenantProvider.TenantId;
|
if (targetUser == null)
|
||||||
if (tenantId == null)
|
|
||||||
{
|
{
|
||||||
errors.Add(new ValidationErrorDto { Field = "email", Message = "Tenant inválido." });
|
return errors;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
var tenantId = targetUser.TenantId;
|
||||||
var exists = await _userManager.Users.AnyAsync(u =>
|
var exists = await _userManager.Users
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.AnyAsync(u =>
|
||||||
u.Id != userId &&
|
u.Id != userId &&
|
||||||
u.TenantId == tenantId &&
|
u.TenantId == tenantId &&
|
||||||
u.NormalizedEmail == normalized);
|
u.NormalizedEmail == normalized);
|
||||||
|
|
@ -451,7 +460,6 @@ public class UsersController : ControllerBase
|
||||||
errors.Add(new ValidationErrorDto { Field = "email", Message = "E-mail já cadastrado." });
|
errors.Add(new ValidationErrorDto { Field = "email", Message = "E-mail já cadastrado." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(req.Senha))
|
if (!string.IsNullOrWhiteSpace(req.Senha))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
public string? Skil { get; set; }
|
public string? Skil { get; set; }
|
||||||
public string? Modalidade { get; set; }
|
public string? Modalidade { get; set; }
|
||||||
public string? VencConta { get; set; }
|
public string? VencConta { get; set; }
|
||||||
|
public decimal? FranquiaLine { get; set; }
|
||||||
|
|
||||||
// Campos para filtro deterministico de adicionais no frontend
|
// Campos para filtro deterministico de adicionais no frontend
|
||||||
public decimal? GestaoVozDados { get; set; }
|
public decimal? GestaoVozDados { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public class CreateSystemTenantUserRequest
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
public string Password { get; set; } = string.Empty;
|
public string Password { get; set; } = string.Empty;
|
||||||
public List<string> Roles { get; set; } = new();
|
public List<string> Roles { get; set; } = new();
|
||||||
|
public bool ClientCredentialsOnly { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SystemTenantUserCreatedDto
|
public class SystemTenantUserCreatedDto
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue