Compare commits

..

2 Commits

Author SHA1 Message Date
Eduardo Lopes 355267b740 fix: ajustar tenant/cliente no cadastro de linhas e reserva 2026-03-03 14:45:39 -03:00
Eduardo 0d23967fea Feat: Deploy Alterações Logo 2026-03-03 13:10:20 -03:00
3 changed files with 262 additions and 62 deletions

View File

@ -719,77 +719,83 @@ namespace line_gestao_api.Controllers
if (string.IsNullOrWhiteSpace(linhaLimpa)) if (string.IsNullOrWhiteSpace(linhaLimpa))
return BadRequest(new { message = "Número de linha inválido." }); return BadRequest(new { message = "Número de linha inválido." });
var exists = await _db.MobileLines.AsNoTracking().AnyAsync(x => x.Linha == linhaLimpa); MobileLine? lineToPersist = null;
if (exists)
if (req.ReservaLineId.HasValue && req.ReservaLineId.Value != Guid.Empty)
{
lineToPersist = await _db.MobileLines.FirstOrDefaultAsync(x => x.Id == req.ReservaLineId.Value);
if (lineToPersist == null)
return BadRequest(new { message = "A linha selecionada na Reserva não foi encontrada." });
if (!IsReservaLineForTransfer(lineToPersist))
return Conflict(new { message = "A linha selecionada não está mais disponível na Reserva." });
if (!string.IsNullOrWhiteSpace(lineToPersist.Linha) &&
!string.Equals(lineToPersist.Linha, linhaLimpa, StringComparison.Ordinal))
{
return BadRequest(new { message = "A linha selecionada na Reserva não corresponde ao número informado." });
}
}
if (lineToPersist == null)
{
var existingByLinha = await _db.MobileLines.FirstOrDefaultAsync(x => x.Linha == linhaLimpa);
if (existingByLinha != null)
{
if (IsReservaLineForTransfer(existingByLinha))
{
lineToPersist = existingByLinha;
}
else
{
return Conflict(new { message = $"A linha {req.Linha} já está cadastrada no sistema." }); return Conflict(new { message = $"A linha {req.Linha} já está cadastrada no sistema." });
}
}
}
if (!string.IsNullOrWhiteSpace(chipLimpo)) if (!string.IsNullOrWhiteSpace(chipLimpo))
{ {
var chipExists = await _db.MobileLines.AsNoTracking().AnyAsync(x => x.Chip == chipLimpo); var chipExists = await _db.MobileLines.AsNoTracking()
.AnyAsync(x => x.Chip == chipLimpo && (lineToPersist == null || x.Id != lineToPersist.Id));
if (chipExists) if (chipExists)
return Conflict(new { message = $"O Chip (ICCID) {req.Chip} já está cadastrado no sistema." }); return Conflict(new { message = $"O Chip (ICCID) {req.Chip} já está cadastrado no sistema." });
} }
var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
var nextItem = maxItem + 1;
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var planSuggestion = await AutoFillRules.ResolvePlanSuggestionAsync(_db, req.PlanoContrato); var planSuggestion = await AutoFillRules.ResolvePlanSuggestionAsync(_db, req.PlanoContrato);
var franquiaVivo = req.FranquiaVivo ?? planSuggestion?.FranquiaGb; var franquiaVivo = req.FranquiaVivo ?? planSuggestion?.FranquiaGb;
var valorPlanoVivo = req.ValorPlanoVivo ?? planSuggestion?.ValorPlano; var valorPlanoVivo = req.ValorPlanoVivo ?? planSuggestion?.ValorPlano;
var newLine = new MobileLine var isReassignmentFromReserva = lineToPersist != null;
if (!isReassignmentFromReserva)
{
var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
lineToPersist = new MobileLine
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = nextItem, Item = maxItem + 1,
Cliente = req.Cliente.Trim().ToUpper(), CreatedAt = now
Linha = linhaLimpa,
Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
Usuario = req.Usuario?.Trim(),
Status = req.Status?.Trim(),
Skil = req.Skil?.Trim(),
Modalidade = req.Modalidade?.Trim(),
PlanoContrato = req.PlanoContrato?.Trim(),
Conta = req.Conta?.Trim(),
VencConta = req.VencConta?.Trim(),
DataBloqueio = ToUtc(req.DataBloqueio),
DataEntregaOpera = ToUtc(req.DataEntregaOpera),
DataEntregaCliente = ToUtc(req.DataEntregaCliente),
Cedente = req.Cedente?.Trim(),
Solicitante = req.Solicitante?.Trim(),
FranquiaVivo = franquiaVivo,
ValorPlanoVivo = valorPlanoVivo,
GestaoVozDados = req.GestaoVozDados,
Skeelo = req.Skeelo,
VivoNewsPlus = req.VivoNewsPlus,
VivoTravelMundo = req.VivoTravelMundo,
VivoSync = req.VivoSync,
VivoGestaoDispositivo = req.VivoGestaoDispositivo,
ValorContratoVivo = req.ValorContratoVivo,
FranquiaLine = req.FranquiaLine,
FranquiaGestao = req.FranquiaGestao,
LocacaoAp = req.LocacaoAp,
ValorContratoLine = req.ValorContratoLine,
Desconto = req.Desconto,
Lucro = req.Lucro,
TipoDeChip = req.TipoDeChip?.Trim(),
CreatedAt = now,
UpdatedAt = now
}; };
_db.MobileLines.Add(lineToPersist);
}
ApplyReservaRule(newLine); var previousLinha = lineToPersist!.Linha;
ApplyCreateRequestToLine(lineToPersist, req, linhaLimpa, chipLimpo, franquiaVivo, valorPlanoVivo, now);
ApplyReservaRule(lineToPersist);
var ensuredTenant = await EnsureTenantForClientAsync(lineToPersist.Cliente);
if (ensuredTenant != null)
{
lineToPersist.TenantId = ensuredTenant.Id;
lineToPersist.Cliente = ensuredTenant.NomeOficial;
}
_db.MobileLines.Add(newLine);
var vigencia = await UpsertVigenciaFromMobileLineAsync( var vigencia = await UpsertVigenciaFromMobileLineAsync(
newLine, lineToPersist,
req.DtEfetivacaoServico, req.DtEfetivacaoServico,
req.DtTerminoFidelizacao, req.DtTerminoFidelizacao,
overrideDates: false); overrideDates: false,
previousLinha: isReassignmentFromReserva ? previousLinha : null);
try try
{ {
@ -801,7 +807,12 @@ namespace line_gestao_api.Controllers
return StatusCode(500, new { message = "Erro ao salvar no banco de dados." }); return StatusCode(500, new { message = "Erro ao salvar no banco de dados." });
} }
return CreatedAtAction(nameof(GetById), new { id = newLine.Id }, ToDetailDto(newLine, vigencia)); if (isReassignmentFromReserva)
{
return Ok(ToDetailDto(lineToPersist, vigencia));
}
return CreatedAtAction(nameof(GetById), new { id = lineToPersist.Id }, ToDetailDto(lineToPersist, vigencia));
} }
// ========================================================== // ==========================================================
@ -853,6 +864,7 @@ namespace line_gestao_api.Controllers
var nextItem = (await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0); var nextItem = (await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0);
var seenBatchLinhas = new HashSet<string>(StringComparer.Ordinal); var seenBatchLinhas = new HashSet<string>(StringComparer.Ordinal);
var seenBatchChips = new HashSet<string>(StringComparer.Ordinal); var seenBatchChips = new HashSet<string>(StringComparer.Ordinal);
var tenantCache = new Dictionary<string, Tenant?>(StringComparer.Ordinal);
var createdLines = new List<(MobileLine line, VigenciaLine? vigencia)>(requests.Count); var createdLines = new List<(MobileLine line, VigenciaLine? vigencia)>(requests.Count);
for (var i = 0; i < requests.Count; i++) for (var i = 0; i < requests.Count; i++)
@ -907,7 +919,7 @@ namespace line_gestao_api.Controllers
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Item = nextItem, Item = nextItem,
Cliente = entry.Cliente.Trim().ToUpper(), Cliente = entry.Cliente.Trim(),
Linha = linhaLimpa, Linha = linhaLimpa,
Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo, Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
Usuario = entry.Usuario?.Trim(), Usuario = entry.Usuario?.Trim(),
@ -948,6 +960,19 @@ namespace line_gestao_api.Controllers
ApplyReservaRule(newLine); ApplyReservaRule(newLine);
var tenantCacheKey = NormalizeTenantKeyValue(newLine.Cliente ?? string.Empty);
if (!tenantCache.TryGetValue(tenantCacheKey, out var ensuredTenant))
{
ensuredTenant = await EnsureTenantForClientAsync(newLine.Cliente);
tenantCache[tenantCacheKey] = ensuredTenant;
}
if (ensuredTenant != null)
{
newLine.TenantId = ensuredTenant.Id;
newLine.Cliente = ensuredTenant.NomeOficial;
}
_db.MobileLines.Add(newLine); _db.MobileLines.Add(newLine);
var vigencia = await UpsertVigenciaFromMobileLineAsync( var vigencia = await UpsertVigenciaFromMobileLineAsync(
@ -1041,7 +1066,8 @@ namespace line_gestao_api.Controllers
var usuarioDestino = string.IsNullOrWhiteSpace(req?.UsuarioDestino) ? null : req!.UsuarioDestino!.Trim(); var usuarioDestino = string.IsNullOrWhiteSpace(req?.UsuarioDestino) ? null : req!.UsuarioDestino!.Trim();
var skilDestinoSolicitado = string.IsNullOrWhiteSpace(req?.SkilDestino) ? null : req!.SkilDestino!.Trim(); var skilDestinoSolicitado = string.IsNullOrWhiteSpace(req?.SkilDestino) ? null : req!.SkilDestino!.Trim();
var clienteDestinoUpper = clienteDestino.ToUpperInvariant(); var ensuredTenantDestino = await EnsureTenantForClientAsync(clienteDestino);
var clienteDestinoOficial = ensuredTenantDestino?.NomeOficial ?? clienteDestino;
var linhas = await _db.MobileLines var linhas = await _db.MobileLines
.Where(x => ids.Contains(x.Id)) .Where(x => ids.Contains(x.Id))
@ -1094,7 +1120,11 @@ namespace line_gestao_api.Controllers
} }
var clienteAnterior = line.Cliente; var clienteAnterior = line.Cliente;
line.Cliente = clienteDestinoUpper; line.Cliente = clienteDestinoOficial;
if (ensuredTenantDestino != null)
{
line.TenantId = ensuredTenantDestino.Id;
}
if (IsReservaValue(line.Usuario)) if (IsReservaValue(line.Usuario))
{ {
@ -1334,6 +1364,12 @@ namespace line_gestao_api.Controllers
} }
ApplyReservaRule(x); ApplyReservaRule(x);
var ensuredTenant = await EnsureTenantForClientAsync(x.Cliente);
if (ensuredTenant != null)
{
x.TenantId = ensuredTenant.Id;
x.Cliente = ensuredTenant.NomeOficial;
}
await UpsertVigenciaFromMobileLineAsync( await UpsertVigenciaFromMobileLineAsync(
x, x,
@ -4049,6 +4085,167 @@ namespace line_gestao_api.Controllers
|| IsReservaValue(line.Cliente); || IsReservaValue(line.Cliente);
} }
private static void ApplyCreateRequestToLine(
MobileLine line,
CreateMobileLineDto req,
string linhaLimpa,
string? chipLimpo,
decimal? franquiaVivo,
decimal? valorPlanoVivo,
DateTime now)
{
line.Cliente = req.Cliente?.Trim();
line.Linha = linhaLimpa;
line.Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo;
line.Usuario = req.Usuario?.Trim();
line.Status = req.Status?.Trim();
line.Skil = req.Skil?.Trim();
line.Modalidade = req.Modalidade?.Trim();
line.PlanoContrato = req.PlanoContrato?.Trim();
line.Conta = req.Conta?.Trim();
line.VencConta = req.VencConta?.Trim();
line.DataBloqueio = ToUtc(req.DataBloqueio);
line.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
line.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
line.Cedente = req.Cedente?.Trim();
line.Solicitante = req.Solicitante?.Trim();
line.FranquiaVivo = franquiaVivo;
line.ValorPlanoVivo = valorPlanoVivo;
line.GestaoVozDados = req.GestaoVozDados;
line.Skeelo = req.Skeelo;
line.VivoNewsPlus = req.VivoNewsPlus;
line.VivoTravelMundo = req.VivoTravelMundo;
line.VivoSync = req.VivoSync;
line.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
line.ValorContratoVivo = req.ValorContratoVivo;
line.FranquiaLine = req.FranquiaLine;
line.FranquiaGestao = req.FranquiaGestao;
line.LocacaoAp = req.LocacaoAp;
line.ValorContratoLine = req.ValorContratoLine;
line.Desconto = req.Desconto;
line.Lucro = req.Lucro;
line.TipoDeChip = req.TipoDeChip?.Trim();
line.UpdatedAt = now;
}
private async Task<Tenant?> EnsureTenantForClientAsync(string? rawClientName)
{
var clientName = (rawClientName ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(clientName) || IsReservaValue(clientName))
{
return null;
}
var normalizedClient = NormalizeTenantKeyValue(clientName);
if (string.IsNullOrWhiteSpace(normalizedClient) ||
string.Equals(normalizedClient, "RESERVA", StringComparison.Ordinal))
{
return null;
}
var existingCandidates = await _db.Tenants
.IgnoreQueryFilters()
.Where(t => !t.IsSystem && t.SourceType == SystemTenantConstants.MobileLinesClienteSourceType)
.ToListAsync();
var tenant = existingCandidates.FirstOrDefault(t =>
string.Equals(NormalizeTenantKeyValue(t.SourceKey ?? string.Empty), normalizedClient, StringComparison.Ordinal) ||
string.Equals(NormalizeTenantKeyValue(t.NomeOficial ?? string.Empty), normalizedClient, StringComparison.Ordinal));
if (tenant != null)
{
var changed = false;
if (!tenant.Ativo)
{
tenant.Ativo = true;
changed = true;
}
if (string.IsNullOrWhiteSpace(tenant.NomeOficial))
{
tenant.NomeOficial = clientName;
changed = true;
}
if (string.IsNullOrWhiteSpace(tenant.SourceKey))
{
tenant.SourceKey = clientName;
changed = true;
}
if (!string.Equals(tenant.SourceType, SystemTenantConstants.MobileLinesClienteSourceType, StringComparison.OrdinalIgnoreCase))
{
tenant.SourceType = SystemTenantConstants.MobileLinesClienteSourceType;
changed = true;
}
if (changed)
{
tenant.NomeOficial = tenant.NomeOficial.Trim();
tenant.SourceKey = tenant.SourceKey?.Trim();
}
return tenant;
}
var deterministicId = DeterministicGuid.FromString($"{SystemTenantConstants.MobileLinesClienteSourceType}:{normalizedClient}");
var idAlreadyExists = await _db.Tenants.IgnoreQueryFilters().AnyAsync(t => t.Id == deterministicId);
tenant = new Tenant
{
Id = idAlreadyExists ? Guid.NewGuid() : deterministicId,
NomeOficial = clientName,
IsSystem = false,
Ativo = true,
SourceType = SystemTenantConstants.MobileLinesClienteSourceType,
SourceKey = clientName,
CreatedAt = DateTime.UtcNow
};
_db.Tenants.Add(tenant);
return tenant;
}
private static string NormalizeTenantKeyValue(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();
}
// ========================================================== // ==========================================================
// HELPERS (SEUS) // HELPERS (SEUS)
// ========================================================== // ==========================================================

View File

@ -35,12 +35,8 @@ public static class SeedData
await db.Database.EnsureCreatedAsync(); await db.Database.EnsureCreatedAsync();
} }
if (!options.Enabled) // Mantem o contrato de roles atualizado em todos os ambientes, inclusive
{ // quando o seed de usuario master estiver desabilitado.
return;
}
var systemTenantId = SystemTenantConstants.SystemTenantId;
var roles = AppRoles.All; var roles = AppRoles.All;
foreach (var role in roles) foreach (var role in roles)
{ {
@ -53,6 +49,12 @@ public static class SeedData
await MigrateLegacyRolesAsync(db, roleManager); await MigrateLegacyRolesAsync(db, roleManager);
if (!options.Enabled)
{
return;
}
var systemTenantId = SystemTenantConstants.SystemTenantId;
var systemTenant = await db.Tenants.FirstOrDefaultAsync(t => t.Id == systemTenantId); var systemTenant = await db.Tenants.FirstOrDefaultAsync(t => t.Id == systemTenantId);
if (systemTenant == null) if (systemTenant == null)
{ {

View File

@ -12,6 +12,7 @@ namespace line_gestao_api.Dtos
public string? Chip { get; set; } // ICCID public string? Chip { get; set; } // ICCID
public string? Cliente { get; set; } // Obrigatório na validação do Controller public string? Cliente { get; set; } // Obrigatório na validação do Controller
public string? Usuario { get; set; } public string? Usuario { get; set; }
public Guid? ReservaLineId { get; set; } // Reaproveita linha já existente na Reserva
// ========================== // ==========================
// Classificação e Status // Classificação e Status