From 14cdce64afc5eb4174dfc5daed4f020c7f8c2cf1 Mon Sep 17 00:00:00 2001 From: Giuliano Paschoalino Date: Mon, 14 Jul 2025 10:29:38 -0300 Subject: [PATCH] =?UTF-8?q?Import=20inicial:=20migra=C3=A7=C3=A3o=20de=20a?= =?UTF-8?q?rquivos=20da=20rede?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 4 + .gitignore | 3 +- 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 | 51 +++ OldComplianceNFs.csproj | 29 -- OldComplianceNFs.sln | 4 +- Program.cs | 329 ++++++++++-------- Properties/Resources.Designer.cs | 4 +- 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 + 30 files changed, 926 insertions(+), 179 deletions(-) create mode 100644 .editorconfig create mode 100644 Application.cs create mode 100644 Domain/Entities/NF.cs create mode 100644 Domain/Entities/TERecord.cs create mode 100644 Domain/Repositories/ITERepository.cs create mode 100644 Domain/Services/INFParser.cs create mode 100644 Handlers/FileHandlerFactory.cs create mode 100644 Handlers/IFileHandler.cs create mode 100644 Handlers/IFileHandlerFactory.cs create mode 100644 Handlers/IValidadorNF.cs create mode 100644 Handlers/NFResult.cs create mode 100644 Handlers/NFValidator.cs create mode 100644 Handlers/PdfFileHandler.cs create mode 100644 Handlers/XmlFileHandler.cs create mode 100644 Infrastructure/AccessRepository.cs create mode 100644 Infrastructure/IDatabaseUpdater.cs create mode 100644 NfProcessorApp.csproj delete mode 100644 OldComplianceNFs.csproj create mode 100644 Services/FileScanner.cs create mode 100644 Services/IFileScanner.cs create mode 100644 Tests/Handlers/NFValidatorTests.cs create mode 100644 Tests/Handlers/PdfFileHandlerTests.cs create mode 100644 Tests/Handlers/XmlFileHandlerTests.cs create mode 100644 Tests/Infrastructure/AccessRepositoryTests.cs create mode 100644 Tests/Services/FileScannerTests.cs create mode 100644 appsettings.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f5faf64 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS8618: O campo não anulável precisa conter um valor não nulo ao sair do construtor. Considere adicionar o modificador "obrigatório" ou declarar como anulável. +dotnet_diagnostic.CS8618.severity = none diff --git a/.gitignore b/.gitignore index 9491a2f..68c7a52 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,5 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +.aider* diff --git a/Application.cs b/Application.cs new file mode 100644 index 0000000..19140dc --- /dev/null +++ b/Application.cs @@ -0,0 +1,91 @@ +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 new file mode 100644 index 0000000..9305d6b --- /dev/null +++ b/Domain/Entities/NF.cs @@ -0,0 +1,92 @@ +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("&amp;", "&").Replace("&", "&"); + + public string RS_Vendedor + => _infNFe.Emit.XNome.Replace("&amp;", "&").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 new file mode 100644 index 0000000..26c9b7c --- /dev/null +++ b/Domain/Entities/TERecord.cs @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..815e12b --- /dev/null +++ b/Domain/Repositories/ITERepository.cs @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..2e4ff26 --- /dev/null +++ b/Domain/Services/INFParser.cs @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000..51687ab --- /dev/null +++ b/Handlers/FileHandlerFactory.cs @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000..3cd09cc --- /dev/null +++ b/Handlers/IFileHandler.cs @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..cd99cc1 --- /dev/null +++ b/Handlers/IFileHandlerFactory.cs @@ -0,0 +1,7 @@ +namespace NfProcessorApp.Handlers +{ + public interface IFileHandlerFactory + { + IFileHandler CreateHandler(string filePath); + } +} diff --git a/Handlers/IValidadorNF.cs b/Handlers/IValidadorNF.cs new file mode 100644 index 0000000..f95dc41 --- /dev/null +++ b/Handlers/IValidadorNF.cs @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..c2e60d0 --- /dev/null +++ b/Handlers/NFResult.cs @@ -0,0 +1,34 @@ +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 new file mode 100644 index 0000000..b60fff5 --- /dev/null +++ b/Handlers/NFValidator.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..b0eaa10 --- /dev/null +++ b/Handlers/PdfFileHandler.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..ab75ace --- /dev/null +++ b/Handlers/XmlFileHandler.cs @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..dbf5e5b --- /dev/null +++ b/Infrastructure/AccessRepository.cs @@ -0,0 +1,133 @@ +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 new file mode 100644 index 0000000..f0a72d7 --- /dev/null +++ b/Infrastructure/IDatabaseUpdater.cs @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000..5bd993d --- /dev/null +++ b/NfProcessorApp.csproj @@ -0,0 +1,51 @@ + + + + Exe + net9.0-windows + pt-BR + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + Always + + + + diff --git a/OldComplianceNFs.csproj b/OldComplianceNFs.csproj deleted file mode 100644 index c86b8fa..0000000 --- a/OldComplianceNFs.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Exe - net9.0-windows - enable - enable - - - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - diff --git a/OldComplianceNFs.sln b/OldComplianceNFs.sln index 03ace6e..64081d7 100644 --- a/OldComplianceNFs.sln +++ b/OldComplianceNFs.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35527.113 d17.12 +VisualStudioVersion = 17.12.35527.113 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OldComplianceNFs", "OldComplianceNFs.csproj", "{6497B886-8A32-4965-8CA0-3A241B98B81A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NfProcessorApp", "NfProcessorApp.csproj", "{6497B886-8A32-4965-8CA0-3A241B98B81A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Program.cs b/Program.cs index 8708bcf..3e714c3 100644 --- a/Program.cs +++ b/Program.cs @@ -1,45 +1,40 @@ -using System.Data; -using System.Data.OleDb; +//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.Text; -using System.Xml.Linq; -using static System.Net.Mime.MediaTypeNames; +using Unimake.Business.DFe.Xml.NFe; +using Emit = Unimake.Business.DFe.Xml.NFe.Emit; +using System.Reflection; class Program { - static readonly DataTable dados = new(); - static void ObterDadosAccess(string connectionString, string query) - { - using OleDbConnection connection = new(connectionString); - OleDbCommand command = new(query, connection); - connection.Open(); - var reader = command.ExecuteReader(); - - dados.Load(reader); - - //reader.Close(); - //command.Dispose(); - //connection.Close(); - } static async Task Main() { string pasta = @"X:\Back\Controle NFs\NFs"; - var arquivosXML = Directory.GetFiles(pasta, "*.xml"); + var arquivosXML = Directory.GetFiles(pasta, "*.Xml"); var arquivosPDF = Directory.GetFiles(pasta, "*.pdf"); var arquivos = arquivosXML.Concat(arquivosPDF); var tarefas = new List>(); - - // Obter dados do banco de dados Access - string connectionString = @"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"; - string query = "SELECT * FROM Dados_TE LEFT JOIN Dados_Cadastrais ON Dados_TE.Cod_smart_unidade = Dados_Cadastrais.Cod_smart_unidade"; - ObterDadosAccess(connectionString, query); - - // Processar arquivosXML em paralelo + var linhas = new List(); + //Processar arquivosXML em paralelo foreach (string arquivo in arquivos) { tarefas.Add(ProcessarXMLAsync(arquivo)); + //linhas.Add(await ProcessarXMLAsync(arquivo)); } - var linhas = await Task.WhenAll(tarefas); + linhas.AddRange(await Task.WhenAll(tarefas)); // Gerar CSV StringBuilder csv = new(); @@ -49,15 +44,8 @@ class Program csv.AppendLine(linha); } - //StringBuilder csv = new(); - - //foreach (string arquivo in arquivos) - //{ - // csv.AppendLine(await ProcessarXMLAsync(arquivo)); - //} - // Escrever CSV no StdOut (VBA vai capturar) - //Console.OutputEncoding = Encoding.UTF8; + Console.OutputEncoding = Encoding.Latin1; Console.Write(csv.ToString()); //dados.Dispose(); } @@ -72,131 +60,182 @@ class Program } static async Task ProcessarXMLAsync(string caminhoArquivo) { - string? CNPJ_Comprador = ""; - string? CNPJ_Vendedor = ""; - float? Montante_operação = null; - float? Valor_unitário = null; - float? Valor_final_c_impostos = null; - string? RS_Comprador = ""; - string? RS_Vendedor = ""; - int? Numero_NF = null; - float? ICMS_NF = null; - string? UF_NF = ""; - string? UF_Vend = ""; - string? Cod_TE = ""; - - if (Path.GetExtension(caminhoArquivo).Equals(".xml", StringComparison.CurrentCultureIgnoreCase)) - { - if (caminhoArquivo == "X:\\Back\\Controle NFs\\NFs\\581556552,910625 - NFE31250317155730000164550010000669361412505682.XML") { ICMS_NF = 0; } - XDocument xml = XDocument.Load(caminhoArquivo); - XNamespace ns = "http://www.portalfiscal.inf.br/nfe"; // Namespace do XML - - CNPJ_Comprador = xml.Descendants(ns + "dest").Elements(ns + "CNPJ").FirstOrDefault()?.Value ?? xml.Descendants(ns + "dest").Elements(ns + "CPF").FirstOrDefault()?.Value; - CNPJ_Comprador = Convert.ToInt64(CNPJ_Comprador).ToString(@"00000000000000"); - - CNPJ_Vendedor = xml.Descendants(ns + "emit").Elements(ns + "CNPJ").FirstOrDefault()?.Value; - CNPJ_Vendedor = Convert.ToInt64(CNPJ_Vendedor).ToString(@"00000000000000"); - - RS_Comprador = xml.Descendants(ns + "dest").Elements(ns + "xNome").FirstOrDefault()?.Value; - RS_Vendedor = xml.Descendants(ns + "emit").Elements(ns + "xNome").FirstOrDefault()?.Value; - - float soma1 = xml.Descendants(ns + "prod").Sum(prod => (float?)prod.Element(ns + "qCom") ?? 0); - float soma2 = xml.Descendants(ns + "prod").Sum(prod => ((float?)prod.Element(ns + "qCom") ?? 0) * ((float?)prod.Element(ns + "vUnCom") ?? 0)); - - if (CNPJ_Vendedor == "06981176000158" || CNPJ_Vendedor == "06981176002100" || CNPJ_Vendedor == "17155730000164") - { - soma1 /= 1000; - } - - Montante_operação = soma1; - if (soma1 != 0) - { - Valor_unitário = soma2 / soma1; - } - else - { - Valor_unitário = 0; - } - - Valor_final_c_impostos = float.TryParse(xml.Descendants(ns + "pag").Elements(ns + "detPag").Elements(ns + "vPag").FirstOrDefault()?.Value.Replace(".",","), out float valor) ? valor : 0; - - if (Valor_final_c_impostos == 0) - { - Valor_final_c_impostos = float.TryParse(xml.Descendants(ns + "cobr").Elements(ns + "fat").Elements(ns + "vLiq").FirstOrDefault()?.Value.Replace(".", ","), out float valor2) ? valor2 : 0; - } - - Numero_NF = int.Parse(xml.Descendants(ns + "ide").Elements(ns + "nNF").FirstOrDefault()?.Value ?? "0"); - - ICMS_NF = (float.TryParse(xml.Descendants(ns + "imposto").Elements(ns + "ICMS").Elements(ns + "ICMS00").Elements(ns + "pICMS").FirstOrDefault()?.Value.Replace(".", ","), out float valor1) ? valor1 : 0)/100; - - if (ICMS_NF == 0 && Valor_final_c_impostos != 0) { - ICMS_NF = 1 - (soma2 / Valor_final_c_impostos); - } - - UF_NF = xml.Descendants(ns + "dest").Elements(ns + "enderDest").Elements(ns + "UF").FirstOrDefault()?.Value; - UF_Vend = xml.Descendants(ns + "emit").Elements(ns + "enderEmit").Elements(ns + "UF").FirstOrDefault()?.Value; - - if (UF_NF == "SP" && UF_Vend == "SP") - { - Valor_unitário *= (1 - ICMS_NF); - } - - Cod_TE = Id_operacao(CNPJ_Comprador, CNPJ_Vendedor, Montante_operação ?? 0, Valor_unitário ?? 0, Valor_final_c_impostos ?? 0); - } - return await Task.Run(() => - { - // Formatar como CSV (com proteção contra vírgulas) - return $"{ProcessarArquivo(caminhoArquivo)};\"{ - CNPJ_Comprador}\";\"{ - CNPJ_Vendedor}\";\"{ - Montante_operação.ToString()?.Replace(',','.')}\";\"{ - Valor_unitário.ToString()?.Replace(',', '.')}\";\"{ - Valor_final_c_impostos.ToString()?.Replace(',','.')}\";\"{ - RS_Comprador}\";\"{ - RS_Vendedor}\";\"{ - Numero_NF}\";\"{ - ICMS_NF.ToString()?.Replace(',','.')}\";\"{ - UF_NF}\""; - //Cod_TE}\""; - - }); - } - static string Id_operacao(string CNPJ_Comprador, string CNPJ_Vendedor, float Montante_operação, float Valor_unitário, float Valor_final_c_impostos) - { - float tolerancia_montante = 0.01f; - float tolerancia_valor_unitario = 0.005f; - float tolerancia_valor_final = 0.01f; - + NFresponse response = new(); + string status = ""; try { - var selecao = dados.Select($"CNPJ_comp = {CNPJ_Comprador} AND CNPJ_vend = {CNPJ_Vendedor}", $"Mes DESC"); - - foreach (var row in selecao) + if (Path.GetExtension(caminhoArquivo).Equals(".Xml", StringComparison.CurrentCultureIgnoreCase)) { - double mont_LO = row.Field("Mont_LO"); - double prec_LO = row.Field("Prec_LO"); - double NF_c_ICMS = row.Field("NF_c_ICMS"); + //if (caminhoArquivo == @"X:\Back\Controle NFs\NFs\345425911,145611 - NFe35250603984862000194550010001466521382788552-procNFe.xml") + //{ + // Thread.Sleep(1000); + //} + NF Current_NF = new(caminhoArquivo); - bool montDentroDaTolerancia = Math.Abs(mont_LO - Montante_operação) / Montante_operação < tolerancia_montante; - bool unitDentroDaTolerancia = Math.Abs(prec_LO - Valor_unitário) / Valor_unitário < tolerancia_valor_unitario; - bool valorDentroDaTolerancia = Math.Abs(NF_c_ICMS - Valor_final_c_impostos) / Valor_final_c_impostos < tolerancia_valor_final; + InfNFe infNFe = new NfeProc().LoadFromFile(caminhoArquivo).NFe.InfNFe.First(); + Dest Comprador = infNFe.Dest; + Emit Vendedor = infNFe.Emit; + List Produtos = infNFe.Det; - if (montDentroDaTolerancia && unitDentroDaTolerancia) + float soma1 = Produtos.Sum(prod => (float?)prod.Prod.QCom ?? 0); + decimal 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 + ) + )); + + if (Current_NF.CNPJ_Vendedor == "06981176000158" || Current_NF.CNPJ_Vendedor == "06981176002100" || Current_NF.CNPJ_Vendedor == "17155730000164") { - return row.Field("Cod_TE")!; + soma1 /= 1000; } - else if (valorDentroDaTolerancia) + + if (soma1 != 0) { - return row.Field("Cod_TE")!; + decimal Valor_unitário = soma2 / (decimal)soma1; } + + response.CNPJ_Comprador = Current_NF.CNPJ_Comprador; + response.CNPJ_Vendedor = Current_NF.CNPJ_Vendedor; + response.Montante_operação = Current_NF.Montante_operação; + response.Valor_unitário = Current_NF.Valor_unitário; + response.Valor_final_c_impostos = Current_NF.Valor_final_c_impostos; + response.RS_Comprador = Current_NF.RS_Comprador ?? ""; + response.RS_Vendedor = Current_NF.RS_Vendedor ?? ""; + response.Numero_NF = Current_NF.Numero_NF ?? 0; + response.ICMS_NF = Current_NF.ICMS_NF; + status = Current_NF.UF_NF ?? ""; } } catch (Exception) { - return "NAO IDENTIFICADA"; + status = "XML vazio"; } + 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}\""; + }); + } + public static 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); + if (primeiraProp == null) { return 0; } + if (primeiraProp.Valor == null) { return 0; } - return "NAO IDENTIFICADA"; + var tipo = primeiraProp.Valor.GetType(); + var valor = Convert.ChangeType(primeiraProp.Valor, tipo); + + var ListICMSReal = valor.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.Name.StartsWith("PICMS")) + .Select(p => p.GetValue(valor)) + .FirstOrDefault(); + + double ICMSReal = 0; + if (ListICMSReal != null && double.TryParse(ListICMSReal.ToString(), out ICMSReal)) + { + ICMSReal /= 100d; // Convert percentage to decimal + } + return ICMSReal; + } + public class NFresponse + { + public string CNPJ_Comprador { get; set; } = ""; + public string CNPJ_Vendedor { get; set; } = ""; + public decimal Montante_operação { get; set; } = 0; + public decimal Valor_unitário { get; set; } = 0; + public double Valor_final_c_impostos { get; set; } = 0; + public string RS_Comprador { get; set; } = ""; + public string RS_Vendedor { get; set; } = ""; + public int Numero_NF { get; set; } = 0; + public double ICMS_NF { get; set; } = 0; + } + 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 decimal 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 + ) + )); + + 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("&amp;", "&").Replace("&", "&"); + public string? RS_Vendedor => Vendedor.XNome.Replace("&amp;", "&").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(); } } diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 14a874a..a062e33 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace OldComplianceNFs.Properties { +namespace NfProcessorApp.Properties { using System; @@ -39,7 +39,7 @@ namespace OldComplianceNFs.Properties { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OldComplianceNFs.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NfProcessorApp.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/Services/FileScanner.cs b/Services/FileScanner.cs new file mode 100644 index 0000000..6dbb6ed --- /dev/null +++ b/Services/FileScanner.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..ba3d0af --- /dev/null +++ b/Services/IFileScanner.cs @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..fa29919 --- /dev/null +++ b/Tests/Handlers/NFValidatorTests.cs @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..15151eb --- /dev/null +++ b/Tests/Handlers/PdfFileHandlerTests.cs @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..c5a4429 --- /dev/null +++ b/Tests/Handlers/XmlFileHandlerTests.cs @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..236f90e --- /dev/null +++ b/Tests/Infrastructure/AccessRepositoryTests.cs @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..f49f9c4 --- /dev/null +++ b/Tests/Services/FileScannerTests.cs @@ -0,0 +1,38 @@ +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 new file mode 100644 index 0000000..cfa3a9f --- /dev/null +++ b/appsettings.json @@ -0,0 +1,17 @@ +{ + "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