From f78446b36113460e1476d0adfced6e20c6f614d8 Mon Sep 17 00:00:00 2001 From: Eduardo Lopes <155753879+eduardolopesx03@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:16:27 -0300 Subject: [PATCH] Support per-line data for bulk creation --- Controllers/LinesController.cs | 222 +++++++++++++++++++++++---------- Dtos/CreateMobileLineDto.cs | 67 +++++++++- 2 files changed, 222 insertions(+), 67 deletions(-) diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index fcea6e7..c6b39cb 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -350,78 +350,174 @@ namespace line_gestao_api.Controllers if (string.IsNullOrWhiteSpace(req.Cliente)) return BadRequest(new { message = "O nome do Cliente é obrigatório." }); - var quantidade = req.QtdLinhas.GetValueOrDefault(1); - if (quantidade < 1) - return BadRequest(new { message = "A quantidade de linhas deve ser maior que zero." }); + if (req.Linhas != null && req.Linhas.Count > 0) + { + var itens = req.Linhas; + var linhasNormalizadas = new List(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(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 chipLimpo = OnlyDigits(req.Chip); - if (quantidade == 1 && string.IsNullOrWhiteSpace(req.Linha)) - return BadRequest(new { message = "O número da Linha é obrigatório." }); - - if (!string.IsNullOrWhiteSpace(req.Linha) && string.IsNullOrWhiteSpace(linhaLimpa)) + if (string.IsNullOrWhiteSpace(linhaLimpa)) 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); - if (exists) - return Conflict(new { message = $"A linha {req.Linha} já está cadastrada no sistema." }); - } + Id = Guid.NewGuid(), + Item = maxItemSingle + 1, + 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; - var now = DateTime.UtcNow; - var created = new List(quantidade); + DataBloqueio = ToUtc(req.DataBloqueio), + DataEntregaOpera = ToUtc(req.DataEntregaOpera), + DataEntregaCliente = ToUtc(req.DataEntregaCliente), - for (var i = 0; i < quantidade; i++) - { - 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(), + Cedente = req.Cedente?.Trim(), + Solicitante = req.Solicitante?.Trim(), - DataBloqueio = ToUtc(req.DataBloqueio), - DataEntregaOpera = ToUtc(req.DataEntregaOpera), - DataEntregaCliente = ToUtc(req.DataEntregaCliente), + FranquiaVivo = req.FranquiaVivo, + 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, - Cedente = req.Cedente?.Trim(), - Solicitante = req.Solicitante?.Trim(), + CreatedAt = nowSingle, + UpdatedAt = nowSingle + }; - FranquiaVivo = req.FranquiaVivo, - 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, + ApplyReservaRule(newLineSingle); - CreatedAt = now, - UpdatedAt = now - }; - - ApplyReservaRule(newLine); - created.Add(newLine); - } - - _db.MobileLines.AddRange(created); + _db.MobileLines.Add(newLineSingle); try { @@ -432,13 +528,7 @@ namespace line_gestao_api.Controllers return StatusCode(500, new { message = "Erro ao salvar no banco de dados." }); } - if (created.Count == 1) - { - var newLine = created[0]; - return CreatedAtAction(nameof(GetById), new { id = newLine.Id }, ToDetailDto(newLine)); - } - - return StatusCode(StatusCodes.Status201Created, created.Select(ToDetailDto).ToList()); + return CreatedAtAction(nameof(GetById), new { id = newLineSingle.Id }, ToDetailDto(newLineSingle)); } // ========================================================== diff --git a/Dtos/CreateMobileLineDto.cs b/Dtos/CreateMobileLineDto.cs index 0780655..3abec57 100644 --- a/Dtos/CreateMobileLineDto.cs +++ b/Dtos/CreateMobileLineDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace line_gestao_api.Dtos { @@ -12,7 +13,8 @@ namespace line_gestao_api.Dtos public string? Chip { get; set; } // ICCID public string? Cliente { get; set; } // Obrigatório na validação do Controller 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? Linhas { get; set; } // Linhas individuais para criação em lote // ========================== // Classificação e Status @@ -67,4 +69,67 @@ namespace line_gestao_api.Dtos public decimal? Desconto { 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; } + } }