@invoicing-sdk/application
Version:
Application layer for the invoicing system including use cases, ports, and application services
470 lines (461 loc) • 15.7 kB
JavaScript
;
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