Import inicial: migração de arquivos da rede

This commit is contained in:
Giuliano Paschoalino 2025-07-15 13:25:13 -03:00
parent ad7dc587b2
commit c48ba4710d
15 changed files with 559 additions and 65 deletions

41
AuditComplianceNFs.cs Normal file
View File

@ -0,0 +1,41 @@
// 📋 Copilot Audit Prompt: ComplianceNFs Project Validator
//
// Purpose:
// Automatically review the ComplianceNFs solution and report whether
// each major requirement from the initial design prompt is implemented.
//
// Instructions to Copilot:
// Write a C# console app or NUnit/xUnit test suite that:
// 1. Loads the ComplianceNFs solution structure (projects: .Service, .Monitor, .Core, .Infrastructure).
// 2. Verifies that each project exists and follows naming conventions.
// 3. In ComplianceNFs.Core:
// • Confirms presence of BuyingRecord, EnergyInvoice, InvoiceStatus enum.
// • Confirms interfaces IMailListener, IXmlParser, IPdfParser, IAccessDbRepository, IAttachmentRepository.
// 4. In ComplianceNFs.Infrastructure:
// • Checks for classes implementing each interface (AccessDbRepository, AttachmentRepository, MailListener, XmlParser, PdfParser).
// • Confirms a FileArchiver or equivalent that creates status subfolders under ArchiveBasePath.
// 5. In ComplianceNFs.Service:
// • Confirms a Generic Host (`Host.CreateDefaultBuilder`) and DI registration for all services.
// • Asserts that MailListener is started on host startup.
// • Reads configuration from appsettings.json (AccessConnectionString, PostgresConnectionString, Mail settings, Tolerances, ArchiveBasePath).
// 6. In ComplianceNFs.Monitor:
// • Checks for a WPF application with a MainWindow, MonitorViewModel, and a “Force Scan” button wired to trigger ingestion.
// 7. Workflow & Logic Checks:
// • Ensures MatchingService applies primary key match (CNPJ_comp, CNPJ_vend, MontLO vs MontNF ±1%, PrecLO vs PrecNF ±0.5%).
// • Ensures fallback-by-date logic (invoiceDate minus 1 month → match on Mes/Ano).
// • Ensures multi-invoice sum check for volume matches.
// • Ensures ComplianceService computes implied tax and compares within ±1%.
// • Ensures NotificationService sends email on mismatches.
// • Ensures ArchivingService moves files into subfolders named for each InvoiceStatus.
// 8. Configuration Validations:
// • Reads appsettings.json and asserts each required key exists.
// 9. Produces a summary report (console output or test results) listing “Pass” or “Fail” for each check,
// and points to the file/line where a missing or mis-configured item was detected.
//
// Approach:
// • Use reflection or simple file parsing (e.g. Roslyn) to find classes, interfaces, methods.
// • Use JSON parsing to read and validate appsettings.json.
// • Optionally scaffold unit tests that assert the numeric tolerances and fallback logic via small in-memory examples.
// • Output a clear, human-readable checklist with file paths and suggestions for fixes.
//
// Begin generating the auditing code now.

View File

@ -5,6 +5,7 @@ namespace ComplianceNFs.Core.Entities
{
public class EnergyInvoice
{
[System.ComponentModel.DataAnnotations.Key]
public required string MailId { get; set; }
public required string ConversationId { get; set; }
public required string SupplierEmail { get; set; }

View File

@ -6,6 +6,7 @@ using Xunit;
using Moq;
using Npgsql;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
namespace ComplianceNFs.Infrastructure.Tests
{
@ -15,8 +16,11 @@ namespace ComplianceNFs.Infrastructure.Tests
public async Task SaveRawAsync_DoesNotThrow_WithValidInvoice()
{
// Arrange
var options = new DbContextOptionsBuilder<ComplianceNFsDbContext>()
.Options;
using var dbContext = new ComplianceNFsDbContext(options);
var mockLogger = new Mock<ILogger<AttachmentRepository>>();
var repo = new AttachmentRepository("Host=localhost;Port=5432;Database=test;Username=test;Password=test", mockLogger.Object);
var repo = new AttachmentRepository(dbContext, mockLogger.Object);
var invoice = new EnergyInvoice
{
MailId = "mailid",
@ -27,9 +31,8 @@ namespace ComplianceNFs.Infrastructure.Tests
Filename = "file.xml",
Status = InvoiceStatus.Validated
};
// This is a placeholder: in a real test, use a test DB or mock NpgsqlConnection/Command
// For demonstration, we'll just check that the method can be called without throwing
await Assert.ThrowsAnyAsync<Exception>(async () => await repo.SaveRawAsync(invoice));
// Act & Assert
await repo.SaveRawAsync(invoice); // Should not throw
}
}
}

View File

@ -0,0 +1,151 @@
using System.Reflection;
using System.Text.Json;
using System.IO;
using NUnit.Framework;
namespace ComplianceNFs.Tests
{
[TestFixture]
public class AuditComplianceNFsTest
{
private readonly string solutionRoot = @"x:\Back\Carteira x.x\Codigo\ComplianceNFs";
[Test]
public void ProjectStructure_ShouldContainAllProjects()
{
var expectedProjects = new[]
{
"ComplianceNFs.Core",
"ComplianceNFs.Infrastructure",
"ComplianceNFs.Service",
"ComplianceNFs.Monitor"
};
foreach (var proj in expectedProjects)
{
var csproj = Directory.GetFiles(solutionRoot, $"{proj}.csproj", SearchOption.AllDirectories).FirstOrDefault();
NUnit.Framework.Assert.That(csproj, Is.Not.Null, $"Project {proj} not found.");
}
}
[Test]
public void CoreProject_ShouldContainRequiredTypesAndInterfaces()
{
var coreDll = Directory.GetFiles(solutionRoot, "ComplianceNFs.Core.dll", SearchOption.AllDirectories).FirstOrDefault();
NUnit.Framework.Assert.That(coreDll, NUnit.Framework.Is.Not.Null, "ComplianceNFs.Core.dll not found. Build the solution first.");
var asm = Assembly.LoadFrom(coreDll!);
NUnit.Framework.Assert.That(asm.GetType("ComplianceNFs.Core.BuyingRecord"), NUnit.Framework.Is.Not.Null, "BuyingRecord class missing.");
NUnit.Framework.Assert.That(asm.GetType("ComplianceNFs.Core.EnergyInvoice"), NUnit.Framework.Is.Not.Null, "EnergyInvoice class missing.");
NUnit.Framework.Assert.That(asm.GetType("ComplianceNFs.Core.InvoiceStatus"), NUnit.Framework.Is.Not.Null, "InvoiceStatus enum missing.");
var interfaces = new[]
{
"IMailListener",
"IXmlParser",
"IPdfParser",
"IAccessDbRepository",
"IAttachmentRepository"
};
foreach (var iface in interfaces)
{
NUnit.Framework.Assert.That(asm.GetType($"ComplianceNFs.Core.{iface}"), NUnit.Framework.Is.Not.Null, $"Interface {iface} missing.");
}
}
[Test]
public void InfrastructureProject_ShouldImplementCoreInterfaces()
{
var infraDll = Directory.GetFiles(solutionRoot, "ComplianceNFs.Infrastructure.dll", SearchOption.AllDirectories).FirstOrDefault();
var coreDll = Directory.GetFiles(solutionRoot, "ComplianceNFs.Core.dll", SearchOption.AllDirectories).FirstOrDefault();
NUnit.Framework.Assert.That(infraDll, NUnit.Framework.Is.Not.Null, "ComplianceNFs.Infrastructure.dll not found.");
NUnit.Framework.Assert.That(coreDll, NUnit.Framework.Is.Not.Null, "ComplianceNFs.Core.dll not found.");
var infraAsm = Assembly.LoadFrom(infraDll!);
var coreAsm = Assembly.LoadFrom(coreDll!);
var interfaceImpls = new (string iface, string impl)[]
{
("IMailListener", "MailListener"),
("IXmlParser", "XmlParser"),
("IPdfParser", "PdfParser"),
("IAccessDbRepository", "AccessDbRepository"),
("IAttachmentRepository", "AttachmentRepository")
};
foreach (var (iface, impl) in interfaceImpls)
{
var ifaceType = coreAsm.GetType($"ComplianceNFs.Core.{iface}");
var implType = infraAsm.GetType($"ComplianceNFs.Infrastructure.{impl}");
NUnit.Framework.Assert.That(implType, NUnit.Framework.Is.Not.Null, $"Implementation {impl} missing.");
NUnit.Framework.Assert.That(ifaceType, NUnit.Framework.Is.Not.Null, $"Interface {iface} missing in core assembly.");
NUnit.Framework.Assert.That(ifaceType!.IsAssignableFrom(implType!), NUnit.Framework.Is.True, $"{impl} does not implement {iface}.");
}
var archiver = infraAsm.GetTypes().FirstOrDefault(t =>
t.Name.Contains("Archiver") || t.Name.Contains("FileArchiver"));
NUnit.Framework.Assert.That(archiver, NUnit.Framework.Is.Not.Null, "FileArchiver or equivalent not found.");
}
[Test]
public void ServiceProject_ShouldUseGenericHostAndRegisterServices()
{
var serviceDir = Directory.GetDirectories(solutionRoot, "ComplianceNFs.Service", SearchOption.TopDirectoryOnly).FirstOrDefault();
NUnit.Framework.Assert.That(serviceDir, NUnit.Framework.Is.Not.Null, "ComplianceNFs.Service directory not found.");
var programFile = Directory.GetFiles(serviceDir!, "Program.cs", SearchOption.AllDirectories).FirstOrDefault();
NUnit.Framework.Assert.That(programFile, NUnit.Framework.Is.Not.Null, "Program.cs not found in Service project.");
var code = File.ReadAllText(programFile!);
NUnit.Framework.Assert.That(code.Contains("Host.CreateDefaultBuilder"), NUnit.Framework.Is.True, "Generic Host not used.");
NUnit.Framework.Assert.That(code.Contains("ConfigureServices"), NUnit.Framework.Is.True, "DI registration missing.");
NUnit.Framework.Assert.That(code.Contains("MailListener"), NUnit.Framework.Is.True, "MailListener not referenced in startup.");
NUnit.Framework.Assert.That(code.Contains("appsettings.json"), NUnit.Framework.Is.True, "appsettings.json not referenced.");
}
[Test]
public void MonitorProject_ShouldContainWPFArtifacts()
{
var monitorDir = Directory.GetDirectories(solutionRoot, "ComplianceNFs.Monitor", SearchOption.TopDirectoryOnly).FirstOrDefault();
NUnit.Framework.Assert.That(monitorDir, NUnit.Framework.Is.Not.Null, "ComplianceNFs.Monitor directory not found.");
NUnit.Framework.Assert.That(File.Exists(Path.Combine(monitorDir!, "MainWindow.xaml")), NUnit.Framework.Is.True, "MainWindow.xaml missing.");
NUnit.Framework.Assert.That(File.Exists(Path.Combine(monitorDir!, "MonitorViewModel.cs")), NUnit.Framework.Is.True, "MonitorViewModel.cs missing.");
var mainWindowCode = File.ReadAllText(Path.Combine(monitorDir!, "MainWindow.xaml"));
NUnit.Framework.Assert.That(mainWindowCode.Contains("Force Scan"), NUnit.Framework.Is.True, "Force Scan button not found in MainWindow.xaml.");
}
[Test]
public void AppSettings_ShouldContainRequiredKeys()
{
var appsettings = Directory.GetFiles(solutionRoot, "appsettings.json", SearchOption.AllDirectories).FirstOrDefault();
NUnit.Framework.Assert.That(appsettings, NUnit.Framework.Is.Not.Null, "appsettings.json not found.");
var json = File.ReadAllText(appsettings!);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
NUnit.Framework.Assert.That(root.TryGetProperty("AccessConnectionString", out _), NUnit.Framework.Is.True, "AccessConnectionString missing.");
NUnit.Framework.Assert.That(root.TryGetProperty("PostgresConnectionString", out _), NUnit.Framework.Is.True, "PostgresConnectionString missing.");
NUnit.Framework.Assert.That(root.TryGetProperty("Mail", out _), NUnit.Framework.Is.True, "Mail settings missing.");
NUnit.Framework.Assert.That(root.TryGetProperty("Tolerances", out _), NUnit.Framework.Is.True, "Tolerances missing.");
NUnit.Framework.Assert.That(root.TryGetProperty("ArchiveBasePath", out _), NUnit.Framework.Is.True, "ArchiveBasePath missing.");
}
[Test]
public void MatchingService_ShouldApplyTolerances()
{
// This is a stub: in a real test, instantiate MatchingService and test with in-memory data.
NUnit.Framework.Assert.Pass("Stub: Implement in-memory test for MatchingService tolerances (±1%, ±0.5%).");
}
[Test]
public void ComplianceService_ShouldComputeImpliedTaxWithinTolerance()
{
// This is a stub: in a real test, instantiate ComplianceService and test with in-memory data.
NUnit.Framework.Assert.Pass("Stub: Implement in-memory test for ComplianceService implied tax logic (±1%).");
}
[Test]
public void NotificationService_ShouldSendEmailOnMismatch()
{
// This is a stub: in a real test, mock NotificationService and verify email send on mismatch.
NUnit.Framework.Assert.Pass("Stub: Implement NotificationService email send test.");
}
[Test]
public void ArchivingService_ShouldMoveFilesToStatusFolders()
{
// This is a stub: in a real test, mock file system and verify archiving logic.
NUnit.Framework.Assert.Pass("Stub: Implement ArchivingService file move test.");
}
}
}

View File

@ -14,6 +14,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
</ItemGroup>

View File

@ -13,8 +13,8 @@ namespace ComplianceNFs.Infrastructure.Tests
public FileArchiverTests()
{
_testBasePath = Path.Combine(Path.GetTempPath(), "ComplianceNFsTestArchive");
if (Directory.Exists(_testBasePath))
Directory.Delete(_testBasePath, true);
// if (Directory.Exists(_testBasePath))
// Directory.Delete(_testBasePath, true);
}
[Fact]
@ -26,18 +26,18 @@ namespace ComplianceNFs.Infrastructure.Tests
MailId = "test-mail-id",
ConversationId = "test-conv-id",
SupplierEmail = "test@supplier.com",
Filename = "testfile.txt",
Filename = @"X:\Back\Controle NFs\NFs\temp\99451152,9268928 - procNFE52250318384740000134550020000065021906003771.xml",
Status = InvoiceStatus.Validated,
// Add required fields for null safety
ReceivedDate = DateTime.Now,
InvoiceId = 1
};
var data = new byte[] { 1, 2, 3, 4 };
var data = await File.ReadAllBytesAsync(invoice.Filename);
archiver.ArchiveAsync(invoice);
var expectedFolder = Path.Combine(_testBasePath, "Validated");
var expectedFile = Path.Combine(expectedFolder, "testfile.txt");
var expectedFile = Path.Combine(expectedFolder, @"X:\Back\Controle NFs\NFs\temp\99451152,9268928 - procNFE52250318384740000134550020000065021906003771.xml");
Assert.True(Directory.Exists(expectedFolder));
Assert.True(File.Exists(expectedFile));
var fileData = await File.ReadAllBytesAsync(expectedFile);
@ -53,18 +53,18 @@ namespace ComplianceNFs.Infrastructure.Tests
MailId = "test-mail-id",
ConversationId = "test-conv-id",
SupplierEmail = "test@supplier.com",
Filename = "testfile.txt",
Filename = @"X:\Back\Controle NFs\NFs\temp\99451152,9268928 - procNFE52250318384740000134550020000065021906003771.xml",
Status = InvoiceStatus.Validated,
// Add required fields for null safety
ReceivedDate = DateTime.Now,
InvoiceId = 1
};
var data2 = new byte[] { 9, 8, 7 };
var data2 = await File.ReadAllBytesAsync(invoice.Filename);
archiver.ArchiveAsync(invoice);
archiver.ArchiveAsync(invoice);
var expectedFile = Path.Combine(_testBasePath, "Validated", "testfile.txt");
var expectedFile = Path.Combine(_testBasePath, "Validated", @"X:\Back\Controle NFs\NFs\temp\99451152,9268928 - procNFE52250318384740000134550020000065021906003771.xml");
var fileData = await File.ReadAllBytesAsync(expectedFile);
Assert.Equal(data2, fileData);
}

View File

@ -6,12 +6,19 @@
<ItemGroup>
<PackageReference Include="MailKit" Version="4.12.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Office.Interop.Outlook" Version="15.0.4797.1004" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="System.Data.OleDb" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Unimake.DFe" Version="20250610.1145.39" />
</ItemGroup>

View File

@ -6,51 +6,32 @@ using Npgsql;
using Newtonsoft.Json;
using System.Numerics;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
namespace ComplianceNFs.Infrastructure.Repositories
{
// Placeholder: fill in actual SQL and mapping logic
public class AttachmentRepository(string connectionString, ILogger<AttachmentRepository> logger) : IAttachmentRepository
public class AttachmentRepository : IAttachmentRepository
{
private readonly ComplianceNFsDbContext _dbContext;
private readonly ILogger<AttachmentRepository> _logger;
public AttachmentRepository(ComplianceNFsDbContext dbContext, ILogger<AttachmentRepository> logger)
{
_dbContext = dbContext;
_logger = logger;
}
public async Task SaveRawAsync(EnergyInvoice invoice)
{
try
{
using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = @"INSERT INTO attachments (
filename, supplier_email, conversation_id, received_date, md5, cnpj_comp, cnpj_vend, mont_nf, prec_nf, valor_sem_imp, valor_com_imp, rs_comp, rs_vend, numero_nf, icms_nf, uf_comp, uf_vend, matched_cod_te, status, discrepancy, metadata
) VALUES (
@filename, @supplier_email, @conversation_id, @received_date, @md5, @cnpj_comp, @cnpj_vend, @mont_nf, @prec_nf, @valor_sem_imp, @valor_com_imp, @rs_comp, @rs_vend, @numero_nf, @icms_nf, @uf_comp, @uf_vend, @matched_cod_te, @status, @discrepancy, @metadata
)";
cmd.Parameters.AddWithValue("@filename", invoice.Filename);
cmd.Parameters.AddWithValue("@supplier_email", invoice.SupplierEmail);
cmd.Parameters.AddWithValue("@conversation_id", (object?)invoice.ConversationId ?? DBNull.Value);
cmd.Parameters.AddWithValue("@received_date", invoice.ReceivedDate);
cmd.Parameters.AddWithValue("@md5", (object?)invoice.Md5 ?? DBNull.Value);
cmd.Parameters.AddWithValue("@cnpj_comp", (object?)invoice.CnpjComp ?? DBNull.Value);
cmd.Parameters.AddWithValue("@cnpj_vend", (object?)invoice.CnpjVend ?? DBNull.Value);
cmd.Parameters.AddWithValue("@mont_nf", (object?)invoice.MontNF ?? DBNull.Value);
cmd.Parameters.AddWithValue("@prec_nf", (object?)invoice.PrecNF ?? DBNull.Value);
cmd.Parameters.AddWithValue("@valor_sem_imp", (object?)invoice.ValorSemImpostos ?? DBNull.Value);
cmd.Parameters.AddWithValue("@valor_com_imp", (object?)invoice.ValorFinalComImpostos ?? DBNull.Value);
cmd.Parameters.AddWithValue("@rs_comp", (object?)invoice.RsComp ?? DBNull.Value);
cmd.Parameters.AddWithValue("@rs_vend", (object?)invoice.RsVend ?? DBNull.Value);
cmd.Parameters.AddWithValue("@numero_nf", (object?)invoice.NumeroNF ?? DBNull.Value);
cmd.Parameters.AddWithValue("@icms_nf", (object?)invoice.IcmsNF ?? DBNull.Value);
cmd.Parameters.AddWithValue("@uf_comp", (object?)invoice.UfComp ?? DBNull.Value);
cmd.Parameters.AddWithValue("@uf_vend", (object?)invoice.UfVend ?? DBNull.Value);
cmd.Parameters.AddWithValue("@matched_cod_te", (object?)invoice.MatchedCodTE ?? DBNull.Value);
cmd.Parameters.AddWithValue("@status", invoice.Status.ToString());
cmd.Parameters.AddWithValue("@discrepancy", (object?)invoice.DiscrepancyNotes ?? DBNull.Value);
cmd.Parameters.AddWithValue("@metadata", Newtonsoft.Json.JsonConvert.SerializeObject(invoice));
await cmd.ExecuteNonQueryAsync();
logger.LogInformation("Saved raw invoice {InvoiceId} to attachments table.", invoice.InvoiceId);
await _dbContext.EnergyInvoices.AddAsync(invoice);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Saved raw invoice {InvoiceId} to EnergyInvoices table.", invoice.InvoiceId);
}
catch (Exception ex)
{
logger.LogError(ex, "Error saving raw invoice {InvoiceId} to attachments table.", invoice.InvoiceId);
_logger.LogError(ex, "Error saving raw invoice {InvoiceId} to EnergyInvoices table.", invoice.InvoiceId);
throw;
}
}
@ -59,20 +40,20 @@ namespace ComplianceNFs.Infrastructure.Repositories
{
try
{
using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = @"UPDATE attachments SET matched_cod_te = @matched_cod_te, status = @status, discrepancy = @discrepancy WHERE invoice_id = @invoice_id";
cmd.Parameters.AddWithValue("@matched_cod_te", matchedCodTE);
cmd.Parameters.AddWithValue("@status", status.ToString());
cmd.Parameters.AddWithValue("@discrepancy", (object?)notes ?? DBNull.Value);
cmd.Parameters.AddWithValue("@invoice_id", invoiceId);
await cmd.ExecuteNonQueryAsync();
logger.LogInformation("Updated match for invoice {InvoiceId}.", invoiceId);
var invoice = await _dbContext.EnergyInvoices.FirstOrDefaultAsync(e => e.InvoiceId == invoiceId);
if (invoice == null)
{
throw new InvalidOperationException($"Invoice with ID {invoiceId} not found.");
}
invoice.MatchedCodTE = matchedCodTE;
invoice.Status = status;
invoice.DiscrepancyNotes = notes;
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Updated match for invoice {InvoiceId}.", invoiceId);
}
catch (Exception ex)
{
logger.LogError(ex, "Error updating match for invoice {InvoiceId}.", invoiceId);
_logger.LogError(ex, "Error updating match for invoice {InvoiceId}.", invoiceId);
throw;
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore;
using ComplianceNFs.Core.Entities;
namespace ComplianceNFs.Infrastructure.Repositories
{
public class ComplianceNFsDbContext : DbContext
{
public ComplianceNFsDbContext(DbContextOptions<ComplianceNFsDbContext> options) : base(options) { }
public DbSet<EnergyInvoice> EnergyInvoices { get; set; }
// Add other DbSets as needed (e.g., BuyingRecord, etc.)
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace ComplianceNFs.Infrastructure.Repositories
{
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ComplianceNFsDbContext>
{
public ComplianceNFsDbContext CreateDbContext(string[] args)
{
// Build config to read connection string from appsettings.json
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true)
.Build();
var connectionString = config["PostgresConnectionString"] ??
"Host=localhost;Database=compliancenfs;Username=postgres;Password=postgres";
var optionsBuilder = new DbContextOptionsBuilder<ComplianceNFsDbContext>();
optionsBuilder.UseNpgsql(connectionString);
return new ComplianceNFsDbContext(optionsBuilder.Options);
}
}
}

View File

@ -0,0 +1,107 @@
// <auto-generated />
using System;
using System.Numerics;
using ComplianceNFs.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ComplianceNFs.Infrastructure.Repositories.Migrations
{
[DbContext(typeof(ComplianceNFsDbContext))]
[Migration("20250704165647_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("ComplianceNFs.Core.Entities.EnergyInvoice", b =>
{
b.Property<string>("MailId")
.HasColumnType("text");
b.Property<string>("CnpjComp")
.HasColumnType("text");
b.Property<string>("CnpjVend")
.HasColumnType("text");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("DiscrepancyNotes")
.HasColumnType("text");
b.Property<string>("Filename")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("IcmsNF")
.HasColumnType("numeric");
b.Property<int>("InvoiceId")
.HasColumnType("integer");
b.Property<BigInteger?>("MatchedCodTE")
.HasColumnType("numeric");
b.Property<string>("Md5")
.HasColumnType("text");
b.Property<decimal?>("MontNF")
.HasColumnType("numeric");
b.Property<string>("NumeroNF")
.HasColumnType("text");
b.Property<decimal?>("PrecNF")
.HasColumnType("numeric");
b.Property<DateTime>("ReceivedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("RsComp")
.HasColumnType("text");
b.Property<string>("RsVend")
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("SupplierEmail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UfComp")
.HasColumnType("text");
b.Property<string>("UfVend")
.HasColumnType("text");
b.Property<decimal?>("ValorFinalComImpostos")
.HasColumnType("numeric");
b.Property<decimal?>("ValorSemImpostos")
.HasColumnType("numeric");
b.HasKey("MailId");
b.ToTable("EnergyInvoices");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Numerics;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ComplianceNFs.Infrastructure.Repositories.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EnergyInvoices",
columns: table => new
{
MailId = table.Column<string>(type: "text", nullable: false),
ConversationId = table.Column<string>(type: "text", nullable: false),
SupplierEmail = table.Column<string>(type: "text", nullable: false),
ReceivedDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
InvoiceId = table.Column<int>(type: "integer", nullable: false),
Filename = table.Column<string>(type: "text", nullable: false),
Md5 = table.Column<string>(type: "text", nullable: true),
CnpjComp = table.Column<string>(type: "text", nullable: true),
CnpjVend = table.Column<string>(type: "text", nullable: true),
MontNF = table.Column<decimal>(type: "numeric", nullable: true),
PrecNF = table.Column<decimal>(type: "numeric", nullable: true),
ValorSemImpostos = table.Column<decimal>(type: "numeric", nullable: true),
ValorFinalComImpostos = table.Column<decimal>(type: "numeric", nullable: true),
RsComp = table.Column<string>(type: "text", nullable: true),
RsVend = table.Column<string>(type: "text", nullable: true),
NumeroNF = table.Column<string>(type: "text", nullable: true),
IcmsNF = table.Column<decimal>(type: "numeric", nullable: true),
UfComp = table.Column<string>(type: "text", nullable: true),
UfVend = table.Column<string>(type: "text", nullable: true),
MatchedCodTE = table.Column<BigInteger>(type: "numeric", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
DiscrepancyNotes = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_EnergyInvoices", x => x.MailId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EnergyInvoices");
}
}
}

View File

@ -0,0 +1,104 @@
// <auto-generated />
using System;
using System.Numerics;
using ComplianceNFs.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ComplianceNFs.Infrastructure.Repositories.Migrations
{
[DbContext(typeof(ComplianceNFsDbContext))]
partial class ComplianceNFsDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("ComplianceNFs.Core.Entities.EnergyInvoice", b =>
{
b.Property<string>("MailId")
.HasColumnType("text");
b.Property<string>("CnpjComp")
.HasColumnType("text");
b.Property<string>("CnpjVend")
.HasColumnType("text");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("DiscrepancyNotes")
.HasColumnType("text");
b.Property<string>("Filename")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("IcmsNF")
.HasColumnType("numeric");
b.Property<int>("InvoiceId")
.HasColumnType("integer");
b.Property<BigInteger?>("MatchedCodTE")
.HasColumnType("numeric");
b.Property<string>("Md5")
.HasColumnType("text");
b.Property<decimal?>("MontNF")
.HasColumnType("numeric");
b.Property<string>("NumeroNF")
.HasColumnType("text");
b.Property<decimal?>("PrecNF")
.HasColumnType("numeric");
b.Property<DateTime>("ReceivedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("RsComp")
.HasColumnType("text");
b.Property<string>("RsVend")
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("SupplierEmail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UfComp")
.HasColumnType("text");
b.Property<string>("UfVend")
.HasColumnType("text");
b.Property<decimal?>("ValorFinalComImpostos")
.HasColumnType("numeric");
b.Property<decimal?>("ValorSemImpostos")
.HasColumnType("numeric");
b.HasKey("MailId");
b.ToTable("EnergyInvoices");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -8,6 +8,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-preview.4.25258.110" />

View File

@ -9,6 +9,8 @@ using ComplianceNFs.Infrastructure.Parsers;
using ComplianceNFs.Infrastructure.Archiving;
using ComplianceNFs.Core.Application.Services;
using ComplianceNFs.Core.Application;
using Microsoft.EntityFrameworkCore;
using ComplianceNFs.Infrastructure;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
@ -18,6 +20,9 @@ IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
var config = context.Configuration;
// Register EF Core DbContext for PostgreSQL
services.AddDbContext<ComplianceNFsDbContext>(options =>
options.UseNpgsql(config["PostgresConnectionString"]));
// Register infrastructure
services.AddSingleton<IAccessDbRepository>(sp =>
{
@ -26,14 +31,8 @@ IHost host = Host.CreateDefaultBuilder(args)
throw new InvalidOperationException("AccessConnectionString is missing in configuration.");
return new AccessDbRepository(connectionString);
});
services.AddSingleton<IAttachmentRepository>(sp =>
{
var pgConnectionString = config["PostgresConnectionString"];
if (string.IsNullOrWhiteSpace(pgConnectionString))
throw new InvalidOperationException("PostgresConnectionString is missing in configuration.");
var logger = sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<AttachmentRepository>>();
return new AttachmentRepository(pgConnectionString, logger);
});
// Register AttachmentRepository as scoped, using EF Core DbContext
services.AddScoped<IAttachmentRepository, AttachmentRepository>();
services.AddSingleton<IMailListener, MailListener>();
services.AddSingleton<IXmlParser, XmlParser>();
services.AddSingleton<IPdfParser, PdfParser>();