diff --git a/Controllers/LinesController.cs b/Controllers/LinesController.cs index cd52c5d..5ae8b27 100644 --- a/Controllers/LinesController.cs +++ b/Controllers/LinesController.cs @@ -785,6 +785,162 @@ namespace line_gestao_api.Controllers return CreatedAtAction(nameof(GetById), new { id = newLine.Id }, ToDetailDto(newLine, vigencia)); } + // ========================================================== + // ✅ 5.1. CREATE BATCH + // ========================================================== + [HttpPost("batch")] + [Authorize(Roles = "admin,gestor")] + public async Task> CreateBatch([FromBody] CreateMobileLinesBatchRequestDto req) + { + var requests = req?.Lines ?? new List(); + if (requests.Count == 0) + return BadRequest(new { message = "Informe ao menos uma linha para cadastro em lote." }); + + if (requests.Count > 1000) + return BadRequest(new { message = "O lote excede o limite de 1000 linhas por envio." }); + + var requestedLinhas = requests + .Select(x => OnlyDigits(x?.Linha)) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.Ordinal) + .ToList(); + + var existingLinhas = requestedLinhas.Count == 0 + ? new HashSet(StringComparer.Ordinal) + : (await _db.MobileLines.AsNoTracking() + .Where(x => x.Linha != null && requestedLinhas.Contains(x.Linha)) + .Select(x => x.Linha!) + .ToListAsync()) + .ToHashSet(StringComparer.Ordinal); + + await using var tx = await _db.Database.BeginTransactionAsync(); + + try + { + var nextItem = (await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0); + var seenBatchLinhas = new HashSet(StringComparer.Ordinal); + var createdLines = new List<(MobileLine line, VigenciaLine? vigencia)>(requests.Count); + + for (var i = 0; i < requests.Count; i++) + { + var entry = requests[i]; + var lineNo = i + 1; + + if (entry == null) + return BadRequest(new { message = $"Linha do lote #{lineNo} está vazia." }); + + if (string.IsNullOrWhiteSpace(entry.Cliente)) + return BadRequest(new { message = $"Linha do lote #{lineNo}: o nome do Cliente é obrigatório." }); + + if (string.IsNullOrWhiteSpace(entry.Linha)) + return BadRequest(new { message = $"Linha do lote #{lineNo}: o número da Linha é obrigatório." }); + + if (!entry.DtEfetivacaoServico.HasValue) + return BadRequest(new { message = $"Linha do lote #{lineNo}: a Dt. Efetivação Serviço é obrigatória." }); + + if (!entry.DtTerminoFidelizacao.HasValue) + return BadRequest(new { message = $"Linha do lote #{lineNo}: a Dt. Término Fidelização é obrigatória." }); + + var linhaLimpa = OnlyDigits(entry.Linha); + var chipLimpo = OnlyDigits(entry.Chip); + + if (string.IsNullOrWhiteSpace(linhaLimpa)) + return BadRequest(new { message = $"Linha do lote #{lineNo}: número de linha inválido." }); + + if (!seenBatchLinhas.Add(linhaLimpa)) + return Conflict(new { message = $"A linha {entry.Linha} está duplicada dentro do lote (registro #{lineNo})." }); + + if (existingLinhas.Contains(linhaLimpa)) + return Conflict(new { message = $"A linha {entry.Linha} já está cadastrada no sistema (registro #{lineNo})." }); + + nextItem++; + + var planSuggestion = await AutoFillRules.ResolvePlanSuggestionAsync(_db, entry.PlanoContrato); + var franquiaVivo = entry.FranquiaVivo ?? planSuggestion?.FranquiaGb; + var valorPlanoVivo = entry.ValorPlanoVivo ?? planSuggestion?.ValorPlano; + var now = DateTime.UtcNow; + + var newLine = new MobileLine + { + Id = Guid.NewGuid(), + Item = nextItem, + Cliente = entry.Cliente.Trim().ToUpper(), + Linha = linhaLimpa, + Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo, + Usuario = entry.Usuario?.Trim(), + Status = entry.Status?.Trim(), + Skil = entry.Skil?.Trim(), + Modalidade = entry.Modalidade?.Trim(), + PlanoContrato = entry.PlanoContrato?.Trim(), + Conta = entry.Conta?.Trim(), + VencConta = entry.VencConta?.Trim(), + + DataBloqueio = ToUtc(entry.DataBloqueio), + DataEntregaOpera = ToUtc(entry.DataEntregaOpera), + DataEntregaCliente = ToUtc(entry.DataEntregaCliente), + + Cedente = entry.Cedente?.Trim(), + Solicitante = entry.Solicitante?.Trim(), + + FranquiaVivo = franquiaVivo, + ValorPlanoVivo = valorPlanoVivo, + GestaoVozDados = entry.GestaoVozDados, + Skeelo = entry.Skeelo, + VivoNewsPlus = entry.VivoNewsPlus, + VivoTravelMundo = entry.VivoTravelMundo, + VivoSync = entry.VivoSync, + VivoGestaoDispositivo = entry.VivoGestaoDispositivo, + ValorContratoVivo = entry.ValorContratoVivo, + FranquiaLine = entry.FranquiaLine, + FranquiaGestao = entry.FranquiaGestao, + LocacaoAp = entry.LocacaoAp, + ValorContratoLine = entry.ValorContratoLine, + Desconto = entry.Desconto, + Lucro = entry.Lucro, + TipoDeChip = entry.TipoDeChip?.Trim(), + + CreatedAt = now, + UpdatedAt = now + }; + + ApplyReservaRule(newLine); + + _db.MobileLines.Add(newLine); + + var vigencia = await UpsertVigenciaFromMobileLineAsync( + newLine, + entry.DtEfetivacaoServico, + entry.DtTerminoFidelizacao, + overrideDates: false); + + createdLines.Add((newLine, vigencia)); + } + + await _db.SaveChangesAsync(); + await tx.CommitAsync(); + await _vigenciaNotificationSyncService.SyncCurrentTenantAsync(); + + return Ok(new CreateMobileLinesBatchResultDto + { + Created = createdLines.Count, + Items = createdLines + .Select(x => new CreateMobileLinesBatchCreatedItemDto + { + Id = x.line.Id, + Item = x.line.Item, + Linha = x.line.Linha, + Cliente = x.line.Cliente + }) + .ToList() + }); + } + catch (DbUpdateException) + { + await tx.RollbackAsync(); + return StatusCode(500, new { message = "Erro ao salvar o lote no banco de dados." }); + } + } + // ========================================================== // ✅ 6. UPDATE // ========================================================== diff --git a/Dtos/CreateMobileLinesBatchDtos.cs b/Dtos/CreateMobileLinesBatchDtos.cs new file mode 100644 index 0000000..db969a8 --- /dev/null +++ b/Dtos/CreateMobileLinesBatchDtos.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace line_gestao_api.Dtos +{ + public class CreateMobileLinesBatchRequestDto + { + public List Lines { get; set; } = new(); + } + + public class CreateMobileLinesBatchResultDto + { + public int Created { get; set; } + public List Items { get; set; } = new(); + } + + public class CreateMobileLinesBatchCreatedItemDto + { + public Guid Id { get; set; } + public int Item { get; set; } + public string? Linha { get; set; } + public string? Cliente { get; set; } + } +}