UNPKG

@invoicing-sdk/application

Version:

Application layer for the invoicing system including use cases, ports, and application services

470 lines (461 loc) 15.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { EventBusError: () => EventBusError, EventPublishError: () => EventPublishError, EventSubscriptionError: () => EventSubscriptionError, createCreateInvoiceUseCase: () => createCreateInvoiceUseCase, createGetInvoiceUseCase: () => createGetInvoiceUseCase, createIssueInvoiceUseCase: () => createIssueInvoiceUseCase, createListInvoicesUseCase: () => createListInvoicesUseCase, createRequestInvoiceUseCase: () => createRequestInvoiceUseCase, makeCreateInvoice: () => makeCreateInvoice, makeGetInvoice: () => makeGetInvoice, makeIssueInvoice: () => makeIssueInvoice, makeListInvoices: () => makeListInvoices, makeRequestInvoice: () => makeRequestInvoice }); module.exports = __toCommonJS(index_exports); // src/use-cases/RequestInvoice.ts var import_domain = require("@invoicing-sdk/domain"); // src/use-cases/mappers/InvoiceDraftMapper.ts var InvoiceDraftMapper = class { /** * Converts InvoiceDraft domain entity to InvoiceDraftDTO * @param draft - The domain InvoiceDraft entity * @returns InvoiceDraftDTO suitable for presentation layer */ static toDTO(draft) { return { invoiceId: draft.invoiceId, invoiceNumber: draft.invoiceNumber, orderId: draft.orderId, customerSnapshot: { firstName: draft.customerSnapshot.firstName, lastName: draft.customerSnapshot.lastName, street: draft.customerSnapshot.street, zip: draft.customerSnapshot.zip, city: draft.customerSnapshot.city, country: draft.customerSnapshot.country, email: draft.customerSnapshot.email, phone: draft.customerSnapshot.phone }, itemsSnapshot: draft.itemsSnapshot.map((item) => ({ name: item.name, variant: item.variant, productNumber: item.productNumber, unitPrice: item.unitPrice.toPlainObject(), quantity: item.quantity, linePrice: item.linePrice.toPlainObject() })), financials: { subtotal: draft.financials.subtotal.toPlainObject(), taxes: draft.financials.taxes.toPlainObject(), shippingCost: draft.financials.shippingCost.toPlainObject(), discount: draft.financials.discount.toPlainObject(), grandTotal: draft.financials.grandTotal.toPlainObject() }, status: "DRAFT", createdOn: draft.createdOn.toISOString(), createdBy: draft.createdBy }; } }; // src/use-cases/RequestInvoice.ts function makeRequestInvoice(deps) { return async (command) => { const { orderRepository, invoiceRepository, invoiceNumberGenerator } = deps; const { orderId } = command; const order = await orderRepository.findById(orderId); if (!order) { throw new import_domain.OrderNotFoundError(orderId); } const existingInvoice = await invoiceRepository.findByOrderId(orderId); if (existingInvoice) { throw new import_domain.InvoiceAlreadyExistsError(orderId); } const financials = { subtotal: order.subtotal, taxes: order.taxes, shippingCost: order.shippingCost, discount: order.discount, grandTotal: order.subtotal.add(order.taxes).add(order.shippingCost).subtract(order.discount) }; const financialsPO = { subtotal: financials.subtotal.toPlainObject(), taxes: financials.taxes.toPlainObject(), shippingCost: financials.shippingCost.toPlainObject(), discount: financials.discount.toPlainObject(), grandTotal: financials.grandTotal.toPlainObject() }; const draft = await import_domain.Invoice.createDraft( { orderId: order.orderId, customerSnapshot: order.customer, itemsSnapshot: order.items, financials, createdBy: command.requestedBy }, { invoiceNumberGenerator } ); await invoiceRepository.save(draft); const draftDTO = InvoiceDraftMapper.toDTO(draft); const result = { orderId: order.orderId, suggestedInvoiceNumber: draftDTO.invoiceNumber, customerInfo: draftDTO.customerSnapshot, items: draftDTO.itemsSnapshot, financials: draftDTO.financials, draft: draftDTO }; if (deps.eventPublisher) { draft.publishAllUncommitted(deps.eventPublisher); } return result; }; } function createRequestInvoiceUseCase(deps) { return makeRequestInvoice(deps); } // src/use-cases/IssueInvoice.ts var import_domain2 = require("@invoicing-sdk/domain"); function makeIssueInvoice(deps) { return async (command) => { const { orderRepository, invoiceRepository, invoiceNumberValidator, shippingRateProvider, shippingCostPolicy, pdfRenderer, legalInfo } = deps; const { invoiceNumber, orderId, issuedBy } = command; const order = await orderRepository.findById(orderId); if (!order) { throw new import_domain2.OrderNotFoundError(orderId); } const existingInvoice = await invoiceRepository.findDraft({ by: "orderId", orderId }); if (!existingInvoice) { throw new import_domain2.InvoiceDraftNotFoundError(orderId); } if (!existingInvoice.requireStatus(import_domain2.InvoiceStatus.DRAFT)) { throw new import_domain2.InvoiceStateViolationError( invoiceNumber, import_domain2.InvoiceStatus.DRAFT, existingInvoice.getStatus() ); } if (existingInvoice.invoiceNumber !== invoiceNumber) { throw new import_domain2.InvoiceIntegrityCheckError( invoiceNumber, "INVOICE_NUMBER_MISMATCH", `Invoice draft has ${existingInvoice.invoiceNumber} but received ${invoiceNumber}` ); } const baseTotal = order.subtotal.add(order.taxes).subtract(order.discount); const shippingCost = import_domain2.ShippingCost.determine( baseTotal, shippingRateProvider, shippingCostPolicy ); const grandTotal = baseTotal.add(shippingCost); const financials = { subtotal: order.subtotal, taxes: order.taxes, shippingCost, discount: order.discount, grandTotal }; const invoiceDataForPdf = { invoiceId: "", // Will be set after invoice creation invoiceNumber, orderId, customer: order.customer, items: order.items, financials, createdOn: /* @__PURE__ */ new Date(), createdBy: issuedBy, legalInfo }; const pdfBytes = await pdfRenderer.render(invoiceDataForPdf); const invoice = await import_domain2.Invoice.issue( { orderId, invoiceNumber, customerSnapshot: existingInvoice.customerSnapshot, itemsSnapshot: existingInvoice.itemsSnapshot, financials, createdBy: issuedBy, // explicit issuer attribution status: import_domain2.InvoiceStatus.DRAFT }, pdfBytes, { invoiceNumberValidator } ); await invoiceRepository.save(invoice); if (deps.eventPublisher) { invoice.publishAllUncommitted(deps.eventPublisher); } const result = { invoiceId: invoice.invoiceId, invoiceNumber: invoice.invoiceNumber, orderId: invoice.orderId, grandTotal: invoice.financials.grandTotal.toPlainObject(), issuedOn: invoice.createdOn, issuedBy: invoice.createdBy }; return result; }; } function createIssueInvoiceUseCase(deps) { return makeIssueInvoice(deps); } // src/use-cases/CreateInvoice.ts var import_domain3 = require("@invoicing-sdk/domain"); function makeCreateInvoice(deps) { return async (command) => { const { orderRepository, invoiceRepository, invoiceNumberValidator, shippingRateProvider, shippingCostPolicy, pdfRenderer, legalInfo } = deps; const { orderId, invoiceNumber, createdBy } = command; const order = await orderRepository.findById(orderId); if (!order) { throw new import_domain3.OrderNotFoundError(orderId); } const existingInvoice = await invoiceRepository.findDraft({ by: "invoiceNumber", invoiceNumber }); if (!existingInvoice) { throw new import_domain3.InvoiceDraftNotFoundError(orderId); } if (!existingInvoice.requireStatus(import_domain3.InvoiceStatus.DRAFT)) { throw new import_domain3.InvoiceStateViolationError( orderId, import_domain3.InvoiceStatus.DRAFT, existingInvoice.getStatus() ); } const grandTotal = order.subtotal.add(order.taxes).add(order.shippingCost).subtract(order.discount); const shippingCost = import_domain3.ShippingCost.determine( grandTotal, shippingRateProvider, shippingCostPolicy ); const financials = { subtotal: order.subtotal, taxes: order.taxes, shippingCost, discount: order.discount, grandTotal }; const invoiceDataForPdf = { invoiceId: "", // Will be set after invoice creation invoiceNumber, orderId, customer: order.customer, items: order.items, financials, createdOn: /* @__PURE__ */ new Date(), createdBy, legalInfo }; const pdfBytes = await pdfRenderer.render(invoiceDataForPdf); const invoice = await import_domain3.Invoice.issue( { orderId, invoiceNumber, customerSnapshot: existingInvoice.customerSnapshot, itemsSnapshot: existingInvoice.itemsSnapshot, financials, createdBy, status: import_domain3.InvoiceStatus.DRAFT // This is always the case after the check above }, pdfBytes, { invoiceNumberValidator } ); await invoiceRepository.save(invoice); if (deps.eventPublisher) { invoice.publishAllUncommitted(deps.eventPublisher); } const result = { invoiceId: invoice.invoiceId, invoiceNumber: invoice.invoiceNumber, orderId: invoice.orderId, grandTotal: invoice.financials.grandTotal.toPlainObject(), createdOn: invoice.createdOn, createdBy: invoice.createdBy }; return result; }; } function createCreateInvoiceUseCase(deps) { return makeCreateInvoice(deps); } // src/use-cases/ListInvoices.ts function makeListInvoices(deps) { return async (query) => { const { invoiceRepository } = deps; if (query.page < 1) { throw new Error("Page number must be greater than 0"); } if (query.limit < 1 || query.limit > 100) { throw new Error("Limit must be between 1 and 100"); } if (query.dateFrom && query.dateTo && query.dateFrom > query.dateTo) { throw new Error("dateFrom must be before or equal to dateTo"); } if (query.minTotal !== void 0 && query.maxTotal !== void 0 && query.minTotal > query.maxTotal) { throw new Error("minTotal must be less than or equal to maxTotal"); } const paginatedResult = await invoiceRepository.list(query); const result = { items: paginatedResult.items.map((invoice) => ({ invoiceId: invoice.invoiceId, invoiceNumber: invoice.invoiceNumber, orderId: invoice.orderId, customerName: `${invoice.customerSnapshot.firstName} ${invoice.customerSnapshot.lastName}`, grandTotal: invoice.financials.grandTotal.toPlainObject(), createdOn: invoice.createdOn, createdBy: invoice.createdBy, itemCount: invoice.getTotalItemCount() })), totalCount: paginatedResult.totalCount, page: paginatedResult.page, limit: paginatedResult.limit, hasNextPage: paginatedResult.hasNextPage, hasPreviousPage: paginatedResult.hasPreviousPage }; return result; }; } function createListInvoicesUseCase(deps) { return makeListInvoices(deps); } // src/use-cases/GetInvoice.ts function makeGetInvoice(deps) { return async (query) => { const { invoiceRepository } = deps; const { invoiceId, invoiceNumber, orderId, includePdf = false } = query; if (!invoiceId && !invoiceNumber && !orderId) { throw new Error("At least one of invoiceId, invoiceNumber, or orderId must be provided"); } let invoice = null; if (invoiceId) { invoice = await invoiceRepository.findById(invoiceId); } else if (invoiceNumber) { invoice = await invoiceRepository.findByInvoiceNumber(invoiceNumber); } else if (orderId) { invoice = await invoiceRepository.findByOrderId(orderId); } if (!invoice) { return null; } const result = { invoiceId: invoice.invoiceId, invoiceNumber: invoice.invoiceNumber, orderId: invoice.orderId, customerSnapshot: { firstName: invoice.customerSnapshot.firstName, lastName: invoice.customerSnapshot.lastName, street: invoice.customerSnapshot.street, zip: invoice.customerSnapshot.zip, city: invoice.customerSnapshot.city, country: invoice.customerSnapshot.country, email: invoice.customerSnapshot.email, phone: invoice.customerSnapshot.phone }, itemsSnapshot: invoice.itemsSnapshot.map((item) => ({ name: item.name, variant: item.variant, productNumber: item.productNumber, unitPrice: item.unitPrice.toPlainObject(), quantity: item.quantity, linePrice: item.linePrice.toPlainObject() })), financials: { subtotal: invoice.financials.subtotal.toPlainObject(), taxes: invoice.financials.taxes.toPlainObject(), shippingCost: invoice.financials.shippingCost.toPlainObject(), discount: invoice.financials.discount.toPlainObject(), grandTotal: invoice.financials.grandTotal.toPlainObject() }, createdOn: invoice.createdOn, createdBy: invoice.createdBy, // Include PDF bytes only if requested pdfBytes: includePdf ? invoice.pdfBytes : void 0 }; return result; }; } function createGetInvoiceUseCase(deps) { return makeGetInvoice(deps); } // src/events/event-bus.ts var EventBusError = class extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = "EventBusError"; } }; var EventPublishError = class extends EventBusError { constructor(eventType, cause) { super(`Failed to publish event: ${eventType}`, cause); this.name = "EventPublishError"; } }; var EventSubscriptionError = class extends EventBusError { constructor(eventType, cause) { super(`Failed to subscribe to event: ${eventType}`, cause); this.name = "EventSubscriptionError"; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { EventBusError, EventPublishError, EventSubscriptionError, createCreateInvoiceUseCase, createGetInvoiceUseCase, createIssueInvoiceUseCase, createListInvoicesUseCase, createRequestInvoiceUseCase, makeCreateInvoice, makeGetInvoice, makeIssueInvoice, makeListInvoices, makeRequestInvoice }); //# sourceMappingURL=index.js.map