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;
}
}