using InvoiceMaster.Application.DTOs; using InvoiceMaster.Core.Entities; using InvoiceMaster.Core.Interfaces; using MediatR; namespace InvoiceMaster.Application.Commands.Invoices; public record UploadInvoiceCommand( Guid UserId, string Provider, string FileName, Stream FileStream, long FileSize, string ContentType) : IRequest; public record UploadInvoiceResult( bool Success, string? ErrorMessage, InvoiceDetailDto? Invoice); public class UploadInvoiceCommandHandler : IRequestHandler { private readonly IRepository _invoiceRepository; private readonly IRepository _connectionRepository; private readonly IBlobStorageService _blobStorage; private readonly IOcrService _ocrService; private readonly IUnitOfWork _unitOfWork; public UploadInvoiceCommandHandler( IRepository invoiceRepository, IRepository connectionRepository, IBlobStorageService blobStorage, IOcrService ocrService, IUnitOfWork unitOfWork) { _invoiceRepository = invoiceRepository; _connectionRepository = connectionRepository; _blobStorage = blobStorage; _ocrService = ocrService; _unitOfWork = unitOfWork; } public async Task Handle(UploadInvoiceCommand request, CancellationToken cancellationToken) { try { var connections = await _connectionRepository.GetAllAsync(cancellationToken); var connection = connections.FirstOrDefault(c => c.UserId == request.UserId && c.Provider.Equals(request.Provider, StringComparison.OrdinalIgnoreCase) && c.IsActive); if (connection == null) { return new UploadInvoiceResult(false, $"No active connection found for provider '{request.Provider}'", null); } var storagePath = await _blobStorage.UploadAsync(request.FileName, request.FileStream, request.ContentType, cancellationToken); var invoice = Invoice.Create( connection.Id, request.Provider, request.FileName, storagePath, request.FileSize); await _invoiceRepository.AddAsync(invoice, cancellationToken); await _unitOfWork.SaveChangesAsync(cancellationToken); request.FileStream.Position = 0; var ocrResult = await _ocrService.ExtractAsync(request.FileStream, request.FileName, cancellationToken); if (ocrResult.Success && ocrResult.Data != null) { var data = ocrResult.Data; invoice.SetExtractionData( System.Text.Json.JsonSerializer.Serialize(data), ocrResult.Confidence, data.SupplierName, data.SupplierOrgNumber, data.InvoiceNumber, data.InvoiceDate, data.DueDate, data.AmountTotal, data.AmountVat, data.VatRate, data.OcrNumber, data.Bankgiro, data.Plusgiro, data.Currency); await _unitOfWork.SaveChangesAsync(cancellationToken); } var dto = MapToDto(invoice); return new UploadInvoiceResult(true, null, dto); } catch (Exception ex) { return new UploadInvoiceResult(false, ex.Message, null); } } private static InvoiceDetailDto MapToDto(Invoice invoice) { return new InvoiceDetailDto( invoice.Id, invoice.Status.ToString().ToLower(), invoice.Provider, new FileInfoDto( invoice.OriginalFilename, invoice.FileSize, $"/api/v1/invoices/{invoice.Id}/file"), new ExtractionDataDto( invoice.ExtractedSupplierName, invoice.ExtractedSupplierOrgNumber, invoice.ExtractedInvoiceNumber, invoice.ExtractedInvoiceDate, invoice.ExtractedDueDate, invoice.ExtractedAmountTotal, invoice.ExtractedAmountVat, invoice.ExtractedVatRate, invoice.ExtractedOcrNumber, invoice.ExtractedBankgiro, invoice.ExtractedPlusgiro, invoice.ExtractedCurrency, invoice.ExtractionConfidence), invoice.SupplierNumber != null ? new SupplierMatchDto( invoice.SupplierMatchAction?.ToString() ?? "USE_EXISTING", invoice.SupplierNumber, invoice.ExtractedSupplierName, invoice.SupplierMatchConfidence) : null, null, invoice.CreatedAt); } }