import { defaultGqlPermission, defaultPermission } from "@tailor-platform/erp-kit/core"; import { definePrimitivesModule, defineOrganizationModule, defineBusinessPartnerModule, defineItemManagementModule, defineProductManagementModule, defineInventoryModule, defineInboundShipmentModule, defineOutboundShipmentModule, definePurchaseModule, defineUserManagementModule, defineCoaManagementModule, defineFinancialAccountingModule, defineAccountPayableModule, defineAccountReceivableModule, defineSalesModule, } from "@tailor-platform/erp-kit/module"; import { db } from "@tailor-platform/sdk"; export const umModules = defineUserManagementModule({ dbNamespace: "main-db", }); const primitivesModules = definePrimitivesModule({}); const orgModules = defineOrganizationModule({ primitives: { db: { currency: primitivesModules.db.currency }, queries: { getCurrency: primitivesModules.queries.getCurrency }, }, }); const coaModules = defineCoaManagementModule({ organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, }); const bpModules = defineBusinessPartnerModule({ organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, primitives: { db: { currency: primitivesModules.db.currency }, queries: { getCurrency: primitivesModules.queries.getCurrency }, }, coaManagement: { db: { account: coaModules.db.account }, queries: { getAccount: coaModules.queries.getAccount }, }, }); const imModules = defineItemManagementModule({ primitives: { db: { unit: primitivesModules.db.unit }, queries: { getUnit: primitivesModules.queries.getUnit }, }, }); const pmModules = defineProductManagementModule({ primitives: { db: { unit: primitivesModules.db.unit }, queries: { getUnit: primitivesModules.queries.getUnit }, }, itemManagement: { db: { item: imModules.db.item }, commands: { createItem: imModules.commands.createItem }, }, }); // ============================================================================= // coa-management / financial-accounting — the general ledger backbone. // Inventory costing (standard costing) records every valuation-affecting stock // movement and standard cost revision directly in financial-accounting as a // posted journal entry, so both modules are wired before inventory. // ============================================================================= const finModules = defineFinancialAccountingModule({ organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, coaManagement: { db: { account: coaModules.db.account }, queries: { listAccounts: coaModules.queries.listAccounts }, }, }); const invModules = defineInventoryModule({ organization: { db: { company: orgModules.db.company, site: orgModules.db.site }, queries: { getCompany: orgModules.queries.getCompany, getSite: orgModules.queries.getSite, }, }, itemManagement: { db: { item: imModules.db.item }, queries: { getItem: imModules.queries.getItem }, }, primitives: { db: { unit: primitivesModules.db.unit }, }, // Costing journals: every valuation-affecting movement creates and posts a // balanced journal entry in financial-accounting within the same transaction. financialAccounting: { commands: { createJournalEntry: finModules.commands.createJournalEntry, postJournalEntry: finModules.commands.postJournalEntry, }, queries: { getPeriodByDate: finModules.queries.getPeriodByDate }, }, coaManagement: { queries: { listAccounts: coaModules.queries.listAccounts }, }, // TransferOrder extension fields. Only two generic columns are layered on top // of erp-kit's native TransferOrder aggregate: // - `docNumber` — human-readable identifier (TO-XXXXXXXX), auto-assigned. // - `notes` — free-form operator notes. transferOrder: { fields: { docNumber: db .string() .index() .unique() .serial({ start: 10000000, format: "TO-%d" }) .description("Auto-generated human-readable document number (e.g. TO-10000000)"), notes: db.string({ optional: true }).description("Free-form operator notes."), }, }, }); export const purchaseModules = definePurchaseModule({ purchaseOrder: { companyType: orgModules.db.company, supplierAccountType: bpModules.db.supplierAccount, siteType: orgModules.db.site, fields: { docNumber: db .string() .index() .unique() .serial({ start: 10000000, format: "PO-%d", }) .description("Auto-generated human-readable document number (e.g. PO-10000000)"), }, }, purchaseOrderLine: { itemType: imModules.db.item, unitType: primitivesModules.db.unit, siteType: orgModules.db.site, }, organization: { db: { company: orgModules.db.company, site: orgModules.db.site, }, queries: { getCompany: orgModules.queries.getCompany, getSite: orgModules.queries.getSite, }, }, businessPartner: { db: { supplierAccount: bpModules.db.supplierAccount, }, queries: { getSupplierAccount: bpModules.queries.getSupplierAccount, }, }, itemManagement: { db: { item: imModules.db.item }, queries: { getItem: imModules.queries.getItem }, }, primitives: { db: { currency: primitivesModules.db.currency, unit: primitivesModules.db.unit }, }, // Purchase orchestrates supply plans (expected-arrival ledger, auto-created on approve) // and declares price-change variances into inventory's acquisition-cost register. inventory: { commands: { createInventorySupplyPlan: invModules.commands.createInventorySupplyPlan, updateInventorySupplyPlan: invModules.commands.updateInventorySupplyPlan, closeInventorySupplyPlan: invModules.commands.closeInventorySupplyPlan, postAcquisitionCostAdjustment: invModules.commands.postAcquisitionCostAdjustment, }, queries: { getInventorySupplyPlan: invModules.queries.getInventorySupplyPlan, }, }, userManagement: { db: { user: umModules.db.user }, }, }); // ============================================================================= // inbound-shipment — the inbound shipment document (入荷). It is the goods-in // instruction/record surfaced in the UI as "Inbound Shipment". On `post` it // drives inventory via `postInventoryLedger` (sourceType INBOUND_SHIPMENT), // recording the physical 入庫 (stock increase), and pushes the received // quantities onto the linked purchase-order lines. A single generic extension // column is layered on: a human-readable document number (ISH-XXXXXXXX), // auto-assigned by a dedicated serial. // ============================================================================= const inboundShipmentModules = defineInboundShipmentModule({ inboundShipment: { fields: { docNumber: db .string() .index() .unique() .serial({ start: 10000000, format: "ISH-%d" }) .description( "Auto-generated human-readable inbound-shipment document number (e.g. ISH-10000000)", ), }, }, itemManagement: { db: { item: imModules.db.item }, queries: { getItem: imModules.queries.getItem }, }, primitives: { db: { unit: primitivesModules.db.unit }, }, inventory: { db: { storageLocation: invModules.db.storageLocation }, commands: { postInventoryLedger: invModules.commands.postInventoryLedger }, queries: { getStorageLocation: invModules.queries.getStorageLocation }, }, purchase: { commands: { recalculatePurchaseOrderReceiptStatus: purchaseModules.commands.recalculatePurchaseOrderReceiptStatus, }, queries: { listPurchaseOrderLinesForMatching: purchaseModules.queries.listPurchaseOrderLinesForMatching, }, }, }); // ============================================================================= // Account payable — captures vendor invoices (AccountPayableDocument, type // INVOICE) against ORDERED purchase orders, runs PO↔receipt↔invoice 3-way // matching on registration, and blocks posting with SYSTEM holds on variance. // Requires coa-management (GL accounts referenced by the payable control // account + line distributions) and financial-accounting for direct posting, // even though this template does not expose the post action. // Receipt quantities for matching come from the purchase-order lines. // ============================================================================= const apModules = defineAccountPayableModule({ organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, businessPartner: { db: { supplierAccount: bpModules.db.supplierAccount }, queries: { getSupplierAccount: bpModules.queries.getSupplierAccount, }, }, primitives: { db: { currency: primitivesModules.db.currency, unit: primitivesModules.db.unit, }, queries: { getCurrency: primitivesModules.queries.getCurrency }, }, financialAccounting: { commands: { createJournalEntry: finModules.commands.createJournalEntry, postJournalEntry: finModules.commands.postJournalEntry, }, queries: { getPeriodByDate: finModules.queries.getPeriodByDate }, }, coaManagement: { db: { account: coaModules.db.account }, queries: { getAccount: coaModules.queries.getAccount, listAccounts: coaModules.queries.listAccounts, }, }, purchase: { db: { purchaseOrderLine: purchaseModules.db.purchaseOrderLine }, commands: { recalculatePurchaseOrderBillingStatus: purchaseModules.commands.recalculatePurchaseOrderBillingStatus, }, queries: { listPurchaseOrderLinesForMatching: purchaseModules.queries.listPurchaseOrderLinesForMatching, }, }, inventory: { commands: { postAcquisitionCostAdjustment: invModules.commands.postAcquisitionCostAdjustment, }, queries: { resolveItemValuationPolicies: invModules.queries.resolveItemValuationPolicies, }, }, }); export const accountPayableDocumentAttachment = db .table("AccountPayableDocumentAttachment", { accountPayableDocumentId: db .uuid() .relation({ type: "n-1", toward: { type: apModules.db.accountPayableDocument, as: "accountPayableDocument", }, backward: "attachments", }) .description("Account payable document that owns this attachment"), originalFileName: db.string().description("Original name of the uploaded file"), ...db.fields.timestamps(), }) .files({ file: "Supporting document attached to an account payable document", }) .permission(defaultPermission) .gqlPermission(defaultGqlPermission); // ============================================================================= // Sales — customer-facing order side (mirror of the purchase side). Owns the // Sales Order lifecycle and the sales Shipment (fulfillment commitment). Sales // is intentionally NOT wired to inventory: shipping a sales order advances the // order's fulfilledQuantity/status but does not move stock. Physical stock // leaves through the outbound-shipment module's Outbound Shipment (see the // settleOutboundShipment resolver, which orchestrates both). // ============================================================================= const salesModules = defineSalesModule({ // Human-readable document number for sales orders (SO-XXXXXXXX), auto-assigned // — mirrors the PurchaseOrder docNumber on the buy side so the UI shows a // document number instead of a raw UUID. salesOrder: { fields: { docNumber: db .string() .index() .unique() .serial({ start: 10000000, format: "SO-%d" }) .description("Auto-generated human-readable document number (e.g. SO-10000000)"), }, }, // sales owns only the order baseline + fulfillment/billing progress projection; // shipment evidence (and thus the site reference) moved out to outbound-shipment // in erp-kit 0.45+, so sales no longer depends on organization.site. organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, businessPartner: { db: { customerAccount: bpModules.db.customerAccount }, queries: { getCustomerAccount: bpModules.queries.getCustomerAccount, }, }, itemManagement: { db: { item: imModules.db.item }, queries: { getItem: imModules.queries.getItem }, }, primitives: { db: { currency: primitivesModules.db.currency, unit: primitivesModules.db.unit }, }, }); const arModules = defineAccountReceivableModule({ organization: { db: { company: orgModules.db.company }, queries: { getCompany: orgModules.queries.getCompany }, }, businessPartner: { db: { customerAccount: bpModules.db.customerAccount }, queries: { getCustomerAccount: bpModules.queries.getCustomerAccount, }, }, primitives: { db: { currency: primitivesModules.db.currency, unit: primitivesModules.db.unit }, queries: { getCurrency: primitivesModules.queries.getCurrency }, }, coaManagement: { db: { account: coaModules.db.account }, queries: { getAccount: coaModules.queries.getAccount, listAccounts: coaModules.queries.listAccounts, }, }, financialAccounting: { commands: { createJournalEntry: finModules.commands.createJournalEntry, postJournalEntry: finModules.commands.postJournalEntry, }, queries: { getPeriodByDate: finModules.queries.getPeriodByDate }, }, sales: { db: { salesOrderLine: salesModules.db.salesOrderLine }, commands: { recalculateSalesOrderBillingStatus: salesModules.commands.recalculateSalesOrderBillingStatus, }, }, }); // ============================================================================= // outbound-shipment — posts physical stock issues and synchronizes shipment // evidence into the sales-order fulfillment projection in the same transaction. // ============================================================================= const outboundShipmentModules = defineOutboundShipmentModule({ outboundShipment: { fields: { docNumber: db .string() .index() .unique() .serial({ start: 10000000, format: "OSH-%d" }) .description( "Auto-generated human-readable outbound-shipment document number (e.g. OSH-10000000)", ), }, }, itemManagement: { db: { item: imModules.db.item }, queries: { getItem: imModules.queries.getItem }, }, primitives: { db: { unit: primitivesModules.db.unit }, }, inventory: { db: { storageLocation: invModules.db.storageLocation }, commands: { postInventoryLedger: invModules.commands.postInventoryLedger }, queries: { getStorageLocation: invModules.queries.getStorageLocation }, }, sales: { commands: { recalculateSalesOrderFulfillmentStatus: salesModules.commands.recalculateSalesOrderFulfillmentStatus, }, }, }); export const primitivesModulesExport = primitivesModules; export const orgModulesExport = orgModules; export const bpModulesExport = bpModules; export const imModulesExport = imModules; export const pmModulesExport = pmModules; export const invModulesExport = invModules; export const inboundShipmentModulesExport = inboundShipmentModules; export const outboundShipmentModulesExport = outboundShipmentModules; export const coaModulesExport = coaModules; export const finModulesExport = finModules; export const apModulesExport = apModules; export const arModulesExport = arModules; export const salesModulesExport = salesModules;