Giuliano Paschoalino 690ab131aa feat: Initialize ComplianceNFs project structure with core, infrastructure, service, and monitor components
- Created ComplianceNFs.Core project with domain entities and ports
- Implemented BuyingRecord, EnergyInvoice, ParsedInvoice entities
- Defined domain interfaces for mail listening, XML and PDF parsing, and repository access
- Established ComplianceNFs.Infrastructure project with file archiving, mail listening, and data access implementations
- Developed PDF and XML parsers for invoice data extraction
- Set up AccessDbRepository and AttachmentRepository for data retrieval and storage
- Created ComplianceNFs.Service project for background processing and service orchestration
- Implemented Worker service for periodic tasks
- Established ComplianceNFs.Monitor project with WPF UI for monitoring invoice statuses
- Added ViewModel for UI data binding and command handling
- Configured project files for .NET 9.0 and necessary package references
- Created initial appsettings.json for configuration management
- Added TODOs and roadmap for future development
2025-06-05 14:47:28 -03:00

152 lines
6.2 KiB
C#

using System;
using System.Threading.Tasks;
using System.Net.Mail;
using System.IO;
using ComplianceNFs.Core.Entities;
using ComplianceNFs.Core.Ports;
namespace ComplianceNFs.Core.Application.Services
{
// Handles ingestion of invoices from mail attachments
public class InvoiceIngestionService : IInvoiceIngestionService
{
private readonly IMailListener _mailListener;
private readonly IAttachmentRepository _attachmentRepository;
private readonly IXmlParser _xmlParser;
private readonly IPdfParser _pdfParser;
public InvoiceIngestionService(IMailListener mailListener, IAttachmentRepository attachmentRepository, IXmlParser xmlParser, IPdfParser pdfParser)
{
_mailListener = mailListener;
_attachmentRepository = attachmentRepository;
_xmlParser = xmlParser;
_pdfParser = pdfParser;
_mailListener.NewMailReceived += OnNewMailReceived;
}
private async void OnNewMailReceived(MailMessage mail)
{
// Download attachments, parse, map to EnergyInvoice, save via _attachmentRepository
foreach (var attachment in mail.Attachments)
{
if (attachment is System.Net.Mail.Attachment att && att.Name != null)
{
using var stream = new MemoryStream();
att.ContentStream.CopyTo(stream);
stream.Position = 0;
ParsedInvoice parsed = new ParsedInvoice();
if (att.Name.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
parsed = _xmlParser.Parse(stream);
else if (att.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
parsed = _pdfParser.Parse(stream);
else
continue;
var invoice = new EnergyInvoice
{
Filename = att.Name,
SupplierEmail = mail.From != null ? mail.From.Address : string.Empty,
ReceivedDate = !string.IsNullOrEmpty(mail.Headers?["Date"]) && DateTime.TryParse(mail.Headers["Date"], out var dt) ? dt : DateTime.Now,
CnpjComp = parsed.CnpjComp,
CnpjVend = parsed.CnpjVend,
MontNF = parsed.MontNF,
PrecNF = parsed.PrecNF,
ValorSemImpostos = parsed.ValorSemImpostos,
ValorFinalComImpostos = parsed.ValorFinalComImpostos,
RsComp = parsed.RsComp,
RsVend = parsed.RsVend,
NumeroNF = parsed.NumeroNF,
IcmsNF = parsed.IcmsNF,
UfComp = parsed.UfComp,
UfVend = parsed.UfVend,
Status = InvoiceStatus.Pending
};
await _attachmentRepository.SaveRawAsync(invoice);
}
}
}
public Task IngestAsync()
{
_mailListener.StartListening();
return Task.CompletedTask;
}
}
// Handles matching logic for invoices
public class MatchingService : IMatchingService
{
private readonly IAccessDbRepository _accessDbRepository;
public MatchingService(IAccessDbRepository accessDbRepository)
{
_accessDbRepository = accessDbRepository;
}
public Task MatchAsync(EnergyInvoice invoice)
{
// Example: Primary match logic (simplified)
var records = _accessDbRepository.GetByUnidade(invoice.CnpjComp);
foreach (var record in records)
{
if (record.CnpjComp == invoice.CnpjComp && record.CnpjVend == invoice.CnpjVend)
{
var volMatch = Math.Abs(record.MontLO - invoice.MontNF) / record.MontLO <= 0.01m;
var priceMatch = Math.Abs(record.PrecLO - invoice.PrecNF) / record.PrecLO <= 0.005m;
if (volMatch && priceMatch)
{
invoice.MatchedCodTE = record.CodTE;
invoice.Status = InvoiceStatus.Matched;
break;
}
}
}
// TODO: Add fallback and multi-invoice sum logic
return Task.CompletedTask;
}
}
// Handles compliance validation
public class ComplianceService : IComplianceService
{
public Task ValidateAsync(EnergyInvoice invoice)
{
// Example: Tax compliance check
if (invoice.Status == InvoiceStatus.Matched || invoice.Status == InvoiceStatus.FallbackMatched)
{
var impliedTax = invoice.ValorFinalComImpostos / (invoice.ValorSemImpostos == 0 ? 1 : invoice.ValorSemImpostos) - 1;
if (Math.Abs(impliedTax - invoice.IcmsNF) > 0.01m)
{
invoice.Status = InvoiceStatus.TaxMismatch;
invoice.DiscrepancyNotes = $"Tax mismatch: implied={impliedTax:P2}, expected={invoice.IcmsNF:P2}";
}
else
{
invoice.Status = InvoiceStatus.Validated;
}
}
return Task.CompletedTask;
}
}
// Handles notifications for mismatches
public class NotificationService : INotificationService
{
public Task NotifyAsync(EnergyInvoice invoice, string message)
{
// Example: Send notification (placeholder)
// In production, use SMTP or other email service
Console.WriteLine($"Notify {invoice.SupplierEmail}: {message}");
return Task.CompletedTask;
}
}
// Handles archiving of files
public class ArchivingService : IArchivingService
{
private readonly IFileArchiver _fileArchiver;
public ArchivingService(IFileArchiver fileArchiver)
{
_fileArchiver = fileArchiver;
}
public Task ArchiveAsync(EnergyInvoice invoice, byte[] rawFile)
{
return _fileArchiver.ArchiveAsync(invoice, rawFile);
}
}
}