1688 lines
69 KiB
C#
1688 lines
69 KiB
C#
using ClosedXML.Excel;
|
|
using line_gestao_api.Data;
|
|
using line_gestao_api.Dtos;
|
|
using line_gestao_api.Models;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace line_gestao_api.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
//[Authorize]
|
|
public class LinesController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
|
|
public LinesController(AppDbContext db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
public class ImportExcelForm
|
|
{
|
|
public IFormFile File { get; set; } = default!;
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 1. ENDPOINT: AGRUPAR POR CLIENTE
|
|
// ==========================================================
|
|
[HttpGet("groups")]
|
|
public async Task<ActionResult<PagedResult<ClientGroupDto>>> GetClientGroups(
|
|
[FromQuery] string? skil,
|
|
[FromQuery] string? search,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 10)
|
|
{
|
|
page = page < 1 ? 1 : page;
|
|
pageSize = pageSize < 1 ? 10 : pageSize;
|
|
|
|
var query = _db.MobileLines.AsNoTracking().Where(x => !string.IsNullOrEmpty(x.Cliente));
|
|
|
|
// Filtro SKIL
|
|
if (!string.IsNullOrWhiteSpace(skil))
|
|
{
|
|
var sSkil = skil.Trim();
|
|
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%"));
|
|
else
|
|
query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
|
}
|
|
|
|
// Filtro SEARCH (Busca pelo Nome do Cliente)
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.Trim();
|
|
query = query.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%"));
|
|
}
|
|
|
|
var groupedQuery = query
|
|
.GroupBy(x => x.Cliente)
|
|
.Select(g => new ClientGroupDto
|
|
{
|
|
Cliente = g.Key!,
|
|
TotalLinhas = g.Count(),
|
|
Ativos = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%ativo%")),
|
|
Bloqueados = g.Count(x => EF.Functions.ILike(x.Status ?? "", "%bloque%") ||
|
|
EF.Functions.ILike(x.Status ?? "", "%perda%") ||
|
|
EF.Functions.ILike(x.Status ?? "", "%roubo%"))
|
|
});
|
|
|
|
var totalGroups = await groupedQuery.CountAsync();
|
|
|
|
var items = await groupedQuery
|
|
.OrderBy(x => x.Cliente)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
|
|
return Ok(new PagedResult<ClientGroupDto>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = totalGroups,
|
|
Items = items
|
|
});
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 2. ENDPOINT: LISTAR NOMES DE CLIENTES (ACEITA SKIL)
|
|
// ==========================================================
|
|
[HttpGet("clients")]
|
|
public async Task<ActionResult<List<string>>> GetClients([FromQuery] string? skil)
|
|
{
|
|
var query = _db.MobileLines.AsNoTracking();
|
|
|
|
if (!string.IsNullOrWhiteSpace(skil))
|
|
{
|
|
var sSkil = skil.Trim();
|
|
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%"));
|
|
else
|
|
query = query.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
|
}
|
|
|
|
var clients = await query
|
|
.Where(x => !string.IsNullOrEmpty(x.Cliente))
|
|
.Select(x => x.Cliente)
|
|
.Distinct()
|
|
.OrderBy(x => x)
|
|
.ToListAsync();
|
|
|
|
return Ok(clients);
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ ENDPOINTS DO FATURAMENTO (PF/PJ)
|
|
// ==========================================================
|
|
[HttpGet("billing")]
|
|
public async Task<ActionResult<PagedResult<BillingClient>>> GetBilling(
|
|
[FromQuery] string? tipo, // "PF", "PJ" ou null (todos)
|
|
[FromQuery] string? search, // busca por cliente
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20,
|
|
[FromQuery] string? sortBy = "cliente",
|
|
[FromQuery] string? sortDir = "asc")
|
|
{
|
|
page = page < 1 ? 1 : page;
|
|
pageSize = pageSize < 1 ? 20 : pageSize;
|
|
|
|
var q = _db.BillingClients.AsNoTracking();
|
|
|
|
if (!string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var t = tipo.Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") q = q.Where(x => x.Tipo == t);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.Trim();
|
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", $"%{s}%"));
|
|
}
|
|
|
|
var total = await q.CountAsync();
|
|
|
|
var sb = (sortBy ?? "cliente").Trim().ToLowerInvariant();
|
|
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
|
|
|
q = sb switch
|
|
{
|
|
"item" => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item),
|
|
"tipo" => desc ? q.OrderByDescending(x => x.Tipo).ThenBy(x => x.Cliente) : q.OrderBy(x => x.Tipo).ThenBy(x => x.Cliente),
|
|
"qtdlinhas" => desc ? q.OrderByDescending(x => x.QtdLinhas ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.QtdLinhas ?? 0).ThenBy(x => x.Cliente),
|
|
"franquiavivo" => desc ? q.OrderByDescending(x => x.FranquiaVivo ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.FranquiaVivo ?? 0).ThenBy(x => x.Cliente),
|
|
"valorcontratovivo" => desc ? q.OrderByDescending(x => x.ValorContratoVivo ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.ValorContratoVivo ?? 0).ThenBy(x => x.Cliente),
|
|
"franquialine" => desc ? q.OrderByDescending(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.FranquiaLine ?? 0).ThenBy(x => x.Cliente),
|
|
"valorcontratoline" => desc ? q.OrderByDescending(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.ValorContratoLine ?? 0).ThenBy(x => x.Cliente),
|
|
"lucro" => desc ? q.OrderByDescending(x => x.Lucro ?? 0).ThenBy(x => x.Cliente) : q.OrderBy(x => x.Lucro ?? 0).ThenBy(x => x.Cliente),
|
|
|
|
"aparelho" => desc ? q.OrderByDescending(x => x.Aparelho ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.Aparelho ?? "").ThenBy(x => x.Cliente),
|
|
"formapagamento" => desc ? q.OrderByDescending(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente) : q.OrderBy(x => x.FormaPagamento ?? "").ThenBy(x => x.Cliente),
|
|
|
|
_ => desc ? q.OrderByDescending(x => x.Cliente).ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente).ThenBy(x => x.Item),
|
|
};
|
|
|
|
var items = await q
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
|
|
return Ok(new PagedResult<BillingClient>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
Items = items
|
|
});
|
|
}
|
|
|
|
[HttpGet("billing/clients")]
|
|
public async Task<ActionResult<List<string>>> GetBillingClients([FromQuery] string? tipo)
|
|
{
|
|
var q = _db.BillingClients.AsNoTracking();
|
|
|
|
if (!string.IsNullOrWhiteSpace(tipo))
|
|
{
|
|
var t = tipo.Trim().ToUpperInvariant();
|
|
if (t == "PF" || t == "PJ") q = q.Where(x => x.Tipo == t);
|
|
}
|
|
|
|
var clients = await q
|
|
.Where(x => !string.IsNullOrEmpty(x.Cliente))
|
|
.Select(x => x.Cliente)
|
|
.Distinct()
|
|
.OrderBy(x => x)
|
|
.ToListAsync();
|
|
|
|
return Ok(clients);
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 3. GET ALL (GERAL)
|
|
// ==========================================================
|
|
[HttpGet]
|
|
public async Task<ActionResult<PagedResult<MobileLineListDto>>> GetAll(
|
|
[FromQuery] string? search,
|
|
[FromQuery] string? skil,
|
|
[FromQuery] string? client,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20,
|
|
[FromQuery] string? sortBy = "item",
|
|
[FromQuery] string? sortDir = "asc")
|
|
{
|
|
page = page < 1 ? 1 : page;
|
|
pageSize = pageSize < 1 ? 20 : pageSize;
|
|
|
|
var q = _db.MobileLines.AsNoTracking();
|
|
|
|
if (!string.IsNullOrWhiteSpace(skil))
|
|
{
|
|
var sSkil = skil.Trim();
|
|
if (sSkil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
|
q = q.Where(x => x.Skil == "RESERVA" || EF.Functions.ILike(x.Skil ?? "", "%RESERVA%"));
|
|
else
|
|
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{sSkil}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(client))
|
|
q = q.Where(x => EF.Functions.ILike(x.Cliente ?? "", client.Trim()));
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.Trim();
|
|
q = q.Where(x =>
|
|
EF.Functions.ILike(x.Linha ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Chip ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Cliente ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Usuario ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Conta ?? "", $"%{s}%") ||
|
|
EF.Functions.ILike(x.Status ?? "", $"%{s}%"));
|
|
}
|
|
|
|
var total = await q.CountAsync();
|
|
|
|
var sb = (sortBy ?? "item").Trim().ToLowerInvariant();
|
|
var desc = string.Equals((sortDir ?? "asc").Trim(), "desc", StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (sb == "plano") sb = "planocontrato";
|
|
if (sb == "contrato") sb = "vencconta";
|
|
|
|
q = sb switch
|
|
{
|
|
"conta" => desc ? q.OrderByDescending(x => x.Conta ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Conta ?? "").ThenBy(x => x.Item),
|
|
"linha" => desc ? q.OrderByDescending(x => x.Linha ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Linha ?? "").ThenBy(x => x.Item),
|
|
"chip" => desc ? q.OrderByDescending(x => x.Chip ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Chip ?? "").ThenBy(x => x.Item),
|
|
"cliente" => desc ? q.OrderByDescending(x => x.Cliente ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Cliente ?? "").ThenBy(x => x.Item),
|
|
"usuario" => desc ? q.OrderByDescending(x => x.Usuario ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Usuario ?? "").ThenBy(x => x.Item),
|
|
"planocontrato" => desc ? q.OrderByDescending(x => x.PlanoContrato ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.PlanoContrato ?? "").ThenBy(x => x.Item),
|
|
"vencconta" => desc ? q.OrderByDescending(x => x.VencConta ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.VencConta ?? "").ThenBy(x => x.Item),
|
|
"status" => desc ? q.OrderByDescending(x => x.Status ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Status ?? "").ThenBy(x => x.Item),
|
|
"skil" => desc ? q.OrderByDescending(x => x.Skil ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Skil ?? "").ThenBy(x => x.Item),
|
|
"modalidade" => desc ? q.OrderByDescending(x => x.Modalidade ?? "").ThenBy(x => x.Item) : q.OrderBy(x => x.Modalidade ?? "").ThenBy(x => x.Item),
|
|
_ => desc ? q.OrderByDescending(x => x.Item) : q.OrderBy(x => x.Item)
|
|
};
|
|
|
|
var items = await q
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(x => new MobileLineListDto
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
Conta = x.Conta,
|
|
Linha = x.Linha,
|
|
Chip = x.Chip,
|
|
Cliente = x.Cliente,
|
|
Usuario = x.Usuario,
|
|
PlanoContrato = x.PlanoContrato,
|
|
Status = x.Status,
|
|
Skil = x.Skil,
|
|
Modalidade = x.Modalidade,
|
|
VencConta = x.VencConta
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(new PagedResult<MobileLineListDto>
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
Items = items
|
|
});
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 4. GET BY ID
|
|
// ==========================================================
|
|
[HttpGet("{id:guid}")]
|
|
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
|
{
|
|
var x = await _db.MobileLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
return Ok(ToDetailDto(x));
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 5. CREATE
|
|
// ==========================================================
|
|
[HttpPost]
|
|
public async Task<ActionResult<MobileLineDetailDto>> Create([FromBody] CreateMobileLineDto req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.Cliente))
|
|
return BadRequest(new { message = "O nome do Cliente é obrigatório." });
|
|
|
|
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 (string.IsNullOrWhiteSpace(linhaLimpa))
|
|
return BadRequest(new { message = "Número de linha inválido." });
|
|
|
|
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 maxItem = await _db.MobileLines.MaxAsync(x => (int?)x.Item) ?? 0;
|
|
var nextItem = maxItem + 1;
|
|
|
|
var newLine = new MobileLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = nextItem,
|
|
Cliente = req.Cliente.Trim().ToUpper(),
|
|
Linha = linhaLimpa,
|
|
Chip = 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 = 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,
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
ApplyReservaRule(newLine);
|
|
|
|
_db.MobileLines.Add(newLine);
|
|
|
|
try
|
|
{
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateException)
|
|
{
|
|
return StatusCode(500, new { message = "Erro ao salvar no banco de dados." });
|
|
}
|
|
|
|
return CreatedAtAction(nameof(GetById), new { id = newLine.Id }, ToDetailDto(newLine));
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 6. UPDATE
|
|
// ==========================================================
|
|
[HttpPut("{id:guid}")]
|
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
|
{
|
|
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
|
|
var newLinha = OnlyDigits(req.Linha);
|
|
if (!string.IsNullOrWhiteSpace(newLinha) && !string.Equals((x.Linha ?? ""), newLinha, StringComparison.Ordinal))
|
|
{
|
|
var exists = await _db.MobileLines.AsNoTracking().AnyAsync(m => m.Linha == newLinha && m.Id != id);
|
|
if (exists) return Conflict(new { message = "Já existe registro com essa LINHA.", linha = newLinha });
|
|
}
|
|
|
|
x.Conta = req.Conta?.Trim();
|
|
x.Linha = newLinha;
|
|
x.Chip = OnlyDigits(req.Chip);
|
|
x.Cliente = req.Cliente?.Trim();
|
|
x.Usuario = req.Usuario?.Trim();
|
|
x.PlanoContrato = req.PlanoContrato?.Trim();
|
|
x.FranquiaVivo = req.FranquiaVivo;
|
|
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
|
x.GestaoVozDados = req.GestaoVozDados;
|
|
x.Skeelo = req.Skeelo;
|
|
x.VivoNewsPlus = req.VivoNewsPlus;
|
|
x.VivoTravelMundo = req.VivoTravelMundo;
|
|
x.VivoGestaoDispositivo = req.VivoGestaoDispositivo;
|
|
x.ValorContratoVivo = req.ValorContratoVivo;
|
|
x.FranquiaLine = req.FranquiaLine;
|
|
x.FranquiaGestao = req.FranquiaGestao;
|
|
x.LocacaoAp = req.LocacaoAp;
|
|
x.ValorContratoLine = req.ValorContratoLine;
|
|
x.Desconto = req.Desconto;
|
|
x.Lucro = req.Lucro;
|
|
x.Status = req.Status?.Trim();
|
|
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
|
x.Skil = req.Skil?.Trim();
|
|
x.Modalidade = req.Modalidade?.Trim();
|
|
x.Cedente = req.Cedente?.Trim();
|
|
x.Solicitante = req.Solicitante?.Trim();
|
|
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
|
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
|
x.VencConta = req.VencConta?.Trim();
|
|
|
|
ApplyReservaRule(x);
|
|
x.UpdatedAt = DateTime.UtcNow;
|
|
|
|
try { await _db.SaveChangesAsync(); }
|
|
catch (DbUpdateException) { return Conflict(new { message = "Conflito ao salvar." }); }
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 7. DELETE
|
|
// ==========================================================
|
|
[HttpDelete("{id:guid}")]
|
|
public async Task<IActionResult> Delete(Guid id)
|
|
{
|
|
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
|
if (x == null) return NotFound();
|
|
_db.MobileLines.Remove(x);
|
|
await _db.SaveChangesAsync();
|
|
return NoContent();
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ 8. IMPORT EXCEL (GERAL + MUREG + FATURAMENTO + DADOS USUÁRIOS + VIGÊNCIA + TROCA DE NÚMERO + PARCELAMENTO)
|
|
// ✅ IMPORTAÇÃO CONTINUA NO LINESCONTROLLER
|
|
// ==========================================================
|
|
[HttpPost("import-excel")]
|
|
[Consumes("multipart/form-data")]
|
|
[RequestSizeLimit(50_000_000)]
|
|
public async Task<ActionResult<ImportResultDto>> ImportExcel([FromForm] ImportExcelForm form)
|
|
{
|
|
var file = form.File;
|
|
if (file == null || file.Length == 0) return BadRequest("Arquivo inválido.");
|
|
|
|
using var stream = file.OpenReadStream();
|
|
using var wb = new XLWorkbook(stream);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA GERAL
|
|
// =========================
|
|
var ws = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("GERAL", StringComparison.OrdinalIgnoreCase));
|
|
if (ws == null) return BadRequest("Aba 'GERAL' não encontrada.");
|
|
|
|
var headerRow = ws.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
|
if (headerRow == null) return BadRequest("Cabeçalho 'ITEM' não encontrado.");
|
|
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
int colItem = GetCol(map, "ITEM");
|
|
if (colItem == 0) return BadRequest("Coluna 'ITEM' não encontrada.");
|
|
|
|
var startRow = headerRow.RowNumber() + 1;
|
|
|
|
await _db.MobileLines.ExecuteDeleteAsync();
|
|
|
|
var buffer = new List<MobileLine>(600);
|
|
var imported = 0;
|
|
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var itemStr = GetCellString(ws, r, colItem);
|
|
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
|
|
|
var e = new MobileLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = TryInt(itemStr),
|
|
Conta = GetCellByHeader(ws, r, map, "CONTA"),
|
|
Linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA")),
|
|
Chip = OnlyDigits(GetCellByHeader(ws, r, map, "CHIP")),
|
|
Cliente = GetCellByHeader(ws, r, map, "CLIENTE"),
|
|
Usuario = GetCellByHeader(ws, r, map, "USUARIO"),
|
|
PlanoContrato = GetCellByHeader(ws, r, map, "PLANO CONTRATO"),
|
|
FranquiaVivo = TryDecimal(GetCellByHeader(ws, r, map, "FRAQUIA")),
|
|
ValorPlanoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR DO PLANO R$")),
|
|
GestaoVozDados = TryDecimal(GetCellByHeader(ws, r, map, "GESTAO VOZ E DADOS R$")),
|
|
Skeelo = TryDecimal(GetCellByHeader(ws, r, map, "SKEELO")),
|
|
VivoNewsPlus = TryDecimal(GetCellByHeader(ws, r, map, "VIVO NEWS PLUS")),
|
|
VivoTravelMundo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO TRAVEL MUNDO")),
|
|
VivoGestaoDispositivo = TryDecimal(GetCellByHeader(ws, r, map, "VIVO GESTAO DISPOSITIVO")),
|
|
ValorContratoVivo = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO VIVO")),
|
|
FranquiaLine = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA LINE")),
|
|
FranquiaGestao = TryDecimal(GetCellByHeader(ws, r, map, "FRANQUIA GESTAO")),
|
|
LocacaoAp = TryDecimal(GetCellByHeader(ws, r, map, "LOCACAO AP.")),
|
|
ValorContratoLine = TryDecimal(GetCellByHeader(ws, r, map, "VALOR CONTRATO LINE")),
|
|
Desconto = TryDecimal(GetCellByHeader(ws, r, map, "DESCONTO")),
|
|
Lucro = TryDecimal(GetCellByHeader(ws, r, map, "LUCRO")),
|
|
Status = GetCellByHeader(ws, r, map, "STATUS"),
|
|
DataBloqueio = TryDate(ws, r, map, "DATA DO BLOQUEIO"),
|
|
Skil = GetCellByHeader(ws, r, map, "SKIL"),
|
|
Modalidade = GetCellByHeader(ws, r, map, "MODALIDADE"),
|
|
Cedente = GetCellByHeader(ws, r, map, "CEDENTE"),
|
|
Solicitante = GetCellByHeader(ws, r, map, "SOLICITANTE"),
|
|
DataEntregaOpera = TryDate(ws, r, map, "DATA DA ENTREGA OPERA."),
|
|
DataEntregaCliente = TryDate(ws, r, map, "DATA DA ENTREGA CLIENTE"),
|
|
VencConta = GetCellByHeader(ws, r, map, "VENC. DA CONTA"),
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
ApplyReservaRule(e);
|
|
|
|
buffer.Add(e);
|
|
imported++;
|
|
|
|
if (buffer.Count >= 500)
|
|
{
|
|
await _db.MobileLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.MobileLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
|
|
// =========================
|
|
// ✅ IMPORTA MUREG
|
|
// =========================
|
|
await ImportMuregFromWorkbook(wb);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA FATURAMENTO PF/PJ
|
|
// =========================
|
|
await ImportBillingFromWorkbook(wb);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA DADOS DOS USUÁRIOS (UserDatas)
|
|
// =========================
|
|
await ImportUserDatasFromWorkbook(wb);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA VIGÊNCIA
|
|
// =========================
|
|
await ImportVigenciaFromWorkbook(wb);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA TROCA DE NÚMERO
|
|
// =========================
|
|
await ImportTrocaNumeroFromWorkbook(wb);
|
|
|
|
// =========================
|
|
// ✅ IMPORTA PARCELAMENTO (NOVO)
|
|
// =========================
|
|
await ImportParcelamentoFromWorkbook(wb);
|
|
|
|
return Ok(new ImportResultDto { Imported = imported });
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO DA ABA MUREG
|
|
// ==========================================================
|
|
private async Task ImportMuregFromWorkbook(XLWorkbook wb)
|
|
{
|
|
var wsM = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("MUREG", StringComparison.OrdinalIgnoreCase))
|
|
?? wb.Worksheets.FirstOrDefault(w => w.Name.Trim().ToUpperInvariant().Contains("MUREG"));
|
|
|
|
if (wsM == null) return;
|
|
|
|
var headerRow = wsM.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
|
if (headerRow == null) return;
|
|
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
int colItem = GetCol(map, "ITEM");
|
|
if (colItem == 0) return;
|
|
|
|
var startRow = headerRow.RowNumber() + 1;
|
|
|
|
await _db.MuregLines.ExecuteDeleteAsync();
|
|
|
|
var buffer = new List<MuregLine>(600);
|
|
|
|
var lastRow = wsM.LastRowUsed()?.RowNumber() ?? startRow;
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var itemStr = GetCellString(wsM, r, colItem);
|
|
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
|
|
|
var e = new MuregLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = TryInt(itemStr),
|
|
LinhaAntiga = OnlyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA")),
|
|
LinhaNova = OnlyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA")),
|
|
ICCID = OnlyDigits(GetCellByHeader(wsM, r, map, "ICCID")),
|
|
DataDaMureg = TryDate(wsM, r, map, "DATA DA MUREG"),
|
|
Cliente = GetCellByHeader(wsM, r, map, "CLIENTE"),
|
|
};
|
|
|
|
buffer.Add(e);
|
|
|
|
if (buffer.Count >= 500)
|
|
{
|
|
await _db.MuregLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.MuregLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO DO FATURAMENTO (PF/PJ)
|
|
// ==========================================================
|
|
private async Task ImportBillingFromWorkbook(XLWorkbook wb)
|
|
{
|
|
await _db.BillingClients.ExecuteDeleteAsync();
|
|
|
|
// PF
|
|
var wsPf = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("FATURAMENTO PF", StringComparison.OrdinalIgnoreCase))
|
|
?? wb.Worksheets.FirstOrDefault(w => w.Name.Trim().ToUpperInvariant().Contains("FATURAMENTO") && w.Name.Trim().ToUpperInvariant().Contains("PF"));
|
|
if (wsPf != null)
|
|
await ImportBillingSheet(wsPf, "PF");
|
|
|
|
// PJ
|
|
var wsPj = wb.Worksheets.FirstOrDefault(w => w.Name.Trim().Equals("FATURAMENTO PJ", StringComparison.OrdinalIgnoreCase))
|
|
?? wb.Worksheets.FirstOrDefault(w => w.Name.Trim().ToUpperInvariant().Contains("FATURAMENTO") && w.Name.Trim().ToUpperInvariant().Contains("PJ"));
|
|
if (wsPj != null)
|
|
await ImportBillingSheet(wsPj, "PJ");
|
|
}
|
|
|
|
private async Task ImportBillingSheet(IXLWorksheet ws, string tipo)
|
|
{
|
|
// ✅ acha linha do header pelo "CLIENTE"
|
|
var headerRow =
|
|
ws.RowsUsed().FirstOrDefault(r =>
|
|
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "CLIENTE"));
|
|
|
|
if (headerRow == null) return;
|
|
|
|
var headerRowIndex = headerRow.RowNumber();
|
|
|
|
// ✅ na PJ (e às vezes PF), existe uma linha acima com os grupos: "VIVO" e "LINE MÓVEL"
|
|
var groupRowIndex = Math.Max(1, headerRowIndex - 1);
|
|
var groupRow = ws.Row(groupRowIndex);
|
|
|
|
var lastCol = GetLastUsedColumn(ws, headerRowIndex);
|
|
|
|
// colunas base (sempre pela linha de header)
|
|
var colItem = FindColByAny(headerRow, lastCol, "ITEM");
|
|
var colCliente = FindColByAny(headerRow, lastCol, "CLIENTE");
|
|
if (colCliente == 0) return;
|
|
|
|
var colQtd = FindColByAny(headerRow, lastCol, "QTD DE LINHAS", "QTD LINHAS", "QTDDLINHAS");
|
|
var colLucro = FindColByAny(headerRow, lastCol, "LUCRO");
|
|
var colAparelho = FindColByAny(headerRow, lastCol, "APARELHO");
|
|
var colForma = FindColByAny(headerRow, lastCol, "FORMA DE PAGAMENTO", "FORMA PAGAMENTO", "FORMAPAGAMENTO");
|
|
|
|
// ----------------------------------------------------------
|
|
// ✅ Principal correção:
|
|
// No PJ, o valor da Vivo costuma estar em uma coluna com header vazio (ou "R$")
|
|
// e o grupo "VIVO" costuma estar MESCLADO (merge), deixando células vazias.
|
|
// ----------------------------------------------------------
|
|
|
|
// tenta detectar se existe "VIVO" e "LINE" na linha de grupos (se não tiver, cai em fallback)
|
|
var hasAnyGroup = RowHasAnyText(groupRow);
|
|
|
|
int colFranquiaVivo = 0;
|
|
int colValorVivo = 0;
|
|
int colFranquiaLine = 0;
|
|
int colValorLine = 0;
|
|
|
|
if (hasAnyGroup)
|
|
{
|
|
// VIVO
|
|
colFranquiaVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO",
|
|
"FRANQUIA", "FRAQUIA", "FRANQUIAVIVO", "FRAQUIAVIVO");
|
|
|
|
colValorVivo = FindColInGroup(groupRow, headerRow, lastCol, "VIVO",
|
|
"VALOR CONTRATO VIVO", "VALOR DO CONTRATO VIVO", "VALOR VIVO", "VALOR",
|
|
"R$", "RS", ""); // "" = aceita header vazio
|
|
|
|
// LINE
|
|
colFranquiaLine = FindColInGroup(groupRow, headerRow, lastCol, "LINE",
|
|
"FRANQUIA LINE", "FRAQUIA LINE", "FRANQUIA", "FRAQUIA", "FRANQUIALINE", "FRAQUIALINE");
|
|
|
|
colValorLine = FindColInGroup(groupRow, headerRow, lastCol, "LINE",
|
|
"VALOR CONTRATO LINE", "VALOR DO CONTRATO LINE", "VALOR LINE", "VALOR",
|
|
"R$", "RS", "");
|
|
|
|
// ✅ Fallback extra: se o valor não foi encontrado mas a Franquia foi,
|
|
// pega a coluna imediatamente à direita (comum quando header do valor é vazio)
|
|
if (colValorVivo == 0 && colFranquiaVivo > 0)
|
|
{
|
|
var cand = colFranquiaVivo + 1;
|
|
if (cand <= lastCol)
|
|
{
|
|
var g = GetMergedGroupKeyAt(groupRow, cand);
|
|
if (!string.IsNullOrWhiteSpace(g) && g.Contains(NormalizeHeader("VIVO")))
|
|
colValorVivo = cand;
|
|
}
|
|
}
|
|
|
|
if (colValorLine == 0 && colFranquiaLine > 0)
|
|
{
|
|
var cand = colFranquiaLine + 1;
|
|
if (cand <= lastCol)
|
|
{
|
|
var g = GetMergedGroupKeyAt(groupRow, cand);
|
|
if (!string.IsNullOrWhiteSpace(g) && g.Contains(NormalizeHeader("LINE")))
|
|
colValorLine = cand;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ✅ fallback (caso a planilha não tenha linha de grupos)
|
|
if (colFranquiaVivo == 0 || colValorVivo == 0 || colFranquiaLine == 0 || colValorLine == 0)
|
|
{
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
if (colFranquiaLine == 0)
|
|
colFranquiaLine = GetColAny(map, "FRAQUIA LINE", "FRANQUIA LINE", "FRANQUIALINE", "FRAQUIALINE");
|
|
|
|
if (colFranquiaVivo == 0)
|
|
{
|
|
colFranquiaVivo = GetColAny(map, "FRAQUIA VIVO", "FRANQUIA VIVO", "FRANQUIAVIVO", "FRAQUIAVIVO");
|
|
if (colFranquiaVivo == 0)
|
|
{
|
|
var colFranquia = GetColAny(map, "FRAQUIA", "FRANQUIA");
|
|
if (colFranquia != 0 && colFranquia != colFranquiaLine) colFranquiaVivo = colFranquia;
|
|
}
|
|
}
|
|
|
|
if (colValorVivo == 0)
|
|
colValorVivo = GetColAny(map,
|
|
"VALOR CONTRATO VIVO",
|
|
"VALOR DO CONTRATO VIVO",
|
|
"VALOR CONTRATO VIVO R$",
|
|
"VALOR VIVO");
|
|
|
|
if (colValorLine == 0)
|
|
colValorLine = GetColAny(map,
|
|
"VALOR CONTRATO LINE",
|
|
"VALOR DO CONTRATO LINE",
|
|
"VALOR CONTRATO LINE R$",
|
|
"VALOR LINE");
|
|
}
|
|
|
|
var startRow = headerRowIndex + 1;
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
var buffer = new List<BillingClient>(400);
|
|
var seqItem = 0;
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var cliente = GetCellString(ws, r, colCliente);
|
|
if (string.IsNullOrWhiteSpace(cliente)) break;
|
|
|
|
seqItem++;
|
|
|
|
var itemStr = colItem > 0 ? GetCellString(ws, r, colItem) : "";
|
|
var item = !string.IsNullOrWhiteSpace(itemStr) ? TryInt(itemStr) : seqItem;
|
|
|
|
int? qtd = null;
|
|
if (colQtd > 0)
|
|
{
|
|
var qtdStr = GetCellString(ws, r, colQtd);
|
|
qtd = TryNullableInt(qtdStr);
|
|
}
|
|
|
|
var franquiaVivoStr = colFranquiaVivo > 0 ? GetCellString(ws, r, colFranquiaVivo) : "";
|
|
var franquiaLineStr = colFranquiaLine > 0 ? GetCellString(ws, r, colFranquiaLine) : "";
|
|
|
|
var valorContratoVivoStr = colValorVivo > 0 ? GetCellString(ws, r, colValorVivo) : "";
|
|
var valorContratoLineStr = colValorLine > 0 ? GetCellString(ws, r, colValorLine) : "";
|
|
|
|
var lucroStr = colLucro > 0 ? GetCellString(ws, r, colLucro) : "";
|
|
var aparelho = colAparelho > 0 ? GetCellString(ws, r, colAparelho) : "";
|
|
var formaPagto = colForma > 0 ? GetCellString(ws, r, colForma) : "";
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
var e = new BillingClient
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Tipo = tipo,
|
|
Item = item,
|
|
Cliente = cliente.Trim(),
|
|
QtdLinhas = qtd,
|
|
|
|
FranquiaVivo = TryDecimal(franquiaVivoStr),
|
|
ValorContratoVivo = TryDecimal(valorContratoVivoStr),
|
|
|
|
FranquiaLine = TryDecimal(franquiaLineStr),
|
|
ValorContratoLine = TryDecimal(valorContratoLineStr),
|
|
|
|
Lucro = TryDecimal(lucroStr),
|
|
|
|
Aparelho = string.IsNullOrWhiteSpace(aparelho) ? null : aparelho.Trim(),
|
|
FormaPagamento = string.IsNullOrWhiteSpace(formaPagto) ? null : formaPagto.Trim(),
|
|
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
buffer.Add(e);
|
|
|
|
if (buffer.Count >= 300)
|
|
{
|
|
await _db.BillingClients.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.BillingClients.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO: DADOS DOS USUÁRIOS (UserDatas)
|
|
// ==========================================================
|
|
private async Task ImportUserDatasFromWorkbook(XLWorkbook wb)
|
|
{
|
|
// procura aba com nome exato ou aproximado
|
|
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUÁRIOS"))
|
|
?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("DADOS DOS USUARIOS"))
|
|
?? wb.Worksheets.FirstOrDefault(w =>
|
|
NormalizeHeader(w.Name).Contains("DADOS") &&
|
|
NormalizeHeader(w.Name).Contains("USUAR"));
|
|
|
|
if (ws == null) return;
|
|
|
|
// header com ITEM ou CLIENTE (mais seguro)
|
|
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
|
r.CellsUsed().Any(c =>
|
|
NormalizeHeader(c.GetString()) == "ITEM" ||
|
|
NormalizeHeader(c.GetString()) == "CLIENTE"));
|
|
|
|
if (headerRow == null) return;
|
|
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
// colunas principais
|
|
var colItem = GetCol(map, "ITEM");
|
|
var colCliente = GetCol(map, "CLIENTE");
|
|
var colLinha = GetCol(map, "LINHA");
|
|
|
|
// se não tiver cliente, não importa
|
|
if (colCliente == 0) return;
|
|
|
|
// limpar tabela (espelho da planilha)
|
|
await _db.UserDatas.ExecuteDeleteAsync();
|
|
|
|
var startRow = headerRow.RowNumber() + 1;
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
var buffer = new List<UserData>(500);
|
|
var seq = 0;
|
|
|
|
// colunas opcionais (várias variações de header)
|
|
var colCpf = GetColAny(map, "CPF");
|
|
var colRg = GetColAny(map, "RG");
|
|
var colEmail = GetColAny(map, "EMAIL", "E-MAIL");
|
|
var colEndereco = GetColAny(map, "ENDERECO", "ENDEREÇO");
|
|
var colCelular = GetColAny(map, "CELULAR", "CEL");
|
|
var colFixo = GetColAny(map, "TELEFONE FIXO", "TELEFONEFIXO", "FIXO", "TELEFONE");
|
|
|
|
var colDataNasc = GetColAny(map,
|
|
"DATA DE NASCIMENTO",
|
|
"DATADENASCIMENTO",
|
|
"DATA NASCIMENTO",
|
|
"DATANASCIMENTO",
|
|
"NASCIMENTO",
|
|
"DTNASC");
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var cliente = GetCellString(ws, r, colCliente);
|
|
if (string.IsNullOrWhiteSpace(cliente)) break;
|
|
|
|
seq++;
|
|
|
|
int item;
|
|
if (colItem > 0)
|
|
{
|
|
var itemStr = GetCellString(ws, r, colItem);
|
|
item = !string.IsNullOrWhiteSpace(itemStr) ? TryInt(itemStr) : seq;
|
|
}
|
|
else item = seq;
|
|
|
|
var linha = colLinha > 0 ? OnlyDigits(GetCellString(ws, r, colLinha)) : "";
|
|
|
|
var cpf = colCpf > 0 ? OnlyDigits(GetCellString(ws, r, colCpf)) : "";
|
|
var rg = colRg > 0 ? OnlyDigits(GetCellString(ws, r, colRg)) : "";
|
|
|
|
DateTime? dataNascimento = null;
|
|
if (colDataNasc > 0)
|
|
dataNascimento = TryDateCell(ws, r, colDataNasc);
|
|
|
|
var email = colEmail > 0 ? GetCellString(ws, r, colEmail) : "";
|
|
var endereco = colEndereco > 0 ? GetCellString(ws, r, colEndereco) : "";
|
|
|
|
var celular = colCelular > 0 ? OnlyDigits(GetCellString(ws, r, colCelular)) : "";
|
|
var fixo = colFixo > 0 ? OnlyDigits(GetCellString(ws, r, colFixo)) : "";
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
var e = new UserData
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = item,
|
|
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha,
|
|
Cliente = cliente.Trim(),
|
|
|
|
Cpf = string.IsNullOrWhiteSpace(cpf) ? null : cpf,
|
|
Rg = string.IsNullOrWhiteSpace(rg) ? null : rg,
|
|
DataNascimento = ToUtc(dataNascimento),
|
|
|
|
Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(),
|
|
Endereco = string.IsNullOrWhiteSpace(endereco) ? null : endereco.Trim(),
|
|
|
|
Celular = string.IsNullOrWhiteSpace(celular) ? null : celular,
|
|
TelefoneFixo = string.IsNullOrWhiteSpace(fixo) ? null : fixo,
|
|
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
buffer.Add(e);
|
|
|
|
if (buffer.Count >= 400)
|
|
{
|
|
await _db.UserDatas.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.UserDatas.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO: VIGÊNCIA
|
|
// ==========================================================
|
|
private async Task ImportVigenciaFromWorkbook(XLWorkbook wb)
|
|
{
|
|
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("VIGÊNCIA"))
|
|
?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("VIGENCIA"))
|
|
?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name).Contains("VIGEN"));
|
|
|
|
if (ws == null) return;
|
|
|
|
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
|
r.CellsUsed().Any(c =>
|
|
{
|
|
var k = NormalizeHeader(c.GetString());
|
|
return k == "ITEM" || k == "ITEM(ID)" || k == "ITEMID";
|
|
}));
|
|
|
|
if (headerRow == null) return;
|
|
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
var colItem = GetColAny(map, "ITEM", "ITEM(ID)", "ITEMID", "ITEM (ID)", "ITÉM (ID)");
|
|
if (colItem == 0) return;
|
|
|
|
var startRow = headerRow.RowNumber() + 1;
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
await _db.VigenciaLines.ExecuteDeleteAsync();
|
|
|
|
var buffer = new List<VigenciaLine>(600);
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var itemStr = GetCellString(ws, r, colItem);
|
|
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
|
|
|
var conta = GetCellByHeader(ws, r, map, "CONTA");
|
|
var linha = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA"));
|
|
var cliente = GetCellByHeader(ws, r, map, "CLIENTE");
|
|
var usuario = GetCellByHeader(ws, r, map, "USUÁRIO");
|
|
if (string.IsNullOrWhiteSpace(usuario))
|
|
usuario = GetCellByHeader(ws, r, map, "USUARIO");
|
|
|
|
var plano = GetCellByHeader(ws, r, map, "PLANO CONTRATO");
|
|
|
|
var dtEfet = TryDate(ws, r, map, "DT. DE EFETIVAÇÃO DO SERVIÇO");
|
|
if (dtEfet == null) dtEfet = TryDate(ws, r, map, "DT. DE EFETIVACAO DO SERVICO");
|
|
|
|
var dtFim = TryDate(ws, r, map, "DT. DE TÉRMINO DA FIDELIZAÇÃO");
|
|
if (dtFim == null) dtFim = TryDate(ws, r, map, "DT. DE TERMINO DA FIDELIZACAO");
|
|
|
|
var totalStr = GetCellByHeader(ws, r, map, "TOTAL");
|
|
var now = DateTime.UtcNow;
|
|
|
|
var e = new VigenciaLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = TryInt(itemStr),
|
|
Conta = string.IsNullOrWhiteSpace(conta) ? null : conta.Trim(),
|
|
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha,
|
|
Cliente = string.IsNullOrWhiteSpace(cliente) ? null : cliente.Trim(),
|
|
Usuario = string.IsNullOrWhiteSpace(usuario) ? null : usuario.Trim(),
|
|
PlanoContrato = string.IsNullOrWhiteSpace(plano) ? null : plano.Trim(),
|
|
DtEfetivacaoServico = dtEfet,
|
|
DtTerminoFidelizacao = dtFim,
|
|
Total = TryDecimal(totalStr),
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
buffer.Add(e);
|
|
|
|
if (buffer.Count >= 500)
|
|
{
|
|
await _db.VigenciaLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.VigenciaLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO: TROCA DE NÚMERO
|
|
// ==========================================================
|
|
private async Task ImportTrocaNumeroFromWorkbook(XLWorkbook wb)
|
|
{
|
|
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("TROCA DE NÚMERO"))
|
|
?? wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name) == NormalizeHeader("TROCA DE NUMERO"))
|
|
?? wb.Worksheets.FirstOrDefault(w =>
|
|
NormalizeHeader(w.Name).Contains("TROCA") &&
|
|
NormalizeHeader(w.Name).Contains("NUMER"));
|
|
|
|
if (ws == null) return;
|
|
|
|
var headerRow = ws.RowsUsed().FirstOrDefault(r =>
|
|
r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
|
|
|
if (headerRow == null) return;
|
|
|
|
var map = BuildHeaderMap(headerRow);
|
|
|
|
var colItem = GetCol(map, "ITEM");
|
|
if (colItem == 0) return;
|
|
|
|
var startRow = headerRow.RowNumber() + 1;
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
await _db.TrocaNumeroLines.ExecuteDeleteAsync();
|
|
|
|
var buffer = new List<TrocaNumeroLine>(600);
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var itemStr = GetCellString(ws, r, colItem);
|
|
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
|
|
|
var linhaAntiga = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA ANTIGA"));
|
|
var linhaNova = OnlyDigits(GetCellByHeader(ws, r, map, "LINHA NOVA"));
|
|
var iccid = OnlyDigits(GetCellByHeader(ws, r, map, "ICCID"));
|
|
|
|
var dataTroca = TryDate(ws, r, map, "DATA TROCA");
|
|
if (dataTroca == null) dataTroca = TryDate(ws, r, map, "DATA DA TROCA");
|
|
|
|
var motivo = GetCellByHeader(ws, r, map, "MOTIVO");
|
|
var obs = GetCellByHeader(ws, r, map, "OBSERVAÇÃO");
|
|
if (string.IsNullOrWhiteSpace(obs)) obs = GetCellByHeader(ws, r, map, "OBSERVACAO");
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
var e = new TrocaNumeroLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = TryInt(itemStr),
|
|
LinhaAntiga = string.IsNullOrWhiteSpace(linhaAntiga) ? null : linhaAntiga,
|
|
LinhaNova = string.IsNullOrWhiteSpace(linhaNova) ? null : linhaNova,
|
|
ICCID = string.IsNullOrWhiteSpace(iccid) ? null : iccid,
|
|
DataTroca = dataTroca,
|
|
Motivo = string.IsNullOrWhiteSpace(motivo) ? null : motivo.Trim(),
|
|
Observacao = string.IsNullOrWhiteSpace(obs) ? null : obs.Trim(),
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
buffer.Add(e);
|
|
|
|
if (buffer.Count >= 500)
|
|
{
|
|
await _db.TrocaNumeroLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
buffer.Clear();
|
|
}
|
|
}
|
|
|
|
if (buffer.Count > 0)
|
|
{
|
|
await _db.TrocaNumeroLines.AddRangeAsync(buffer);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ IMPORTAÇÃO: PARCELAMENTO (NOVO)
|
|
// - aba real costuma ser "PARCELAMENTOS DE APARELHOS"
|
|
// - mas buscamos qualquer aba que contenha "PARCEL"
|
|
// ==========================================================
|
|
private async Task ImportParcelamentoFromWorkbook(XLWorkbook wb)
|
|
{
|
|
var ws = wb.Worksheets.FirstOrDefault(w => NormalizeHeader(w.Name).Contains("PARCEL"));
|
|
if (ws == null) return;
|
|
|
|
// acha linha de cabeçalho (onde existem LINHA e CLIENTE)
|
|
var headerRowIndex = FindParcelamentoHeaderRow(ws);
|
|
if (headerRowIndex <= 0) return;
|
|
|
|
// localizar colunas principais
|
|
int colLinha = FindColExact(ws, headerRowIndex, "LINHA");
|
|
int colCliente = FindColExact(ws, headerRowIndex, "CLIENTE");
|
|
int colQtParcelas = FindColContains(ws, headerRowIndex, "QTPARCELAS");
|
|
int colValorCheio = FindColContains(ws, headerRowIndex, "VALORCHEIO");
|
|
int colDesconto = FindColContains(ws, headerRowIndex, "DESCONTO");
|
|
int colValorComDesc = FindColContains(ws, headerRowIndex, "VALORCDESCONTO");
|
|
|
|
if (colLinha == 0 || colCliente == 0 || colValorComDesc == 0) return;
|
|
|
|
// ano e item ficam antes de LINHA (ex: ... 2025 | 1 | 7199... | CONSEF ...)
|
|
int colAno = Math.Max(1, colLinha - 2);
|
|
int colItem = Math.Max(1, colLinha - 1);
|
|
|
|
// meses ficam após VALOR C\ DESCONTO
|
|
var monthCols = FindMonthColumns(ws, headerRowIndex, colValorComDesc + 1);
|
|
if (monthCols.Count == 0) return;
|
|
|
|
// linha acima do header tem os anos (ex: 2005, 2026, 2027) em células mescladas
|
|
var yearRowIndex = Math.Max(1, headerRowIndex - 1);
|
|
|
|
// minAno real (pra corrigir 2005 -> 2025)
|
|
var minAnoRef = FindMinAnoRef(ws, headerRowIndex + 2, colAno, colLinha);
|
|
|
|
// map coluna -> competencia
|
|
var colToCompetencia = BuildCompetenciaMap(ws, headerRowIndex, yearRowIndex, monthCols, minAnoRef);
|
|
|
|
// limpa tabela do parcelamento (cascade resolve meses)
|
|
await _db.ParcelamentoMonthValues.ExecuteDeleteAsync();
|
|
await _db.ParcelamentoLines.ExecuteDeleteAsync();
|
|
|
|
var bufferLines = new List<ParcelamentoLine>(300);
|
|
var bufferMonths = new List<ParcelamentoMonthValue>(1200);
|
|
|
|
// dados começam em header + 2 (porque tem uma linha de total logo abaixo do header)
|
|
var startRow = headerRowIndex + 2;
|
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? startRow;
|
|
|
|
for (int r = startRow; r <= lastRow; r++)
|
|
{
|
|
var linhaRaw = GetCellString(ws, r, colLinha);
|
|
if (string.IsNullOrWhiteSpace(linhaRaw)) break;
|
|
|
|
var linha = OnlyDigits(linhaRaw);
|
|
var cliente = GetCellString(ws, r, colCliente);
|
|
|
|
var anoStr = GetCellString(ws, r, colAno);
|
|
var itemStr = GetCellString(ws, r, colItem);
|
|
|
|
var e = new ParcelamentoLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
AnoRef = TryNullableInt(anoStr),
|
|
Item = TryNullableInt(itemStr),
|
|
Linha = string.IsNullOrWhiteSpace(linha) ? null : linha,
|
|
Cliente = string.IsNullOrWhiteSpace(cliente) ? null : cliente.Trim(),
|
|
QtParcelas = colQtParcelas > 0 ? GetCellString(ws, r, colQtParcelas) : null,
|
|
ValorCheio = colValorCheio > 0 ? TryDecimal(GetCellString(ws, r, colValorCheio)) : null,
|
|
Desconto = colDesconto > 0 ? TryDecimal(GetCellString(ws, r, colDesconto)) : null,
|
|
ValorComDesconto = TryDecimal(GetCellString(ws, r, colValorComDesc)),
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
bufferLines.Add(e);
|
|
|
|
foreach (var col in monthCols)
|
|
{
|
|
var v = ParseDecimalCell(ws.Cell(r, col));
|
|
if (v == null) continue;
|
|
|
|
bufferMonths.Add(new ParcelamentoMonthValue
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParcelamentoLineId = e.Id,
|
|
Competencia = colToCompetencia[col],
|
|
Valor = v.Value
|
|
});
|
|
}
|
|
|
|
if (bufferLines.Count >= 250)
|
|
{
|
|
await _db.ParcelamentoLines.AddRangeAsync(bufferLines);
|
|
await _db.ParcelamentoMonthValues.AddRangeAsync(bufferMonths);
|
|
await _db.SaveChangesAsync();
|
|
bufferLines.Clear();
|
|
bufferMonths.Clear();
|
|
}
|
|
}
|
|
|
|
if (bufferLines.Count > 0)
|
|
{
|
|
await _db.ParcelamentoLines.AddRangeAsync(bufferLines);
|
|
await _db.ParcelamentoMonthValues.AddRangeAsync(bufferMonths);
|
|
await _db.SaveChangesAsync();
|
|
bufferLines.Clear();
|
|
bufferMonths.Clear();
|
|
}
|
|
}
|
|
|
|
// ============================
|
|
// PARCELAMENTO HELPERS (NOVO)
|
|
// ============================
|
|
private static int FindParcelamentoHeaderRow(IXLWorksheet ws)
|
|
{
|
|
// busca nas primeiras 60 linhas uma linha que contenha LINHA e CLIENTE
|
|
for (int r = 1; r <= 60; r++)
|
|
{
|
|
bool hasLinha = false;
|
|
bool hasCliente = false;
|
|
|
|
foreach (var c in ws.Row(r).CellsUsed())
|
|
{
|
|
var k = NormalizeHeader(c.GetString());
|
|
if (k == "LINHA") hasLinha = true;
|
|
if (k == "CLIENTE") hasCliente = true;
|
|
if (hasLinha && hasCliente) return r;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static int FindColExact(IXLWorksheet ws, int row, string header)
|
|
{
|
|
var target = NormalizeHeader(header);
|
|
foreach (var c in ws.Row(row).CellsUsed())
|
|
{
|
|
var k = NormalizeHeader(c.GetString());
|
|
if (k == target) return c.Address.ColumnNumber;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static int FindColContains(IXLWorksheet ws, int row, string headerKeyNoSpaces)
|
|
{
|
|
var target = NormalizeHeader(headerKeyNoSpaces);
|
|
foreach (var c in ws.Row(row).CellsUsed())
|
|
{
|
|
var k = NormalizeHeader(c.GetString());
|
|
if (!string.IsNullOrWhiteSpace(k) && k.Contains(target))
|
|
return c.Address.ColumnNumber;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static List<int> FindMonthColumns(IXLWorksheet ws, int headerRow, int startCol)
|
|
{
|
|
var cols = new List<int>();
|
|
var months = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"JAN","FEV","MAR","ABR","MAI","JUN","JUL","AGO","SET","OUT","NOV","DEZ"
|
|
};
|
|
|
|
int emptyStreak = 0;
|
|
|
|
for (int col = startCol; col <= 220; col++)
|
|
{
|
|
var v = NormalizeHeader(ws.Cell(headerRow, col).GetString());
|
|
if (string.IsNullOrWhiteSpace(v))
|
|
{
|
|
emptyStreak++;
|
|
if (emptyStreak >= 6) break;
|
|
continue;
|
|
}
|
|
|
|
emptyStreak = 0;
|
|
|
|
// mês vem abreviado (JAN/FEV/...)
|
|
var raw = ws.Cell(headerRow, col).GetValue<string>()?.Trim().ToUpperInvariant() ?? "";
|
|
if (months.Contains(raw))
|
|
cols.Add(col);
|
|
}
|
|
|
|
return cols;
|
|
}
|
|
|
|
private static int FindMinAnoRef(IXLWorksheet ws, int startRow, int colAno, int colLinha)
|
|
{
|
|
int min = int.MaxValue;
|
|
|
|
for (int r = startRow; r <= startRow + 250; r++)
|
|
{
|
|
var linha = ws.Cell(r, colLinha).GetValue<string>()?.Trim();
|
|
if (string.IsNullOrWhiteSpace(linha)) break;
|
|
|
|
var sAno = ws.Cell(r, colAno).GetValue<string>()?.Trim();
|
|
if (int.TryParse(OnlyDigits(sAno), out var ano) && ano > 1900)
|
|
min = Math.Min(min, ano);
|
|
}
|
|
|
|
return min == int.MaxValue ? 2025 : min;
|
|
}
|
|
|
|
private static Dictionary<int, DateTime> BuildCompetenciaMap(
|
|
IXLWorksheet ws,
|
|
int headerRow,
|
|
int yearRow,
|
|
List<int> monthCols,
|
|
int minAnoRef)
|
|
{
|
|
int MonthToNum(string abbr) => abbr.ToUpperInvariant() switch
|
|
{
|
|
"JAN" => 1,
|
|
"FEV" => 2,
|
|
"MAR" => 3,
|
|
"ABR" => 4,
|
|
"MAI" => 5,
|
|
"JUN" => 6,
|
|
"JUL" => 7,
|
|
"AGO" => 8,
|
|
"SET" => 9,
|
|
"OUT" => 10,
|
|
"NOV" => 11,
|
|
"DEZ" => 12,
|
|
_ => 1
|
|
};
|
|
|
|
int YearAtColumnMergeAware(int col)
|
|
{
|
|
// caminha para esquerda até achar um número de ano
|
|
for (int c = col; c >= 1; c--)
|
|
{
|
|
var s = ws.Cell(yearRow, c).GetValue<string>()?.Trim();
|
|
if (string.IsNullOrWhiteSpace(s)) continue;
|
|
|
|
if (int.TryParse(OnlyDigits(s), out var y) && y >= 1000)
|
|
{
|
|
// correção: 2005 na sua planilha é 2025 (quando minAnoRef >= 2020)
|
|
if (y < 2010 && minAnoRef >= 2020) y += 20;
|
|
return y;
|
|
}
|
|
}
|
|
|
|
return minAnoRef;
|
|
}
|
|
|
|
var map = new Dictionary<int, DateTime>();
|
|
|
|
foreach (var col in monthCols)
|
|
{
|
|
var mAbbr = ws.Cell(headerRow, col).GetValue<string>()?.Trim().ToUpperInvariant() ?? "";
|
|
var year = YearAtColumnMergeAware(col);
|
|
var month = MonthToNum(mAbbr);
|
|
map[col] = new DateTime(year, month, 1);
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
private static decimal? ParseDecimalCell(IXLCell cell)
|
|
{
|
|
// tenta tipo numérico direto
|
|
if (cell.DataType == XLDataType.Number)
|
|
{
|
|
if (cell.TryGetValue<double>(out var d))
|
|
return (decimal)d;
|
|
}
|
|
|
|
// tenta string
|
|
var s = cell.GetValue<string>()?.Trim();
|
|
return TryDecimal(s);
|
|
}
|
|
|
|
// ==========================================================
|
|
// HELPERS (SEUS - MANTIDOS)
|
|
// ==========================================================
|
|
private static Dictionary<string, int> BuildHeaderMap(IXLRow headerRow)
|
|
{
|
|
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var cell in headerRow.CellsUsed())
|
|
{
|
|
var k = NormalizeHeader(cell.GetString());
|
|
if (!string.IsNullOrWhiteSpace(k) && !map.ContainsKey(k))
|
|
map[k] = cell.Address.ColumnNumber;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
private static DateTime? ToUtc(DateTime? dt)
|
|
{
|
|
if (dt == null) return null;
|
|
var v = dt.Value;
|
|
return v.Kind == DateTimeKind.Utc ? v :
|
|
(v.Kind == DateTimeKind.Local ? v.ToUniversalTime() : DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
|
}
|
|
|
|
private static MobileLineDetailDto ToDetailDto(MobileLine x) => new()
|
|
{
|
|
Id = x.Id,
|
|
Item = x.Item,
|
|
Conta = x.Conta,
|
|
Linha = x.Linha,
|
|
Chip = x.Chip,
|
|
Cliente = x.Cliente,
|
|
Usuario = x.Usuario,
|
|
PlanoContrato = x.PlanoContrato,
|
|
FranquiaVivo = x.FranquiaVivo,
|
|
ValorPlanoVivo = x.ValorPlanoVivo,
|
|
GestaoVozDados = x.GestaoVozDados,
|
|
Skeelo = x.Skeelo,
|
|
VivoNewsPlus = x.VivoNewsPlus,
|
|
VivoTravelMundo = x.VivoTravelMundo,
|
|
VivoGestaoDispositivo = x.VivoGestaoDispositivo,
|
|
ValorContratoVivo = x.ValorContratoVivo,
|
|
FranquiaLine = x.FranquiaLine,
|
|
FranquiaGestao = x.FranquiaGestao,
|
|
LocacaoAp = x.LocacaoAp,
|
|
ValorContratoLine = x.ValorContratoLine,
|
|
Desconto = x.Desconto,
|
|
Lucro = x.Lucro,
|
|
Status = x.Status,
|
|
DataBloqueio = x.DataBloqueio,
|
|
Skil = x.Skil,
|
|
Modalidade = x.Modalidade,
|
|
Cedente = x.Cedente,
|
|
Solicitante = x.Solicitante,
|
|
DataEntregaOpera = x.DataEntregaOpera,
|
|
DataEntregaCliente = x.DataEntregaCliente,
|
|
VencConta = x.VencConta
|
|
};
|
|
|
|
private static void ApplyReservaRule(MobileLine x)
|
|
{
|
|
if ((x.Cliente ?? "").Trim().ToUpper() == "RESERVA" || (x.Usuario ?? "").Trim().ToUpper() == "RESERVA")
|
|
{
|
|
x.Cliente = "RESERVA";
|
|
x.Usuario = "RESERVA";
|
|
x.Skil = "RESERVA";
|
|
}
|
|
}
|
|
|
|
private static int GetCol(Dictionary<string, int> map, string name)
|
|
=> map.TryGetValue(NormalizeHeader(name), out var c) ? c : 0;
|
|
|
|
private static int GetColAny(Dictionary<string, int> map, params string[] headers)
|
|
{
|
|
foreach (var h in headers)
|
|
{
|
|
var k = NormalizeHeader(h);
|
|
if (map.TryGetValue(k, out var c)) return c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static string GetCellByHeader(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
|
{
|
|
var k = NormalizeHeader(header);
|
|
return map.TryGetValue(k, out var c) ? GetCellString(ws, row, c) : "";
|
|
}
|
|
|
|
private static string GetCellString(IXLWorksheet ws, int row, int col)
|
|
{
|
|
if (col <= 0) return "";
|
|
return (ws.Cell(row, col).GetValue<string>() ?? "").Trim();
|
|
}
|
|
|
|
private static DateTime? TryDate(IXLWorksheet ws, int row, Dictionary<string, int> map, string header)
|
|
{
|
|
var k = NormalizeHeader(header);
|
|
if (!map.TryGetValue(k, out var c)) return null;
|
|
return TryDateCell(ws, row, c);
|
|
}
|
|
|
|
private static DateTime? TryDateCell(IXLWorksheet ws, int row, int col)
|
|
{
|
|
if (col <= 0) return null;
|
|
|
|
var cell = ws.Cell(row, col);
|
|
|
|
if (cell.DataType == XLDataType.DateTime)
|
|
return ToUtc(cell.GetDateTime());
|
|
|
|
if (cell.TryGetValue<DateTime>(out var dt))
|
|
return ToUtc(dt);
|
|
|
|
var s = cell.GetValue<string>()?.Trim();
|
|
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
|
|
if (DateTime.TryParse(s, new CultureInfo("pt-BR"), DateTimeStyles.None, out var d))
|
|
return ToUtc(d);
|
|
|
|
return null;
|
|
}
|
|
|
|
private static decimal? TryDecimal(string? s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
|
|
s = s.Replace("R$", "", StringComparison.OrdinalIgnoreCase).Trim();
|
|
|
|
if (decimal.TryParse(s, NumberStyles.Any, new CultureInfo("pt-BR"), out var d)) return d;
|
|
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
|
|
|
// fallback: normaliza separadores se vier "7.469,62"
|
|
var s2 = s.Replace(".", "").Replace(",", ".");
|
|
if (decimal.TryParse(s2, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
|
|
|
return null;
|
|
}
|
|
|
|
private static int TryInt(string s)
|
|
=> int.TryParse(OnlyDigits(s), out var n) ? n : 0;
|
|
|
|
private static int? TryNullableInt(string? s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return null;
|
|
var d = OnlyDigits(s);
|
|
if (string.IsNullOrWhiteSpace(d)) return null;
|
|
return int.TryParse(d, out var n) ? n : null;
|
|
}
|
|
|
|
private static string OnlyDigits(string? s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return "";
|
|
var sb = new StringBuilder();
|
|
foreach (var c in s) if (char.IsDigit(c)) sb.Append(c);
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string NormalizeHeader(string? s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return "";
|
|
s = s.Trim().ToUpperInvariant().Normalize(NormalizationForm.FormD);
|
|
|
|
var sb = new StringBuilder();
|
|
foreach (var c in s)
|
|
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
|
|
sb.Append(c);
|
|
|
|
return sb.ToString()
|
|
.Normalize(NormalizationForm.FormC)
|
|
.Replace(" ", "")
|
|
.Replace("\t", "")
|
|
.Replace("\n", "")
|
|
.Replace("\r", "");
|
|
}
|
|
|
|
// ==========================================================
|
|
// ✅ BILLING HELPERS (resolve duplicados tipo "R$" no PJ)
|
|
// ==========================================================
|
|
private static int GetLastUsedColumn(IXLWorksheet ws, int headerRowIndex)
|
|
{
|
|
var row = ws.Row(headerRowIndex);
|
|
var last = row.LastCellUsed()?.Address.ColumnNumber ?? 1;
|
|
var last2 = ws.LastColumnUsed()?.ColumnNumber() ?? last;
|
|
return Math.Max(last, last2);
|
|
}
|
|
|
|
private static bool RowHasAnyText(IXLRow row)
|
|
{
|
|
foreach (var c in row.CellsUsed())
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(c.GetValue<string>()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static int FindColByAny(IXLRow headerRow, int lastCol, params string[] headers)
|
|
{
|
|
var wanted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var h in headers) wanted.Add(NormalizeHeader(h));
|
|
|
|
for (int col = 1; col <= lastCol; col++)
|
|
{
|
|
var key = NormalizeHeader(headerRow.Cell(col).GetString());
|
|
if (string.IsNullOrWhiteSpace(key)) continue;
|
|
|
|
if (wanted.Contains(key)) return col;
|
|
|
|
foreach (var w in wanted)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(w) && key.Contains(w))
|
|
return col;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static string GetMergedGroupKeyAt(IXLRow groupRow, int col)
|
|
{
|
|
for (int c = col; c >= 1; c--)
|
|
{
|
|
var g = NormalizeHeader(groupRow.Cell(c).GetString());
|
|
if (!string.IsNullOrWhiteSpace(g))
|
|
return g;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
private static int FindColInGroup(IXLRow groupRow, IXLRow headerRow, int lastCol, string groupKey, params string[] headerKeys)
|
|
{
|
|
var gk = NormalizeHeader(groupKey);
|
|
var wanted = headerKeys.Select(NormalizeHeader).ToArray();
|
|
|
|
for (int col = 1; col <= lastCol; col++)
|
|
{
|
|
var groupAtCol = GetMergedGroupKeyAt(groupRow, col);
|
|
if (string.IsNullOrWhiteSpace(groupAtCol)) continue;
|
|
|
|
if (!groupAtCol.Contains(gk)) continue;
|
|
|
|
var h = NormalizeHeader(headerRow.Cell(col).GetString());
|
|
|
|
if (string.IsNullOrWhiteSpace(h) && wanted.Any(w => w == ""))
|
|
return col;
|
|
|
|
foreach (var w in wanted)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(w)) continue;
|
|
|
|
if (h == w) return col;
|
|
if (!string.IsNullOrWhiteSpace(h) && h.Contains(w)) return col;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|