books/backend/Books.Api/Commands/Invoices/InvoiceCommands.cs
Nicolaj Hartmann 1f75c5d791 Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:

Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess

Commands & Handlers:
- Full CQRS command structure for all domains

Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories

GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries

Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)

Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00

144 lines
4.7 KiB
C#

using Books.Api.Domain.Invoices;
using EventFlow.Commands;
namespace Books.Api.Commands.Invoices;
/// <summary>
/// Creates a new invoice draft for a customer.
/// Invoice number is assigned at creation (Momsloven §52).
/// </summary>
public class CreateInvoiceCommand(
InvoiceId invoiceId,
string companyId,
string fiscalYearId,
string customerId,
string customerName,
string customerNumber,
string invoiceNumber,
DateOnly invoiceDate,
DateOnly dueDate,
int paymentTermsDays,
string currency,
string? vatCode,
string? notes,
string? reference,
string createdBy) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public string CompanyId { get; } = companyId;
public string FiscalYearId { get; } = fiscalYearId;
public string CustomerId { get; } = customerId;
public string CustomerName { get; } = customerName;
public string CustomerNumber { get; } = customerNumber;
public string InvoiceNumber { get; } = invoiceNumber;
public DateOnly InvoiceDate { get; } = invoiceDate;
public DateOnly DueDate { get; } = dueDate;
public int PaymentTermsDays { get; } = paymentTermsDays;
public string Currency { get; } = currency;
public string? VatCode { get; } = vatCode;
public string? Notes { get; } = notes;
public string? Reference { get; } = reference;
public string CreatedBy { get; } = createdBy;
}
/// <summary>
/// Adds a line to an invoice draft.
/// </summary>
public class AddInvoiceLineCommand(
InvoiceId invoiceId,
string description,
decimal quantity,
decimal unitPrice,
string vatCode,
string? accountId = null,
string? unit = null,
decimal discountPercent = 0) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public string Description { get; } = description;
public decimal Quantity { get; } = quantity;
public decimal UnitPrice { get; } = unitPrice;
public string VatCode { get; } = vatCode;
public string? AccountId { get; } = accountId;
public string? Unit { get; } = unit;
public decimal DiscountPercent { get; } = discountPercent;
}
/// <summary>
/// Updates a line on an invoice draft.
/// </summary>
public class UpdateInvoiceLineCommand(
InvoiceId invoiceId,
int lineNumber,
string description,
decimal quantity,
decimal unitPrice,
string vatCode,
string? accountId = null,
string? unit = null,
decimal discountPercent = 0) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public int LineNumber { get; } = lineNumber;
public string Description { get; } = description;
public decimal Quantity { get; } = quantity;
public decimal UnitPrice { get; } = unitPrice;
public string VatCode { get; } = vatCode;
public string? AccountId { get; } = accountId;
public string? Unit { get; } = unit;
public decimal DiscountPercent { get; } = discountPercent;
}
/// <summary>
/// Removes a line from an invoice draft.
/// </summary>
public class RemoveInvoiceLineCommand(
InvoiceId invoiceId,
int lineNumber) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public int LineNumber { get; } = lineNumber;
}
/// <summary>
/// Marks an invoice as sent and records the ledger transaction.
/// Should be called after successfully posting to the ledger.
/// </summary>
public class MarkInvoiceSentCommand(
InvoiceId invoiceId,
string ledgerTransactionId,
string sentBy) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public string LedgerTransactionId { get; } = ledgerTransactionId;
public string SentBy { get; } = sentBy;
}
/// <summary>
/// Records a payment received for an invoice.
/// </summary>
public class ReceiveInvoicePaymentCommand(
InvoiceId invoiceId,
decimal amount,
string? bankTransactionId,
string? ledgerTransactionId,
string? paymentReference,
DateOnly paymentDate,
string recordedBy) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public decimal Amount { get; } = amount;
public string? BankTransactionId { get; } = bankTransactionId;
public string? LedgerTransactionId { get; } = ledgerTransactionId;
public string? PaymentReference { get; } = paymentReference;
public DateOnly PaymentDate { get; } = paymentDate;
public string RecordedBy { get; } = recordedBy;
}
/// <summary>
/// Voids an invoice. If already sent, includes the reversal transaction ID.
/// </summary>
public class VoidInvoiceCommand(
InvoiceId invoiceId,
string reason,
string? reversalLedgerTransactionId,
string voidedBy) : Command<InvoiceAggregate, InvoiceId>(invoiceId)
{
public string Reason { get; } = reason;
public string? ReversalLedgerTransactionId { get; } = reversalLedgerTransactionId;
public string VoidedBy { get; } = voidedBy;
}