using Books.Api.Domain; using Books.Api.Domain.Invoices; using Books.Api.EventFlow.Repositories; using Books.Api.Invoicing.Services; using EventFlow.Commands; namespace Books.Api.Commands.Invoices; /// /// Command handler for creating invoices. /// Auto-assigns a sequential invoice number if one is not provided. /// Validates the company has a CVR number (required for invoicing). /// public class CreateInvoiceCommandHandler( IInvoiceNumberService invoiceNumberService, ICompanyRepository companyRepository) : CommandHandler { public override async Task ExecuteAsync( InvoiceAggregate aggregate, CreateInvoiceCommand command, CancellationToken cancellationToken) { // Validate company has a CVR number (required for invoicing per Danish law) var company = await companyRepository.GetByIdAsync(command.CompanyId, cancellationToken); if (company == null) { throw new DomainException( "COMPANY_NOT_FOUND", $"Company '{command.CompanyId}' not found", $"Virksomheden '{command.CompanyId}' blev ikke fundet"); } if (string.IsNullOrWhiteSpace(company.Cvr)) { throw new DomainException( "CVR_REQUIRED_FOR_INVOICE", "Company must have a CVR number to create invoices. Please update company settings.", "Virksomheden skal have et CVR-nummer for at oprette fakturaer. Opdater venligst virksomhedsindstillinger."); } // Auto-assign invoice number if not provided var invoiceNumber = command.InvoiceNumber; if (string.IsNullOrWhiteSpace(invoiceNumber)) { invoiceNumber = await invoiceNumberService.GetNextInvoiceNumberAsync( command.CompanyId, command.InvoiceDate.Year, cancellationToken); } aggregate.Create( command.CompanyId, command.FiscalYearId, command.CustomerId, command.CustomerName, command.CustomerNumber, invoiceNumber, command.InvoiceDate, command.DueDate, command.PaymentTermsDays, command.Currency, command.VatCode, command.Notes, command.Reference, command.CreatedBy); } } public class AddInvoiceLineCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, AddInvoiceLineCommand command, CancellationToken cancellationToken) { aggregate.AddLine( command.Description, command.Quantity, command.UnitPrice, command.VatCode, command.AccountId, command.Unit, command.DiscountPercent); return Task.CompletedTask; } } public class UpdateInvoiceLineCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, UpdateInvoiceLineCommand command, CancellationToken cancellationToken) { aggregate.UpdateLine( command.LineNumber, command.Description, command.Quantity, command.UnitPrice, command.VatCode, command.AccountId, command.Unit, command.DiscountPercent); return Task.CompletedTask; } } public class RemoveInvoiceLineCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, RemoveInvoiceLineCommand command, CancellationToken cancellationToken) { aggregate.RemoveLine(command.LineNumber); return Task.CompletedTask; } } public class MarkInvoiceSentCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, MarkInvoiceSentCommand command, CancellationToken cancellationToken) { aggregate.Send( command.LedgerTransactionId, command.SentBy); return Task.CompletedTask; } } public class ReceiveInvoicePaymentCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, ReceiveInvoicePaymentCommand command, CancellationToken cancellationToken) { aggregate.ReceivePayment( command.Amount, command.BankTransactionId, command.LedgerTransactionId, command.PaymentReference, command.PaymentDate, command.RecordedBy); return Task.CompletedTask; } } public class VoidInvoiceCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, VoidInvoiceCommand command, CancellationToken cancellationToken) { aggregate.Void( command.Reason, command.ReversalLedgerTransactionId, command.VoidedBy); return Task.CompletedTask; } } // ===================================================== // CREDIT NOTE COMMAND HANDLERS // ===================================================== public class CreateCreditNoteCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, CreateCreditNoteCommand command, CancellationToken cancellationToken) { aggregate.CreateCreditNote( command.CompanyId, command.FiscalYearId, command.CustomerId, command.CustomerName, command.CustomerNumber, command.CreditNoteNumber, command.CreditNoteDate, command.Currency, command.VatCode, command.Notes, command.Reference, command.CreatedBy, command.OriginalInvoiceId, command.OriginalInvoiceNumber, command.CreditReason); return Task.CompletedTask; } } public class IssueCreditNoteCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, IssueCreditNoteCommand command, CancellationToken cancellationToken) { aggregate.Issue( command.LedgerTransactionId, command.IssuedBy); return Task.CompletedTask; } } public class ApplyCreditNoteCommandHandler : CommandHandler { public override Task ExecuteAsync( InvoiceAggregate aggregate, ApplyCreditNoteCommand command, CancellationToken cancellationToken) { aggregate.ApplyCredit( command.TargetInvoiceId, command.TargetInvoiceNumber, command.Amount, command.AppliedDate, command.AppliedBy, command.LedgerTransactionId); return Task.CompletedTask; } }