From 3033a2e12acf897fe4ba0e0824902c29f46baa3c Mon Sep 17 00:00:00 2001 From: Giuliano Paschoalino Date: Mon, 13 Oct 2025 14:23:26 -0300 Subject: [PATCH] =?UTF-8?q?Refatora=C3=A7=C3=A3o=20e=20reestrutura=C3=A7?= =?UTF-8?q?=C3=A3o=20geral=20do=20projeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removidas classes, interfaces e testes obsoletos, incluindo `Application.cs`, `NF.cs`, `TERecord.cs`, `FileHandlerFactory.cs`, entre outros. A lógica de inicialização, manipulação de arquivos, validação de notas fiscais e acesso ao banco de dados foi completamente reestruturada ou substituída. Adicionada a classe `Arquivo` para encapsular informações de arquivos XML e PDF, com suporte a múltiplos PDFs associados a um único XML. Refatorada a classe `NF` para melhorar a organização e lidar com novos requisitos, como cálculo de valores e suporte a diferentes unidades de medida. A lógica de processamento de arquivos XML foi otimizada para execução paralela, com melhorias na geração de CSV e associação de arquivos. Configurações antigas, como `appsettings.json`, foram removidas. Dependências e namespaces desnecessários foram excluídos. Adicionados novos tratamentos de exceções, melhorias na modularidade e na clareza do código, além de suporte a novos padrões de saída e codificação. O projeto foi reorganizado para atender a novos requisitos e padrões. --- Application.cs | 91 ------- Domain/Entities/NF.cs | 92 ------- Domain/Entities/TERecord.cs | 41 --- Domain/Repositories/ITERepository.cs | 27 -- Domain/Services/INFParser.cs | 12 - Handlers/FileHandlerFactory.cs | 20 -- Handlers/IFileHandler.cs | 7 - Handlers/IFileHandlerFactory.cs | 7 - Handlers/IValidadorNF.cs | 7 - Handlers/NFResult.cs | 34 --- Handlers/NFValidator.cs | 11 - Handlers/PdfFileHandler.cs | 11 - Handlers/XmlFileHandler.cs | 16 -- Infrastructure/AccessRepository.cs | 133 ---------- Infrastructure/IDatabaseUpdater.cs | 9 - NfProcessorApp.csproj | 8 +- Program.cs | 245 +++++++++++------- Services/FileScanner.cs | 13 - Services/IFileScanner.cs | 7 - Tests/Handlers/NFValidatorTests.cs | 17 -- Tests/Handlers/PdfFileHandlerTests.cs | 16 -- Tests/Handlers/XmlFileHandlerTests.cs | 27 -- Tests/Infrastructure/AccessRepositoryTests.cs | 28 -- Tests/Services/FileScannerTests.cs | 38 --- appsettings.json | 17 -- 25 files changed, 152 insertions(+), 782 deletions(-) delete mode 100644 Application.cs delete mode 100644 Domain/Entities/NF.cs delete mode 100644 Domain/Entities/TERecord.cs delete mode 100644 Domain/Repositories/ITERepository.cs delete mode 100644 Domain/Services/INFParser.cs delete mode 100644 Handlers/FileHandlerFactory.cs delete mode 100644 Handlers/IFileHandler.cs delete mode 100644 Handlers/IFileHandlerFactory.cs delete mode 100644 Handlers/IValidadorNF.cs delete mode 100644 Handlers/NFResult.cs delete mode 100644 Handlers/NFValidator.cs delete mode 100644 Handlers/PdfFileHandler.cs delete mode 100644 Handlers/XmlFileHandler.cs delete mode 100644 Infrastructure/AccessRepository.cs delete mode 100644 Infrastructure/IDatabaseUpdater.cs delete mode 100644 Services/FileScanner.cs delete mode 100644 Services/IFileScanner.cs delete mode 100644 Tests/Handlers/NFValidatorTests.cs delete mode 100644 Tests/Handlers/PdfFileHandlerTests.cs delete mode 100644 Tests/Handlers/XmlFileHandlerTests.cs delete mode 100644 Tests/Infrastructure/AccessRepositoryTests.cs delete mode 100644 Tests/Services/FileScannerTests.cs delete mode 100644 appsettings.json diff --git a/Application.cs b/Application.cs deleted file mode 100644 index 19140dc..0000000 --- a/Application.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NfProcessorApp.Infrastructure; -using NfProcessorApp.Services; -using NfProcessorApp.Handlers; -using NfProcessorApp.Domain.Repositories; - -namespace NfProcessorApp -{ - public class Application - { - private readonly IFileScanner _scanner; - private readonly IFileHandlerFactory _handlerFactory; - private readonly IDatabaseUpdater _dbUpdater; - private readonly ILogger _logger; - private readonly IConfiguration _config; - -#pragma warning disable IDE0290 // Usar construtor primário - public Application( -#pragma warning restore IDE0290 // Usar construtor primário - IFileScanner scanner, - IFileHandlerFactory handlerFactory, - IDatabaseUpdater dbUpdater, - ILogger logger, - IConfiguration config) - { - _scanner = scanner; - _handlerFactory = handlerFactory; - _dbUpdater = dbUpdater; - _logger = logger; - _config = config; - } - - public static Application Build() - { - var config = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", optional: false) - .Build(); - - var services = new ServiceCollection(); - services.AddSingleton(config); - services.AddLogging(builder => builder.AddConsole()); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - var provider = services.BuildServiceProvider(); - return provider.GetRequiredService(); - } - - public void Run() - { - var folder = _config["Settings:InputFolder"] ?? throw new InvalidOperationException("Settings:InputFolder não configurado."); - -#pragma warning disable IDE0301 // Simplificar a inicialização de coleção - var extensions = _config.GetSection("Settings:Extensions").Get() ?? Array.Empty(); -#pragma warning restore IDE0301 // Simplificar a inicialização de coleção - var files = _scanner.ListFiles(folder, extensions); - - foreach (var file in files) - { - try - { - var handler = _handlerFactory.CreateHandler(file); - var result = handler.Process(file); - if (result.IsValid) - { - _dbUpdater.Update(result); - _logger.LogInformation("Updated NF {NumeroNF} from file {file}", result.NumeroNF, file); - } - else - { - _logger.LogWarning("Invalid NF in file {file}", file); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing file {file}", file); - } - } - } - } -} \ No newline at end of file diff --git a/Domain/Entities/NF.cs b/Domain/Entities/NF.cs deleted file mode 100644 index 9305d6b..0000000 --- a/Domain/Entities/NF.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Reflection; -using NfProcessorApp.Domain.Services; -using NfProcessorApp.Handlers; -using Unimake.Business.DFe.Xml.NFe; - -namespace NfProcessorApp.Domain.Entities -{ - public class NF(InfNFe infNFe) : INFParser - { - private readonly InfNFe _infNFe = infNFe; - - public string CNPJ_Comprador - => Convert.ToInt64(_infNFe.Dest.CNPJ ?? _infNFe.Dest.CPF) - .ToString("00000000000000"); - - public string CNPJ_Vendedor - => Convert.ToInt64(_infNFe.Emit.CNPJ) - .ToString("00000000000000"); - - public decimal Montante_operacao - => _infNFe.Det.Sum(d => - d.Prod.UCom == "KWH" - ? d.Prod.QCom / 1000m - : d.Prod.QCom); - - public double Soma2 - => (double)_infNFe.Det.Sum(d => - ((d.Prod.UCom == "KWH" - ? d.Prod.QCom / 1000m - : d.Prod.QCom) - * (d.Prod.UCom == "KWH" - ? d.Prod.VUnCom / 1000m - : d.Prod.VUnCom))); - - public double Valor_unitario => GetValorUnit(); - - public double Valor_final_com_impostos - => _infNFe.Pag.DetPag.Sum(p => p.VPag) != 0 - ? _infNFe.Pag.DetPag.Sum(p => p.VPag) - : _infNFe.Cobr.Fat.VLiq; - - public string RS_Comprador - => _infNFe.Dest.XNome.Replace("&", "&").Replace("&", "&"); - - public string RS_Vendedor - => _infNFe.Emit.XNome.Replace("&", "&").Replace("&", "&"); - - public int Numero_NF - => _infNFe.Ide.NNF; - - public double ICMS_NF => GetICMS(_infNFe.Det.First().Imposto.ICMS); - public string UF_NF { get; set; } = infNFe.Dest.EnderDest.UF.ToString(); - public string UF_Vend { get; set; } = infNFe.Emit.EnderEmit.UF.ToString(); - public NF Parse(string caminhoArquivo) - { - // Implementar lógica de parsing aqui - throw new NotImplementedException(); - } - public double GetICMS(ICMS icms) - { - var propICMS = icms.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - // Opcional: filtrar apenas pelas que começam com "ICMS" - .Where(p => p.Name.StartsWith("ICMS")) - .ToList(); - var primeiraProp = propICMS - .Select(p => new { Prop = p, Valor = p.GetValue(icms) }) - .FirstOrDefault(x => x.Valor != null); - bool test = double - .TryParse(GetICMS(_infNFe.Det.First().Imposto.ICMS) - .ToString(), out double ICMS); - - if (!test && Valor_final_com_impostos != 0) - { - return 1 - (Soma2 / Valor_final_com_impostos); - } - return ICMS; - } - public double GetValorUnit() - { - if (UF_NF == "SP" && UF_Vend == "SP") - { - return Montante_operacao == 0 ? 0 : (Soma2 / (double)Montante_operacao) * (1 - ICMS_NF); - } - else - { - return Montante_operacao == 0 ? 0 : (Soma2 / (double)Montante_operacao); - } - } - public static NFResult Invalid(string filePath) => new() { FilePath = filePath, IsValid = false }; - } -} diff --git a/Domain/Entities/TERecord.cs b/Domain/Entities/TERecord.cs deleted file mode 100644 index 26c9b7c..0000000 --- a/Domain/Entities/TERecord.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Numerics; -using Unimake.Business.DFe.Xml.GNRE; - -namespace NfProcessorApp.Domain.Entities -{ - public class TERecord - { - public BigInteger Cod_TE { get; set; } - public BigInteger Cod_Smart_unidade { get; set; } - public int Mes { get; set; } - public DateTime Hora_LO { get; set; } - public string Operacao { get; set; } - public string Tipo { get; set; } - public DateTime Hora_NF { get; set; } - public double Tempo_NF { get; set; } - public string Contraparte_NF { get; set; } - public string Energia { get; set; } - public double Montante_NF { get; set; } - public double Preco_NF { get; set; } - public double Desconto_NF { get; set; } - public double NF_c_ICMS { get; set; } - public bool NF_recebida { get; set; } - public bool NF_Correta { get; set; } - public string Numero_NF { get; set; } - public string Chave_acesso { get; set; } - public bool Lanc_autom { get; set; } - public double Revend_Mont { get; set; } - public double Revend_Prec { get; set; } - public string CNPJ_comp { get; set; } - public string CNPJ_vend { get; set; } - public double Mont_LO { get; set; } - public double Prec_LO { get; set; } - public string Contrato_CliqCCEE { get; set; } - public string Vig_ini_CliqCCEE { get; set; } - public string Vig_fim_CliqCCEE { get; set; } - public string Submercado { get; set; } - public string Consolidado { get; set; } - public string PerfilCliqCCEE { get; set; } - public string PerfilContr { get; set; } - } -} diff --git a/Domain/Repositories/ITERepository.cs b/Domain/Repositories/ITERepository.cs deleted file mode 100644 index 815e12b..0000000 --- a/Domain/Repositories/ITERepository.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using NfProcessorApp.Domain.Entities; -using NfProcessorApp.Handlers; - -namespace NfProcessorApp.Domain.Repositories -{ - public interface ITERepository - { - /// - /// Retorna todos os registros do banco cujo CNPJ comprador e vendedor coincidam, - /// ordenados pelo campo de data (mais recentes primeiro). - /// - IEnumerable ObterCandidatos(string cnpjComprador, string cnpjVendedor); - - /// - /// Atualiza o registro identificando que a NF foi validada com sucesso - /// e define a data de verificação. - /// - void MarcarComoCorreta(string cod_TE, string numeroNF, DateTime dataVerificacao, string caminhoDestino); - - /// - /// Retorna o caminho de arquivamento para este fornecedor (campo Caminho_NFs). - /// - string ObterCaminhoDestino(string cnpjFornecedor); - } -} diff --git a/Domain/Services/INFParser.cs b/Domain/Services/INFParser.cs deleted file mode 100644 index 2e4ff26..0000000 --- a/Domain/Services/INFParser.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NfProcessorApp.Domain.Entities; -using Unimake.Business.DFe.Xml.NFe; - -namespace NfProcessorApp.Domain.Services -{ - public interface INFParser - { - NF Parse(string caminhoArquivo); - double GetICMS(ICMS icms); - double GetValorUnit(); - } -} \ No newline at end of file diff --git a/Handlers/FileHandlerFactory.cs b/Handlers/FileHandlerFactory.cs deleted file mode 100644 index 51687ab..0000000 --- a/Handlers/FileHandlerFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace NfProcessorApp.Handlers -{ - public class FileHandlerFactory(IServiceProvider provider) : IFileHandlerFactory - { - private readonly IServiceProvider _provider = provider; - - public IFileHandler CreateHandler(string filePath) - { - var ext = Path.GetExtension(filePath).ToLowerInvariant(); - return ext switch - { - ".xml" => _provider.GetRequiredService(), - ".pdf" => _provider.GetRequiredService(), - _ => throw new NotSupportedException($"Extension not supported: {ext}") - }; - } - } -} \ No newline at end of file diff --git a/Handlers/IFileHandler.cs b/Handlers/IFileHandler.cs deleted file mode 100644 index 3cd09cc..0000000 --- a/Handlers/IFileHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NfProcessorApp.Handlers -{ - public interface IFileHandler - { - NFResult Process(string filePath); - } -} \ No newline at end of file diff --git a/Handlers/IFileHandlerFactory.cs b/Handlers/IFileHandlerFactory.cs deleted file mode 100644 index cd99cc1..0000000 --- a/Handlers/IFileHandlerFactory.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NfProcessorApp.Handlers -{ - public interface IFileHandlerFactory - { - IFileHandler CreateHandler(string filePath); - } -} diff --git a/Handlers/IValidadorNF.cs b/Handlers/IValidadorNF.cs deleted file mode 100644 index f95dc41..0000000 --- a/Handlers/IValidadorNF.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NfProcessorApp.Handlers -{ - public interface IValidadorNF - { - bool Validate(NFResult result); - } -} \ No newline at end of file diff --git a/Handlers/NFResult.cs b/Handlers/NFResult.cs deleted file mode 100644 index c2e60d0..0000000 --- a/Handlers/NFResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -using NfProcessorApp.Domain.Entities; -using Unimake.Business.DFe.Xml.NFe; - -namespace NfProcessorApp.Handlers -{ - public class NFResult - { - public string FilePath { get; set; } - public string NumeroNF { get; set; } - public string CNPJ_Comprador { get; set; } - public string CNPJ_Vendedor { get; set; } - public bool IsValid { get; set; } - - public static NFResult FromPath(string filePath) - { - var inf = new NfeProc().LoadFromFile(filePath).NFe.InfNFe.First(); - - if (inf == null) { return Invalid(filePath); } - - var nf = new NF(inf); - - return new NFResult - { - FilePath = filePath, - NumeroNF = nf.Numero_NF.ToString(), - CNPJ_Comprador = nf.CNPJ_Comprador, - CNPJ_Vendedor = nf.CNPJ_Vendedor, - IsValid = false - }; - } - - public static NFResult Invalid(string filePath) => new() { FilePath = filePath, IsValid = false }; - } -} \ No newline at end of file diff --git a/Handlers/NFValidator.cs b/Handlers/NFValidator.cs deleted file mode 100644 index b60fff5..0000000 --- a/Handlers/NFValidator.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NfProcessorApp.Handlers -{ - public class NFValidator : IValidadorNF - { - public bool Validate(NFResult result) - { - // TODO: implementar lógica de validação comparando com banco Access - return true; - } - } -} \ No newline at end of file diff --git a/Handlers/PdfFileHandler.cs b/Handlers/PdfFileHandler.cs deleted file mode 100644 index b0eaa10..0000000 --- a/Handlers/PdfFileHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NfProcessorApp.Handlers -{ - public class PdfFileHandler : IFileHandler - { - public NFResult Process(string filePath) - { - // TODO: implementar leitura básica de PDF - return NFResult.Invalid(filePath); - } - } -} \ No newline at end of file diff --git a/Handlers/XmlFileHandler.cs b/Handlers/XmlFileHandler.cs deleted file mode 100644 index ab75ace..0000000 --- a/Handlers/XmlFileHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Unimake.Business.DFe.Xml.NFe; - -namespace NfProcessorApp.Handlers -{ - public class XmlFileHandler(IValidadorNF validator) : IFileHandler - { - private readonly IValidadorNF _validator = validator; - - public NFResult Process(string filePath) - { - var result = NFResult.FromPath(filePath); - result.IsValid = _validator.Validate(result); - return result; - } - } -} \ No newline at end of file diff --git a/Infrastructure/AccessRepository.cs b/Infrastructure/AccessRepository.cs deleted file mode 100644 index dbf5e5b..0000000 --- a/Infrastructure/AccessRepository.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.OleDb; -using Dapper; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using NfProcessorApp.Domain.Entities; -using NfProcessorApp.Domain.Repositories; -using NfProcessorApp.Handlers; -using NfProcessorApp.Infrastructure; - -namespace NfProcessorApp.Infrastructure -{ - /// - /// Implementação de INotaFiscalRepository usando banco Access - /// - public class AccessRepository : ITERepository, IDatabaseUpdater - { - private readonly string _connectionString; - private readonly ILogger _logger; - - public AccessRepository(IConfiguration config, ILogger logger) - { - ArgumentNullException.ThrowIfNull(config); - ArgumentNullException.ThrowIfNull(logger); - _connectionString = config.GetConnectionString("AccessDb") ?? throw new InvalidOperationException("Connection string 'AccessDb' não configurada."); - _logger = logger; - } - - public IEnumerable ObterCandidatos(string cnpjComprador, string cnpjVendedor) - { - const string sql = @" - SELECT Mont_LO AS Mont_LO, - Prec_LO AS Prec_LO, - NF_c_ICMS AS NF_c_ICMS, - Hora_NF AS Data - FROM Dados_TE DT - INNER JOIN Dados_Cadastrais DC ON DT.Cod_smart_unidade = DC.Cod_smart_unidade - WHERE DC.CNPJ_comp = @CNPJ_comp - AND DC.CNPJ_vend = @CNPJ_vend - ORDER BY Hora_NF DESC"; - - try - { - using var conn = new OleDbConnection(_connectionString); - return conn.Query(sql, new - { - CNPJ_comp = cnpjComprador, - CNPJ_vend = cnpjVendedor - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Erro obtendo candidatos com Dapper para {CNPJ_comp}/{CNPJ_vend}", cnpjComprador, cnpjVendedor); - -#pragma warning disable IDE0301 // Simplificar a inicialização de coleção - return Array.Empty(); -#pragma warning restore IDE0301 // Simplificar a inicialização de coleção - - } - } - - public void MarcarComoCorreta(string cod_TE, string numeroNF, DateTime dataVerificacao, string caminhoDestino) - { - const string sql = @" - UPDATE Dados_Cadastrais - SET Numero_NF = @Numero_NF, - NF_Correta = TRUE, - Hora_NF = @Hora_NF, - Caminho_NFs = @Caminho_NFs - WHERE Cod_TE = @Cod_TE"; - - try - { - using var conn = new OleDbConnection(_connectionString); - var affected = conn.Execute(sql, new - { - Cod_TE = cod_TE, - Numero_NF = numeroNF, - Hora_NF = dataVerificacao, - Caminho_NFs = caminhoDestino - }); - _logger.LogInformation("Registros atualizados: {AffectedRows}", affected); - } - catch (Exception ex) - { - _logger.LogError(ex, "Erro ao marcar NF como correta com Dapper para Cod_TE {cod_TE}", cod_TE); - } - } - - public string ObterCaminhoDestino(string cnpjComprador) - { - const string sql = @" - SELECT Caminho_NFs - FROM Dados_Cadastrais - WHERE CNPJ_CPF = @CNPJ_comp"; - - try - { - using var conn = new OleDbConnection(_connectionString); - return conn.QueryFirstOrDefault(sql, new { CNPJ_comp = cnpjComprador }) - ?? string.Empty; - } - catch (Exception ex) - { - _logger.LogError(ex, "Erro ao obter caminho de destino para o cliente com CNPJ {CNPJ}", cnpjComprador); - return string.Empty; - } - } - - /// - public void Update(NFResult result) - { - ArgumentNullException.ThrowIfNull(result); - - // Data de verificação = momento atual - var dataVerificacao = DateTime.Now; - - // Obtém caminho de destino usando o CNPJ do vendedor - var caminhoDestino = ObterCaminhoDestino(result.CNPJ_Vendedor); - - var candidatos = ObterCandidatos(result.CNPJ_Comprador, result.CNPJ_Vendedor); - - // Chama o método de marcação usando Cod_TE e NumeroNF do resultado - MarcarComoCorreta( - cod_TE: candidatos.First().Cod_TE.ToString(), - numeroNF: result.NumeroNF, - dataVerificacao: dataVerificacao, - caminhoDestino: caminhoDestino - ); - } - } -} \ No newline at end of file diff --git a/Infrastructure/IDatabaseUpdater.cs b/Infrastructure/IDatabaseUpdater.cs deleted file mode 100644 index f0a72d7..0000000 --- a/Infrastructure/IDatabaseUpdater.cs +++ /dev/null @@ -1,9 +0,0 @@ -using NfProcessorApp.Handlers; - -namespace NfProcessorApp.Infrastructure -{ - public interface IDatabaseUpdater - { - void Update(NFResult result); - } -} \ No newline at end of file diff --git a/NfProcessorApp.csproj b/NfProcessorApp.csproj index 5bd993d..301dceb 100644 --- a/NfProcessorApp.csproj +++ b/NfProcessorApp.csproj @@ -23,7 +23,7 @@ - + @@ -42,10 +42,4 @@ - - - Always - - - diff --git a/Program.cs b/Program.cs index 3e714c3..b8d80e1 100644 --- a/Program.cs +++ b/Program.cs @@ -1,34 +1,31 @@ -//namespace NfProcessorApp -//{ -// class Program -// { -// static void Main() -// { -// // Caminho da pasta configurado em appsettings.json -// var app = Application.Build(); -// app.Run(); -// } -// } -//} - -using System.Data; +using System.Reflection; using System.Text; using Unimake.Business.DFe.Xml.NFe; +using System.Data; using Emit = Unimake.Business.DFe.Xml.NFe.Emit; -using System.Reflection; +using System.Xml; +using Unimake.Business.DFe.Validator.NFe; class Program { + static readonly string agora = DateTime.Now.AddMonths(-1).ToString("yyMM"); static async Task Main() { string pasta = @"X:\Back\Controle NFs\NFs"; - var arquivosXML = Directory.GetFiles(pasta, "*.Xml"); - var arquivosPDF = Directory.GetFiles(pasta, "*.pdf"); - var arquivos = arquivosXML.Concat(arquivosPDF); + List arquivosXML = Directory.GetFiles(pasta, "*.Xml").Select(x => new Arquivo(x)).Where(y => y.processar).ToList(); + foreach (var pdf in Directory.GetFiles(pasta, "*.pdf")) + { + if (arquivosXML.Any(x => x.Referencia == Path.GetFileName(pdf).Split(" - ").First())) + { + arquivosXML.First(x => x.Referencia == Path.GetFileName(pdf).Split(" - ").First())?.CaminhoPdf.Add(pdf); + } + } + var arquivos = arquivosXML.OrderBy(x => x.DataDeCriacao); var tarefas = new List>(); var linhas = new List(); + //Processar arquivosXML em paralelo - foreach (string arquivo in arquivos) + foreach (var arquivo in arquivos) { tarefas.Add(ProcessarXMLAsync(arquivo)); //linhas.Add(await ProcessarXMLAsync(arquivo)); @@ -49,30 +46,61 @@ class Program Console.Write(csv.ToString()); //dados.Dispose(); } - static string ProcessarArquivo(string caminhoArquivo) - { - string nomeArquivo = Path.GetFileName(caminhoArquivo); - string tipoArquivo = Path.GetExtension(caminhoArquivo).TrimStart('.'); - string referencia = nomeArquivo.Split(" - ").FirstOrDefault() ?? ""; - // Formatar como CSV (com proteção contra vírgulas) - return $"\"{caminhoArquivo}\";\"{nomeArquivo}\";\"{tipoArquivo}\";\"{referencia}\""; + public class Arquivo + { + public string CaminhoArquivo { get; set; } + public List CaminhoPdf { get; set; } = []; + public string NomeArquivo { get; set; } + public string TipoArquivo { get; set; } + public string Referencia { get; set; } + public DateTime DataDeCriacao { get; } + + public NF NF; + public bool processar = true; + public Arquivo(string caminhoArquivo) + { + //Console.WriteLine($"Lendo {caminhoArquivo}"); + if (caminhoArquivo == "X:\\Back\\Controle NFs\\NFs\\173420 - DANFE 000173420 (1).xml") + { + Thread.Sleep(10); // Simula atraso para testes + } + CaminhoArquivo = caminhoArquivo; + NomeArquivo = Path.GetFileName(caminhoArquivo); + TipoArquivo = Path.GetExtension(caminhoArquivo).TrimStart('.'); + Referencia = NomeArquivo.Split(" - ").FirstOrDefault() ?? ""; + DataDeCriacao = File.GetCreationTime(caminhoArquivo); + + if (TipoArquivo.Equals("xml", StringComparison.CurrentCultureIgnoreCase)) + { + try + { + NF = new NF(caminhoArquivo); + } + catch + { + processar = false; + } + + } + } + } - static async Task ProcessarXMLAsync(string caminhoArquivo) + static async Task ProcessarXMLAsync(Arquivo arquivo) { NFresponse response = new(); string status = ""; try { - if (Path.GetExtension(caminhoArquivo).Equals(".Xml", StringComparison.CurrentCultureIgnoreCase)) + if (arquivo.TipoArquivo.Equals("xml", StringComparison.CurrentCultureIgnoreCase)) { - //if (caminhoArquivo == @"X:\Back\Controle NFs\NFs\345425911,145611 - NFe35250603984862000194550010001466521382788552-procNFe.xml") + //if (arquivo == @"X:\Back\Controle NFs\NFs\345425911,145611 - NFe35250603984862000194550010001466521382788552-procNFe.xml") //{ // Thread.Sleep(1000); //} - NF Current_NF = new(caminhoArquivo); + NF Current_NF = new(arquivo.CaminhoArquivo); - InfNFe infNFe = new NfeProc().LoadFromFile(caminhoArquivo).NFe.InfNFe.First(); + InfNFe infNFe = new NfeProc().LoadFromFile(arquivo.CaminhoArquivo).NFe.InfNFe.First(); Dest Comprador = infNFe.Dest; Emit Vendedor = infNFe.Emit; List Produtos = infNFe.Det; @@ -114,6 +142,7 @@ class Program response.Numero_NF = Current_NF.Numero_NF ?? 0; response.ICMS_NF = Current_NF.ICMS_NF; status = Current_NF.UF_NF ?? ""; + response.Month = Current_NF.Month ?? agora; } } catch (Exception) @@ -122,7 +151,7 @@ class Program } return await Task.Run(() => { - return $"{ProcessarArquivo(caminhoArquivo)};\"{response.CNPJ_Comprador}\";\"{response.CNPJ_Vendedor}\";\"{response.Montante_operação.ToString()?.Replace(',', '.')}\";\"{response.Valor_unitário.ToString()?.Replace(',', '.')}\";\"{response.Valor_final_c_impostos.ToString()?.Replace(',', '.')}\";\"{response.RS_Comprador}\";\"{response.RS_Vendedor}\";\"{response.Numero_NF}\";\"{response.ICMS_NF.ToString()?.Replace(',', '.')}\";\"{status}\""; + return $"\"{arquivo.CaminhoArquivo}\";\"{arquivo.NomeArquivo}\";\"{arquivo.TipoArquivo}\";\"{arquivo.Referencia}\";\"{response.CNPJ_Comprador}\";\"{response.CNPJ_Vendedor}\";\"{response.Montante_operação.ToString()?.Replace(',', '.')}\";\"{response.Valor_unitário.ToString()?.Replace(',', '.')}\";\"{response.Valor_final_c_impostos.ToString()?.Replace(',', '.')}\";\"{response.RS_Comprador}\";\"{response.RS_Vendedor}\";\"{response.Numero_NF}\";\"{response.ICMS_NF.ToString()?.Replace(',', '.')}\";\"{status}\";\"{arquivo.CaminhoPdf.Join("***")}\";\"{response.Month}\""; }); } public static double GetICMS(ICMS icms) @@ -166,76 +195,104 @@ class Program public string RS_Vendedor { get; set; } = ""; public int Numero_NF { get; set; } = 0; public double ICMS_NF { get; set; } = 0; + public string Month { get; set; } = ""; } public class NF - ( - string caminhoArquivo - ) { - readonly InfNFe infNFe = new NfeProc().LoadFromFile(caminhoArquivo).NFe.InfNFe.First(); - Dest Comprador => infNFe.Dest; - Emit Vendedor => infNFe.Emit; - Det Detalhes => infNFe.Det.First(); - List Produtos => infNFe.Det; - Imposto Impostos => Detalhes.Imposto; - public string NomeArquivo => Path.GetFileName(caminhoArquivo); - public string? TipoArquivo => Path.GetExtension(caminhoArquivo).TrimStart('.'); - public string? Referencia => this.NomeArquivo.Split(" - ").FirstOrDefault() ?? ""; - public string CNPJ_Comprador => Convert.ToInt64(Comprador.CNPJ ?? Comprador.CPF).ToString(@"00000000000000"); - public string CNPJ_Vendedor => Convert.ToInt64(Vendedor.CNPJ).ToString(@"00000000000000"); + public NF(string caminhoArquivo) + { + nfeProc = new NfeProc().LoadFromFile(caminhoArquivo); + nfe = nfeProc.NFe; + infNFe = nfe.InfNFe.FirstOrDefault() ?? throw new InvalidOperationException("InfNFe is null or empty."); + Comprador = infNFe.Dest; + Vendedor = infNFe.Emit; + Detalhes = infNFe.Det.First(); + Produtos = infNFe.Det; + Impostos = Detalhes.Imposto; + NomeArquivo = Path.GetFileName(caminhoArquivo); + TipoArquivo = Path.GetExtension(caminhoArquivo).TrimStart('.'); + Referencia = NF.NomeArquivo.Split(" - ").FirstOrDefault() ?? ""; + CNPJ_Comprador = Convert.ToInt64(Comprador.CNPJ ?? Comprador.CPF).ToString(@"00000000000000"); + CNPJ_Vendedor = Convert.ToInt64(Vendedor.CNPJ).ToString(@"00000000000000"); - public decimal Soma2 => - Produtos.Sum(prod => - ( + Soma2 = + Produtos.Sum(prod => + ( + ( + prod.Prod.UCom == "KWH" ? + prod.Prod.QCom / 1000M : + prod.Prod.QCom + ) + ) * + ( + ( + prod.Prod.UCom == "KWH" ? + prod.Prod.VUnCom * 1000M : + prod.Prod.VUnCom + ) + )); + + Montante_operação + = + Produtos.Sum(prod => ( prod.Prod.UCom == "KWH" ? prod.Prod.QCom / 1000M : prod.Prod.QCom ) - ) * - ( - ( - prod.Prod.UCom == "KWH" ? - prod.Prod.VUnCom * 1000M : - prod.Prod.VUnCom - ) - )); - - public decimal Montante_operação - => - Produtos.Sum(prod => - ( - prod.Prod.UCom == "KWH" ? - prod.Prod.QCom / 1000M : - prod.Prod.QCom - ) - ); - public decimal Valor_unitário => - ( - Comprador.EnderDest.UF.ToString() == "SP" && - Vendedor.EnderEmit.UF.ToString() == "SP" - ) - ? - ( - Montante_operação == 0 - ? - 0 - : - (Soma2 / Montante_operação) * (1 - (decimal)ICMS_NF)) - : - ( - Montante_operação == 0 - ? - 0 - : - (Soma2 / Montante_operação) ); - public double Valor_final_c_impostos => (infNFe.Pag.DetPag.Sum(pag => (pag.VPag)) != 0 ? infNFe.Pag.DetPag.Sum(pag => (pag.VPag)) : ((infNFe.Cobr == null ? infNFe.Total.ICMSTot.VNF : infNFe.Cobr.Fat.VLiq))); - public string? RS_Comprador => Comprador.XNome.Replace("&", "&").Replace("&", "&"); - public string? RS_Vendedor => Vendedor.XNome.Replace("&", "&").Replace("&", "&"); - public int? Numero_NF => infNFe.Ide.NNF; - public double ICMS_NF => (GetICMS(Impostos.ICMS) == 0 && Valor_final_c_impostos != 0) ? 1 - ((double)Soma2 / Valor_final_c_impostos) : GetICMS(Impostos.ICMS); - public string? UF_NF => Comprador.EnderDest.UF.ToString(); - public string? UF_Vend => Vendedor.EnderEmit.UF.ToString(); + Valor_unitário = + ( + Comprador.EnderDest.UF.ToString() == "SP" && + Vendedor.EnderEmit.UF.ToString() == "SP" + ) + ? + ( + Montante_operação == 0 + ? + 0 + : + (Soma2 / Montante_operação) * (1 - (decimal)ICMS_NF)) + : + ( + Montante_operação == 0 + ? + 0 + : + (Soma2 / Montante_operação) + ); + Valor_final_c_impostos = (infNFe.Pag.DetPag.Sum(pag => (pag.VPag)) != 0 ? infNFe.Pag.DetPag.Sum(pag => (pag.VPag)) : ((infNFe.Cobr == null ? infNFe.Total.ICMSTot.VNF : infNFe.Cobr.Fat.VLiq))); + RS_Comprador = Comprador.XNome.Replace("&", "&").Replace("&", "&"); + RS_Vendedor = Vendedor.XNome.Replace("&", "&").Replace("&", "&"); + Numero_NF = infNFe.Ide.NNF; + ICMS_NF = (GetICMS(Impostos.ICMS) == 0 && Valor_final_c_impostos != 0) ? 1 - ((double)Soma2 / Valor_final_c_impostos) : GetICMS(Impostos.ICMS); + UF_NF = Comprador.EnderDest.UF.ToString(); + UF_Vend = Vendedor.EnderEmit.UF.ToString(); + Month = infNFe.Ide.DhEmi.AddMonths(-1).ToString("yyMM"); + } + static NfeProc nfeProc; + static NFe nfe; + static InfNFe infNFe; + static Dest Comprador; + static Emit Vendedor; + static Det Detalhes; + static List Produtos; + static Imposto Impostos; + public static string NomeArquivo; + public static string? TipoArquivo; + public string? Referencia; + public string CNPJ_Comprador; + public string CNPJ_Vendedor; + public decimal Soma2; + public decimal Montante_operação; + public decimal Valor_unitário; + public double Valor_final_c_impostos; + public string? RS_Comprador; + public string? RS_Vendedor; + public int? Numero_NF; + public double ICMS_NF; + public string? UF_NF; + public string? UF_Vend; + public string? Month; } -} +} \ No newline at end of file diff --git a/Services/FileScanner.cs b/Services/FileScanner.cs deleted file mode 100644 index 6dbb6ed..0000000 --- a/Services/FileScanner.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NfProcessorApp.Services -{ - public class FileScanner : IFileScanner - { - public IEnumerable ListFiles(string folderPath, string[] extensions) - { - return Directory - .EnumerateFiles(folderPath) - .Where(f => extensions.Contains(Path.GetExtension(f), System.StringComparer.OrdinalIgnoreCase)) - .OrderBy(f => f); - } - } -} \ No newline at end of file diff --git a/Services/IFileScanner.cs b/Services/IFileScanner.cs deleted file mode 100644 index ba3d0af..0000000 --- a/Services/IFileScanner.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NfProcessorApp.Services -{ - public interface IFileScanner - { - IEnumerable ListFiles(string folderPath, string[] extensions); - } -} \ No newline at end of file diff --git a/Tests/Handlers/NFValidatorTests.cs b/Tests/Handlers/NFValidatorTests.cs deleted file mode 100644 index fa29919..0000000 --- a/Tests/Handlers/NFValidatorTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NfProcessorApp.Handlers; -using Xunit; - -namespace NfProcessorApp.Tests.Handlers -{ - public class NFValidatorTests - { - private readonly NFValidator _validator = new(); - - [Fact] - public void Validate_Always_ReturnsTrue() // TODO: adjust when real logic implemented - { - var dummy = NFResult.Invalid("file"); - Assert.True(_validator.Validate(dummy)); - } - } -} \ No newline at end of file diff --git a/Tests/Handlers/PdfFileHandlerTests.cs b/Tests/Handlers/PdfFileHandlerTests.cs deleted file mode 100644 index 15151eb..0000000 --- a/Tests/Handlers/PdfFileHandlerTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NfProcessorApp.Handlers; -using Xunit; - -namespace NfProcessorApp.Tests.Handlers -{ - public class PdfFileHandlerTests - { - [Fact] - public void Process_Always_ReturnsInvalid() - { - var handler = new PdfFileHandler(); - var result = handler.Process("dummy.pdf"); - Assert.False(result.IsValid); - } - } -} \ No newline at end of file diff --git a/Tests/Handlers/XmlFileHandlerTests.cs b/Tests/Handlers/XmlFileHandlerTests.cs deleted file mode 100644 index c5a4429..0000000 --- a/Tests/Handlers/XmlFileHandlerTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NfProcessorApp.Handlers; -using Moq; -using Xunit; - -namespace NfProcessorApp.Tests.Handlers -{ - public class XmlFileHandlerTests - { - [Fact] - public void Process_ValidXml_ReturnsValidResult() - { - // Arrange - var mockValidator = new Mock(); - mockValidator.Setup(v => v.Validate(It.IsAny())).Returns(true); - var handler = new XmlFileHandler(mockValidator.Object); - var tempFile = Path.GetTempFileName().Replace(".tmp", ".xml"); - File.WriteAllText(tempFile, "35123Test551100000000000191Test"); - - // Act - var result = handler.Process(tempFile); - - // Assert - Assert.True(result.IsValid); - Assert.Equal("1", result.NumeroNF); - } - } -} \ No newline at end of file diff --git a/Tests/Infrastructure/AccessRepositoryTests.cs b/Tests/Infrastructure/AccessRepositoryTests.cs deleted file mode 100644 index 236f90e..0000000 --- a/Tests/Infrastructure/AccessRepositoryTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Moq; -using NfProcessorApp.Handlers; -using NfProcessorApp.Infrastructure; -using Xunit; - -namespace NfProcessorApp.Tests.Infrastructure -{ - public class AccessRepositoryTests - { - [Fact] - public void Update_InvalidResult_DoesNotThrow() - { - // Arrange - var inMemConfig = new ConfigurationBuilder() - .AddInMemoryCollection([new KeyValuePair("ConnectionStrings:AccessDb", "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=:memory:;")]) - .Build(); - var mockLogger = new Mock>(); - var repo = new AccessRepository(inMemConfig, mockLogger.Object); - var result = NFResult.Invalid("file"); - - // Act & Assert: should not throw even if DB unaccessible - var ex = Record.Exception(() => "Não concordância"); - Assert.Null(ex); - } - } -} \ No newline at end of file diff --git a/Tests/Services/FileScannerTests.cs b/Tests/Services/FileScannerTests.cs deleted file mode 100644 index f49f9c4..0000000 --- a/Tests/Services/FileScannerTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using NfProcessorApp.Services; -using Xunit; - -namespace NfProcessorApp.Tests.Services -{ - public class FileScannerTests - { - private readonly FileScanner _scanner = new(); - - [Fact] - public void ListFiles_InvalidFolder_ThrowsDirectoryNotFoundException() - { - Assert.Throws(() => _scanner.ListFiles("nonexistent", [".xml"]).ToList()); - } - - [Fact] - public void ListFiles_FiltersByExtension_OnlyReturnsMatchingFiles() - { - // Arrange: create temporary folder with files - var temp = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(temp); - File.WriteAllText(Path.Combine(temp, "a.xml"), ""); - File.WriteAllText(Path.Combine(temp, "b.pdf"), ""); - File.WriteAllText(Path.Combine(temp, "c.txt"), ""); - - // Act - var result = _scanner.ListFiles(temp, [".xml", ".pdf"]).ToList(); - - // Assert - Assert.Contains(Path.Combine(temp, "a.xml"), result); - Assert.Contains(Path.Combine(temp, "b.pdf"), result); - Assert.DoesNotContain(Path.Combine(temp, "c.txt"), result); - - // Cleanup - Directory.Delete(temp, true); - } - } -} \ No newline at end of file diff --git a/appsettings.json b/appsettings.json deleted file mode 100644 index cfa3a9f..0000000 --- a/appsettings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "exclude": [ - "**/bin", - "**/bower_components", - "**/jspm_packages", - "**/node_modules", - "**/obj", - "**/platforms" - ], - "Settings": { - "InputFolder": "X:\\Back\\Controle NFs\\NFs", - "Extensions": [ ".xml", ".pdf" ] - }, - "ConnectionStrings": { - "AccessDb": "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=X:/Middle/Informativo Setorial/Modelo Word/BD1_dados cadastrais e faturas.accdb;Jet OLEDB:Database Password=gds21" - } -} \ No newline at end of file