Support per-line data for bulk creation

This commit is contained in:
Eduardo Lopes 2026-01-30 12:16:27 -03:00
parent bb6f1fc044
commit f78446b361
2 changed files with 222 additions and 67 deletions

View File

@ -350,78 +350,174 @@ namespace line_gestao_api.Controllers
if (string.IsNullOrWhiteSpace(req.Cliente)) if (string.IsNullOrWhiteSpace(req.Cliente))
return BadRequest(new { message = "O nome do Cliente é obrigatório." }); return BadRequest(new { message = "O nome do Cliente é obrigatório." });
var quantidade = req.QtdLinhas.GetValueOrDefault(1); if (req.Linhas != null && req.Linhas.Count > 0)
if (quantidade < 1) {
return BadRequest(new { message = "A quantidade de linhas deve ser maior que zero." }); var itens = req.Linhas;
var linhasNormalizadas = new List<string>(itens.Count);
for (var i = 0; i < itens.Count; i++)
{
var linhaOriginal = itens[i].Linha;
if (string.IsNullOrWhiteSpace(linhaOriginal))
return BadRequest(new { message = $"O número da Linha é obrigatório (linha {i + 1})." });
var linhaLimpa = OnlyDigits(linhaOriginal);
if (string.IsNullOrWhiteSpace(linhaLimpa))
return BadRequest(new { message = $"Número de linha inválido (linha {i + 1})." });
linhasNormalizadas.Add(linhaLimpa);
}
var duplicadas = linhasNormalizadas
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
if (duplicadas.Count > 0)
return Conflict(new { message = $"Existem linhas duplicadas na requisição: {string.Join(", ", duplicadas)}." });
var existentes = await _db.MobileLines
.AsNoTracking()
.Where(x => x.Linha != null && linhasNormalizadas.Contains(x.Linha))
.Select(x => x.Linha!)
.ToListAsync();
if (existentes.Count > 0)
return Conflict(new { message = $"As linhas {string.Join(", ", existentes)} já estão cadastradas no sistema." });
var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
var now = DateTime.UtcNow;
var created = new List<MobileLine>(itens.Count);
for (var i = 0; i < itens.Count; i++)
{
var item = itens[i];
var linhaLimpa = linhasNormalizadas[i];
var chipLimpo = OnlyDigits(item.Chip);
var newLine = new MobileLine
{
Id = Guid.NewGuid(),
Item = maxItem + 1 + i,
Cliente = req.Cliente.Trim().ToUpper(),
Linha = linhaLimpa,
Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
Usuario = item.Usuario?.Trim(),
Status = item.Status?.Trim(),
Skil = item.Skil?.Trim(),
Modalidade = item.Modalidade?.Trim(),
PlanoContrato = item.PlanoContrato?.Trim(),
Conta = item.Conta?.Trim(),
VencConta = item.VencConta?.Trim(),
DataBloqueio = ToUtc(item.DataBloqueio),
DataEntregaOpera = ToUtc(item.DataEntregaOpera),
DataEntregaCliente = ToUtc(item.DataEntregaCliente),
Cedente = item.Cedente?.Trim(),
Solicitante = item.Solicitante?.Trim(),
FranquiaVivo = item.FranquiaVivo,
ValorPlanoVivo = item.ValorPlanoVivo,
GestaoVozDados = item.GestaoVozDados,
Skeelo = item.Skeelo,
VivoNewsPlus = item.VivoNewsPlus,
VivoTravelMundo = item.VivoTravelMundo,
VivoGestaoDispositivo = item.VivoGestaoDispositivo,
ValorContratoVivo = item.ValorContratoVivo,
FranquiaLine = item.FranquiaLine,
FranquiaGestao = item.FranquiaGestao,
LocacaoAp = item.LocacaoAp,
ValorContratoLine = item.ValorContratoLine,
Desconto = item.Desconto,
Lucro = item.Lucro,
CreatedAt = now,
UpdatedAt = now
};
ApplyReservaRule(newLine);
created.Add(newLine);
}
_db.MobileLines.AddRange(created);
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateException)
{
return StatusCode(500, new { message = "Erro ao salvar no banco de dados." });
}
return StatusCode(StatusCodes.Status201Created, created.Select(ToDetailDto).ToList());
}
if (req.QtdLinhas.GetValueOrDefault(1) > 1)
return BadRequest(new { message = "Para criar múltiplas linhas, envie a lista em 'Linhas' com os dados de cada linha." });
if (string.IsNullOrWhiteSpace(req.Linha))
return BadRequest(new { message = "O número da Linha é obrigatório." });
var linhaLimpa = OnlyDigits(req.Linha); var linhaLimpa = OnlyDigits(req.Linha);
var chipLimpo = OnlyDigits(req.Chip); var chipLimpo = OnlyDigits(req.Chip);
if (quantidade == 1 && string.IsNullOrWhiteSpace(req.Linha)) if (string.IsNullOrWhiteSpace(linhaLimpa))
return BadRequest(new { message = "O número da Linha é obrigatório." });
if (!string.IsNullOrWhiteSpace(req.Linha) && string.IsNullOrWhiteSpace(linhaLimpa))
return BadRequest(new { message = "Número de linha inválido." }); return BadRequest(new { message = "Número de linha inválido." });
if (!string.IsNullOrWhiteSpace(linhaLimpa)) var exists = await _db.MobileLines.AsNoTracking().AnyAsync(x => x.Linha == linhaLimpa);
if (exists)
return Conflict(new { message = $"A linha {req.Linha} já está cadastrada no sistema." });
var maxItemSingle = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
var nowSingle = DateTime.UtcNow;
var newLineSingle = new MobileLine
{ {
var exists = await _db.MobileLines.AsNoTracking().AnyAsync(x => x.Linha == linhaLimpa); Id = Guid.NewGuid(),
if (exists) Item = maxItemSingle + 1,
return Conflict(new { message = $"A linha {req.Linha} já está cadastrada no sistema." }); Cliente = req.Cliente.Trim().ToUpper(),
} 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(),
var maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0; DataBloqueio = ToUtc(req.DataBloqueio),
var now = DateTime.UtcNow; DataEntregaOpera = ToUtc(req.DataEntregaOpera),
var created = new List<MobileLine>(quantidade); DataEntregaCliente = ToUtc(req.DataEntregaCliente),
for (var i = 0; i < quantidade; i++) Cedente = req.Cedente?.Trim(),
{ Solicitante = req.Solicitante?.Trim(),
var newLine = new MobileLine
{
Id = Guid.NewGuid(),
Item = maxItem + 1 + i,
Cliente = req.Cliente.Trim().ToUpper(),
Linha = i == 0 ? linhaLimpa : null,
Chip = i == 0 && !string.IsNullOrWhiteSpace(chipLimpo) ? chipLimpo : null,
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), FranquiaVivo = req.FranquiaVivo,
DataEntregaOpera = ToUtc(req.DataEntregaOpera), ValorPlanoVivo = req.ValorPlanoVivo,
DataEntregaCliente = ToUtc(req.DataEntregaCliente), GestaoVozDados = req.GestaoVozDados,
Skeelo = req.Skeelo,
VivoNewsPlus = req.VivoNewsPlus,
VivoTravelMundo = req.VivoTravelMundo,
VivoGestaoDispositivo = req.VivoGestaoDispositivo,
ValorContratoVivo = req.ValorContratoVivo,
FranquiaLine = req.FranquiaLine,
FranquiaGestao = req.FranquiaGestao,
LocacaoAp = req.LocacaoAp,
ValorContratoLine = req.ValorContratoLine,
Desconto = req.Desconto,
Lucro = req.Lucro,
Cedente = req.Cedente?.Trim(), CreatedAt = nowSingle,
Solicitante = req.Solicitante?.Trim(), UpdatedAt = nowSingle
};
FranquiaVivo = req.FranquiaVivo, ApplyReservaRule(newLineSingle);
ValorPlanoVivo = req.ValorPlanoVivo,
GestaoVozDados = req.GestaoVozDados,
Skeelo = req.Skeelo,
VivoNewsPlus = req.VivoNewsPlus,
VivoTravelMundo = req.VivoTravelMundo,
VivoGestaoDispositivo = req.VivoGestaoDispositivo,
ValorContratoVivo = req.ValorContratoVivo,
FranquiaLine = req.FranquiaLine,
FranquiaGestao = req.FranquiaGestao,
LocacaoAp = req.LocacaoAp,
ValorContratoLine = req.ValorContratoLine,
Desconto = req.Desconto,
Lucro = req.Lucro,
CreatedAt = now, _db.MobileLines.Add(newLineSingle);
UpdatedAt = now
};
ApplyReservaRule(newLine);
created.Add(newLine);
}
_db.MobileLines.AddRange(created);
try try
{ {
@ -432,13 +528,7 @@ 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." });
} }
if (created.Count == 1) return CreatedAtAction(nameof(GetById), new { id = newLineSingle.Id }, ToDetailDto(newLineSingle));
{
var newLine = created[0];
return CreatedAtAction(nameof(GetById), new { id = newLine.Id }, ToDetailDto(newLine));
}
return StatusCode(StatusCodes.Status201Created, created.Select(ToDetailDto).ToList());
} }
// ========================================================== // ==========================================================

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace line_gestao_api.Dtos namespace line_gestao_api.Dtos
{ {
@ -12,7 +13,8 @@ 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 int? QtdLinhas { get; set; } // Quantidade de linhas a serem criadas public int? QtdLinhas { get; set; } // Quantidade de linhas (uso legado)
public List<CreateMobileLineItemDto>? Linhas { get; set; } // Linhas individuais para criação em lote
// ========================== // ==========================
// Classificação e Status // Classificação e Status
@ -67,4 +69,67 @@ namespace line_gestao_api.Dtos
public decimal? Desconto { get; set; } public decimal? Desconto { get; set; }
public decimal? Lucro { get; set; } public decimal? Lucro { get; set; }
} }
public class CreateMobileLineItemDto
{
// ==========================
// Identificação Básica
// ==========================
public string? Linha { get; set; }
public string? Chip { get; set; }
public string? Usuario { get; set; }
// ==========================
// Classificação e Status
// ==========================
public string? Status { get; set; }
public string? Skil { get; set; }
public string? Modalidade { get; set; }
// ==========================
// Dados Contratuais
// ==========================
public string? PlanoContrato { get; set; }
public string? Conta { get; set; }
public string? VencConta { get; set; }
// ==========================
// Datas Importantes
// ==========================
public DateTime? DataBloqueio { get; set; }
public DateTime? DataEntregaOpera { get; set; }
public DateTime? DataEntregaCliente { get; set; }
// ==========================
// Responsáveis / Logística
// ==========================
public string? Cedente { get; set; }
public string? Solicitante { get; set; }
// ==========================
// Financeiro - Vivo
// ==========================
public decimal? FranquiaVivo { get; set; }
public decimal? ValorPlanoVivo { get; set; }
public decimal? GestaoVozDados { get; set; }
public decimal? Skeelo { get; set; }
public decimal? VivoNewsPlus { get; set; }
public decimal? VivoTravelMundo { get; set; }
public decimal? VivoGestaoDispositivo { get; set; }
public decimal? ValorContratoVivo { get; set; }
// ==========================
// Financeiro - Line Móvel
// ==========================
public decimal? FranquiaLine { get; set; }
public decimal? FranquiaGestao { get; set; }
public decimal? LocacaoAp { get; set; }
public decimal? ValorContratoLine { get; set; }
// ==========================
// Resultado Financeiro
// ==========================
public decimal? Desconto { get; set; }
public decimal? Lucro { get; set; }
}
} }