@invoicing-sdk/application
Version:
Application layer for the invoicing system including use cases, ports, and application services
550 lines (537 loc) • 19.6 kB
TypeScript
import { MoneyPlainObject, Invoice, InvoiceDraft, PaginatedResult, Order, InvoiceNumberGenerator, DomainEventPublisher, InvoiceNumberValidator, PDFRenderer, GermanLegalInfo, ShippingRateProvider, ShippingCostPolicy, DomainEvent } from '@invoicing-sdk/domain';
/**
* Command interfaces for invoice operations
*/
/**
* Command to request an invoice draft preparation
* This prepares invoice data without finalizing it
*/
interface RequestInvoiceCommand {
readonly orderId: string;
readonly requestedBy: string;
}
/**
* DTO representing an invoice draft for presentation layer
* Contains all invoice draft data in a serializable format without domain methods
*/
interface InvoiceDraftDTO {
readonly invoiceId: string;
readonly invoiceNumber: string;
readonly orderId: string;
readonly customerSnapshot: {
readonly firstName: string;
readonly lastName: string;
readonly street: string;
readonly zip: string;
readonly city: string;
readonly country: string;
readonly email: string;
readonly phone?: string | undefined;
};
readonly itemsSnapshot: Array<{
readonly name: string;
readonly variant?: string | undefined;
readonly productNumber?: string | undefined;
readonly unitPrice: MoneyPlainObject;
readonly quantity: number;
readonly linePrice: MoneyPlainObject;
}>;
readonly financials: {
readonly subtotal: MoneyPlainObject;
readonly taxes: MoneyPlainObject;
readonly shippingCost: MoneyPlainObject;
readonly discount: MoneyPlainObject;
readonly grandTotal: MoneyPlainObject;
};
readonly status: "DRAFT";
readonly createdOn: string;
readonly createdBy: string;
}
/**
* Command to issue and finalize an invoice.
*
* This issues the invoice with PDF generation and persistence
*/
interface IssueInvoiceCommand {
/**
* Reference to the draft to issue. Pass the invoice number of the draft.
* See {@link createRequestInvoiceUseCase} for preparing a draft before issuing.
*/
readonly invoiceNumber: string;
/**
* Who requests the issuing.
* This is typically the user or system initiating the invoice process,
* and it may be validated against the {@link InvoiceDraft.createdBy} value.
*
* NOTE: In a future version, we may add a policy to enforce
* that the user who issues must be the same user who drafted.
*/
readonly issuedBy: string;
/**
* Reference to the order being invoiced.
* Used to validate integrity.
*/
readonly orderId: string;
}
/**
* Command to create and finalize an invoice.
*
* @deprecated Use {@link IssueInvoiceCommand} instead. "Issue Invoice" is the preferred terminology
* as it aligns with real-world accounting language where invoices are issued to customers.
* This interface will be removed in the next major version.
*
* This creates the invoice with PDF generation and persistence
*/
interface CreateInvoiceCommand {
readonly orderId: string;
readonly invoiceNumber: string;
readonly createdBy: string;
}
/**
* Result of requesting an invoice draft
*
* @deprecated Use the `draft` property instead of individual fields.
* The duplicated fields will be removed in the next major version.
* Migrate to using `result.draft.customerSnapshot` instead of `result.customerInfo`,
* `result.draft.itemsSnapshot` instead of `result.items`, etc.
*/
interface RequestInvoiceResult {
readonly orderId: string;
/**
* @deprecated Use `draft.invoiceNumber` instead. Will be removed in next major version.
*/
readonly suggestedInvoiceNumber: string;
/**
* @deprecated Use `draft.customerSnapshot` instead. Will be removed in next major version.
*/
readonly customerInfo: {
readonly firstName: string;
readonly lastName: string;
readonly street: string;
readonly zip: string;
readonly city: string;
readonly country: string;
readonly email: string;
readonly phone?: string | undefined;
};
/**
* @deprecated Use `draft.itemsSnapshot` instead. Will be removed in next major version.
*/
readonly items: Array<{
readonly name: string;
readonly variant?: string | undefined;
readonly productNumber?: string | undefined;
readonly unitPrice: MoneyPlainObject;
readonly quantity: number;
readonly linePrice: MoneyPlainObject;
}>;
/**
* @deprecated Use `draft.financials` instead. Will be removed in next major version.
*/
readonly financials: {
readonly subtotal: MoneyPlainObject;
readonly taxes: MoneyPlainObject;
readonly shippingCost: MoneyPlainObject;
readonly discount: MoneyPlainObject;
readonly grandTotal: MoneyPlainObject;
};
/**
* The complete invoice draft data as a DTO.
* This is the preferred way to access draft information.
*/
readonly draft: InvoiceDraftDTO;
}
/**
* Result of issuing an invoice
*/
interface IssueInvoiceResult {
readonly invoiceId: string;
readonly invoiceNumber: string;
readonly orderId: string;
readonly grandTotal: MoneyPlainObject;
readonly issuedOn: Date;
readonly issuedBy: string;
}
/**
* Result of creating an invoice
*
* @deprecated Use {@link IssueInvoiceResult} instead. "Issue Invoice" is the preferred terminology
* as it aligns with real-world accounting language where invoices are issued to customers.
* This interface will be removed in the next major version.
*/
interface CreateInvoiceResult {
readonly invoiceId: string;
readonly invoiceNumber: string;
readonly orderId: string;
readonly grandTotal: MoneyPlainObject;
readonly createdOn: Date;
readonly createdBy: string;
}
/**
* Arguments for finding a draft invoice by invoice number or order ID
*/
type FindDraftArgs = {
by: "invoiceNumber";
invoiceNumber: string;
} | {
by: "orderId";
orderId: string;
};
/**
* Query parameters for listing invoices with filtering and pagination
*/
interface ListInvoicesQuery {
readonly invoiceNumber?: string;
readonly dateFrom?: Date;
readonly dateTo?: Date;
readonly minTotal?: number;
readonly maxTotal?: number;
readonly page: number;
readonly limit: number;
}
/**
* Repository interface for Invoice aggregate
* Provides persistence and querying capabilities for invoices
*/
interface InvoiceRepository {
/**
* Save an invoice to the repository
* @param invoice - The invoice aggregate to save
* @returns Promise that resolves when the invoice is saved
*/
save(invoice: Invoice): Promise<void>;
/**
* Find an invoice by its order ID
* @param orderId - The order identifier
* @returns Promise resolving to the invoice or null if not found
*/
findByOrderId(orderId: string): Promise<Invoice | null>;
/**
* Find a draft invoice by invoice number or order ID
* @param args - The search criteria (discriminated union)
* @returns Promise resolving to the draft invoice or null if not found
*/
findDraft(args: FindDraftArgs): Promise<InvoiceDraft | null>;
/**
* Find an invoice by its invoice number
* @param invoiceNumber - The invoice number
* @returns Promise resolving to the invoice or null if not found
*/
findByInvoiceNumber(invoiceNumber: string): Promise<Invoice | null>;
/**
* Find an invoice by its unique identifier
* @param invoiceId - The invoice identifier
* @returns Promise resolving to the invoice or null if not found
*/
findById(invoiceId: string): Promise<Invoice | null>;
/**
* List invoices with filtering and pagination
* @param query - Query parameters for filtering and pagination
* @returns Promise resolving to paginated results
*/
list(query: ListInvoicesQuery): Promise<PaginatedResult<Invoice>>;
/**
* Check if an invoice exists for a given order
* @param orderId - The order identifier
* @returns Promise resolving to true if invoice exists, false otherwise
*/
existsForOrder(orderId: string): Promise<boolean>;
/**
* Check if an invoice number is already taken
* @param invoiceNumber - The invoice number to check
* @returns Promise resolving to true if number exists, false otherwise
*/
invoiceNumberExists(invoiceNumber: string): Promise<boolean>;
/**
* Get the total count of invoices (for statistics)
* @returns Promise resolving to the total number of invoices
*/
getTotalCount(): Promise<number>;
}
/**
* Repository interface for Order entities
* Provides read-only access to order data for invoice creation
*/
interface OrderRepository {
/**
* Find an order by its unique identifier
* @param orderId - The unique order identifier
* @returns Promise resolving to the order or null if not found
*/
findById(orderId: string): Promise<Order | null>;
/**
* Check if an order exists
* @param orderId - The unique order identifier
* @returns Promise resolving to true if order exists, false otherwise
*/
exists(orderId: string): Promise<boolean>;
}
/**
* Query to get a single invoice by ID or invoice number
*/
interface GetInvoiceQuery {
readonly invoiceId?: string;
readonly invoiceNumber?: string;
readonly orderId?: string;
readonly includePdf?: boolean;
}
/**
* Result of listing invoices
*/
interface ListInvoicesResult {
readonly items: Array<{
readonly invoiceId: string;
readonly invoiceNumber: string;
readonly orderId: string;
readonly customerName: string;
readonly grandTotal: MoneyPlainObject;
readonly createdOn: Date;
readonly createdBy: string;
readonly itemCount: number;
}>;
readonly totalCount: number;
readonly page: number;
readonly limit: number;
readonly hasNextPage: boolean;
readonly hasPreviousPage: boolean;
}
/**
* Result of getting a single invoice
*/
interface GetInvoiceResult {
readonly invoiceId: string;
readonly invoiceNumber: string;
readonly orderId: string;
readonly customerSnapshot: {
readonly firstName: string;
readonly lastName: string;
readonly street: string;
readonly zip: string;
readonly city: string;
readonly country: string;
readonly email: string;
readonly phone?: string;
};
readonly itemsSnapshot: Array<{
readonly name: string;
readonly variant?: string;
readonly productNumber?: string;
readonly unitPrice: MoneyPlainObject;
readonly quantity: number;
readonly linePrice: MoneyPlainObject;
}>;
readonly financials: {
readonly subtotal: MoneyPlainObject;
readonly taxes: MoneyPlainObject;
readonly shippingCost: MoneyPlainObject;
readonly discount: MoneyPlainObject;
readonly grandTotal: MoneyPlainObject;
};
readonly createdOn: Date;
readonly createdBy: string;
readonly pdfBytes?: Uint8Array;
}
/**
* Dependencies required for RequestInvoice use case
*/
interface RequestInvoiceDependencies {
readonly orderRepository: OrderRepository;
readonly invoiceRepository: InvoiceRepository;
readonly invoiceNumberGenerator: InvoiceNumberGenerator;
readonly eventPublisher?: DomainEventPublisher;
}
/**
* Use case for requesting an invoice draft preparation
* This prepares invoice data without finalizing it, allowing review before creation
*
* Requirements: 1.1 - Allow creation of invoice draft from order
*/
declare function makeRequestInvoice(deps: RequestInvoiceDependencies): (command: RequestInvoiceCommand) => Promise<RequestInvoiceResult>;
/**
* Factory function to create RequestInvoice use case with dependencies
*/
declare function createRequestInvoiceUseCase(deps: RequestInvoiceDependencies): (command: RequestInvoiceCommand) => Promise<RequestInvoiceResult>;
/**
* Dependencies required for IssueInvoice use case
*/
interface IssueInvoiceDependencies {
readonly orderRepository: OrderRepository;
readonly invoiceRepository: InvoiceRepository;
readonly invoiceNumberValidator: InvoiceNumberValidator;
readonly pdfRenderer: PDFRenderer;
readonly legalInfo: GermanLegalInfo;
readonly shippingRateProvider: ShippingRateProvider;
readonly shippingCostPolicy: ShippingCostPolicy;
/**
* Event publisher to publish {@link InvoiceIssued} events to.
*
* Can be omitted to not use event publishing.
*/
readonly eventPublisher?: DomainEventPublisher;
}
/**
* Use case for issuing and finalizing an invoice
* This issues the invoice with validation, PDF generation, and persistence
*
* Requirements:
* - 1.1: Ensure the draft exists
* - 1.2: Snapshot customer information and order items
* - 1.3: Validate invoice number format
* - 1.4: Calculate totals correctly
* - 1.5: Generate PDF/A-3 compliant PDF
* - 1.6: Make invoice immutable after issuance
*/
declare function makeIssueInvoice(deps: IssueInvoiceDependencies): (command: IssueInvoiceCommand) => Promise<IssueInvoiceResult>;
/**
* Factory function to create IssueInvoice use case with dependencies
*/
declare function createIssueInvoiceUseCase(deps: IssueInvoiceDependencies): (command: IssueInvoiceCommand) => Promise<IssueInvoiceResult>;
/**
* Dependencies required for CreateInvoice use case
*/
interface CreateInvoiceDependencies {
readonly orderRepository: OrderRepository;
readonly invoiceRepository: InvoiceRepository;
readonly invoiceNumberValidator: InvoiceNumberValidator;
readonly pdfRenderer: PDFRenderer;
readonly legalInfo: GermanLegalInfo;
readonly shippingRateProvider: ShippingRateProvider;
readonly shippingCostPolicy: ShippingCostPolicy;
/**
* Event publisher to publish {@link InvoiceCreated} events to.
*
* Can be omitted to not use event publishing.
*/
readonly eventPublisher?: DomainEventPublisher;
}
/**
* Use case for creating and finalizing an invoice
* This creates the invoice with validation, PDF generation, and persistence
*
* Requirements:
* - 1.1: Ensure the draft exists
* - 1.2: Snapshot customer information and order items
* - 1.3: Validate invoice number format
* - 1.4: Calculate totals correctly
* - 1.5: Generate PDF/A-3 compliant PDF
* - 1.6: Make invoice immutable after creation
*
* @deprecated Use {@link makeIssueInvoice} instead.
* The new API behaves the same, but has a more aligned naming convention.
* This use case factory will be removed in the next major version.
*/
declare function makeCreateInvoice(deps: CreateInvoiceDependencies): (command: CreateInvoiceCommand) => Promise<CreateInvoiceResult>;
/**
* Factory function to create CreateInvoice use case with dependencies
*
* @deprecated Use {@link makeIssueInvoice} instead.
* The new API behaves the same, but has a more aligned naming convention.
* This use case factory will be removed in the next major version.
*/
declare function createCreateInvoiceUseCase(deps: CreateInvoiceDependencies): (command: CreateInvoiceCommand) => Promise<CreateInvoiceResult>;
/**
* Dependencies required for ListInvoices use case
*/
interface ListInvoicesDependencies {
readonly invoiceRepository: InvoiceRepository;
}
/**
* Use case for listing invoices with filtering and pagination
*
* Requirements:
* - 2.1: Support filtering by invoice number, date range, and total amount range
* - 2.2: Provide paginated results
* - 2.5: Return results in consistent format with metadata
*/
declare function makeListInvoices(deps: ListInvoicesDependencies): (query: ListInvoicesQuery) => Promise<ListInvoicesResult>;
/**
* Factory function to create ListInvoices use case with dependencies
*/
declare function createListInvoicesUseCase(deps: ListInvoicesDependencies): (query: ListInvoicesQuery) => Promise<ListInvoicesResult>;
/**
* Dependencies required for GetInvoice use case
*/
interface GetInvoiceDependencies {
readonly invoiceRepository: InvoiceRepository;
}
/**
* Use case for retrieving a single invoice by ID, invoice number, or order ID
*
* Requirements:
* - 2.3: Allow retrieval by either orderId or invoiceNumber
* - 2.4: Return either domain data or PDF bytes based on request type
*/
declare function makeGetInvoice(deps: GetInvoiceDependencies): (query: GetInvoiceQuery) => Promise<GetInvoiceResult | null>;
/**
* Factory function to create GetInvoice use case with dependencies
*/
declare function createGetInvoiceUseCase(deps: GetInvoiceDependencies): (query: GetInvoiceQuery) => Promise<GetInvoiceResult | null>;
/**
* Event handler function type
*/
type EventHandler<T extends DomainEvent = DomainEvent> = (event: T) => Promise<void>;
/**
* Event subscription interface
*/
interface EventSubscription<T extends DomainEvent = DomainEvent> {
readonly eventType: T["eventType"];
readonly handler: EventHandler<T>;
unsubscribe(): Promise<void>;
}
/**
* Event bus interface for publishing and subscribing to domain events
*/
interface EventBus {
/**
* Publish a domain event to all subscribers
*/
publish<T extends DomainEvent>(event: T): Promise<void>;
/**
* Subscribe to a specific event type
* - eventType is now strictly typed to the discriminant of T
* - the returned subscription is also typed with T
*/
subscribe<T extends DomainEvent>(eventType: T["eventType"], handler: EventHandler<T>): Promise<EventSubscription<T>>;
/**
* Start the event bus (connect to message broker)
*/
start(): Promise<void>;
/**
* Stop the event bus (disconnect from message broker)
*/
stop(): Promise<void>;
/**
* Check if the event bus is connected
*/
isConnected(): Promise<boolean>;
}
/**
* Event bus configuration interface
*/
interface EventBusConfig {
readonly connectionString: string;
readonly exchangeName?: string;
readonly queuePrefix?: string;
readonly retryAttempts?: number;
readonly retryDelay?: number;
}
/**
* Base event bus error
*/
declare class EventBusError extends Error {
readonly cause?: Error | undefined;
constructor(message: string, cause?: Error | undefined);
}
/**
* Event publishing error
*/
declare class EventPublishError extends EventBusError {
constructor(eventType: string, cause?: Error);
}
/**
* Event subscription error
*/
declare class EventSubscriptionError extends EventBusError {
constructor(eventType: string, cause?: Error);
}
export { type CreateInvoiceCommand, type CreateInvoiceDependencies, type CreateInvoiceResult, type EventBus, type EventBusConfig, EventBusError, type EventHandler, EventPublishError, type EventSubscription, EventSubscriptionError, type GetInvoiceDependencies, type GetInvoiceQuery, type GetInvoiceResult, type InvoiceDraftDTO, type InvoiceRepository, type IssueInvoiceCommand, type IssueInvoiceDependencies, type IssueInvoiceResult, type ListInvoicesDependencies, type ListInvoicesQuery, type ListInvoicesResult, type OrderRepository, type RequestInvoiceCommand, type RequestInvoiceDependencies, type RequestInvoiceResult, createCreateInvoiceUseCase, createGetInvoiceUseCase, createIssueInvoiceUseCase, createListInvoicesUseCase, createRequestInvoiceUseCase, makeCreateInvoice, makeGetInvoice, makeIssueInvoice, makeListInvoices, makeRequestInvoice };