@open-mercato/core 0.4.5-develop-811deeb983 → 0.4.5-develop-3d8e759e45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/catalog/inbox-actions.js +51 -0
- package/dist/modules/catalog/inbox-actions.js.map +7 -0
- package/dist/modules/customers/inbox-actions.js +230 -0
- package/dist/modules/customers/inbox-actions.js.map +7 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +40 -1
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/extract/route.js +87 -0
- package/dist/modules/inbox_ops/api/extract/route.js.map +7 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js +6 -1
- package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +40 -14
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +161 -79
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +109 -62
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +3 -3
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +36 -14
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js +65 -10
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +58 -10
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/inbox_ops/lib/constants.js.map +2 -2
- package/dist/modules/inbox_ops/lib/contactValidation.js +40 -0
- package/dist/modules/inbox_ops/lib/contactValidation.js.map +7 -0
- package/dist/modules/inbox_ops/lib/executionEngine.js +31 -826
- package/dist/modules/inbox_ops/lib/executionEngine.js.map +3 -3
- package/dist/modules/inbox_ops/lib/executionHelpers.js +368 -0
- package/dist/modules/inbox_ops/lib/executionHelpers.js.map +7 -0
- package/dist/modules/inbox_ops/lib/extractionPrompt.js +28 -35
- package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +3 -3
- package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js +1 -0
- package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js.map +7 -0
- package/dist/modules/inbox_ops/lib/translationProvider.js +15 -10
- package/dist/modules/inbox_ops/lib/translationProvider.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js +16 -16
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
- package/dist/modules/sales/inbox-actions.js +278 -0
- package/dist/modules/sales/inbox-actions.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.mocks/inbox-actions.generated.js +5 -0
- package/package.json +2 -2
- package/src/modules/catalog/inbox-actions.ts +60 -0
- package/src/modules/customers/inbox-actions.ts +285 -0
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +44 -0
- package/src/modules/inbox_ops/api/extract/route.ts +94 -0
- package/src/modules/inbox_ops/api/proposals/[id]/translate/route.ts +6 -1
- package/src/modules/inbox_ops/api/proposals/counts/route.ts +2 -0
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +43 -13
- package/src/modules/inbox_ops/backend/inbox-ops/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +176 -81
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +122 -68
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.meta.ts +2 -2
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +36 -14
- package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +91 -7
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +64 -12
- package/src/modules/inbox_ops/lib/constants.ts +9 -0
- package/src/modules/inbox_ops/lib/contactValidation.ts +54 -0
- package/src/modules/inbox_ops/lib/executionEngine.ts +47 -1060
- package/src/modules/inbox_ops/lib/executionHelpers.ts +527 -0
- package/src/modules/inbox_ops/lib/extractionPrompt.ts +45 -34
- package/src/modules/inbox_ops/lib/inbox-actions-generated.d.ts +11 -0
- package/src/modules/inbox_ops/lib/translationProvider.ts +16 -10
- package/src/modules/inbox_ops/subscribers/extractionWorker.ts +16 -18
- package/src/modules/sales/inbox-actions.ts +359 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/inbox_ops/lib/executionEngine.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityClass } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport type { EventBus } from '@open-mercato/events/types'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxProposal, InboxProposalAction, InboxDiscrepancy } from '../data/entities'\nimport type { InboxActionStatus, InboxActionType, InboxProposalStatus } from '../data/entities'\nimport {\n createContactPayloadSchema,\n createProductPayloadSchema,\n draftReplyPayloadSchema,\n linkContactPayloadSchema,\n logActivityPayloadSchema,\n orderPayloadSchema,\n updateOrderPayloadSchema,\n updateShipmentPayloadSchema,\n type CreateContactPayload,\n type CreateProductPayload,\n type DraftReplyPayload,\n type LinkContactPayload,\n type LogActivityPayload,\n type OrderPayload,\n type UpdateOrderPayload,\n type UpdateShipmentPayload,\n} from '../data/validators'\nimport { REQUIRED_FEATURES_MAP } from './constants'\nimport { formatZodErrors } from './validation'\n\ninterface CommonEntityFields {\n tenantId?: string\n organizationId?: string\n deletedAt?: Date | null\n createdAt?: Date\n}\n\nexport interface CrossModuleEntities {\n CustomerEntity: EntityClass<CommonEntityFields & { id: string; kind: string; displayName: string; primaryEmail?: string | null }>\n SalesOrder: EntityClass<CommonEntityFields & { id: string; orderNumber: string; currencyCode: string; comments?: string | null; customerReference?: string | null }>\n SalesShipment: EntityClass<CommonEntityFields & { id: string; order: unknown }>\n SalesChannel: EntityClass<CommonEntityFields & { id: string; name: string; currencyCode?: string; metadata?: Record<string, unknown> | null }>\n Dictionary: EntityClass<CommonEntityFields & { id: string; key: string }>\n DictionaryEntry: EntityClass<CommonEntityFields & { id: string; label: string; value: string; normalizedValue?: string | null; dictionary: unknown }>\n}\n\ninterface ExecutionContext {\n em: EntityManager\n userId: string\n tenantId: string\n organizationId: string\n eventBus?: EventBus | null\n container: AwilixContainer\n auth?: AuthContext\n entities?: CrossModuleEntities\n}\n\ninterface ExecutionResult {\n success: boolean\n createdEntityId?: string | null\n createdEntityType?: string | null\n error?: string\n statusCode?: number\n}\n\ninterface TypeExecutionResult {\n createdEntityId?: string | null\n createdEntityType?: string | null\n matchedEntityId?: string | null\n matchedEntityType?: string | null\n}\n\nclass ExecutionError extends Error {\n statusCode: number\n\n constructor(message: string, statusCode = 400) {\n super(message)\n this.statusCode = statusCode\n }\n}\n\nconst SALES_SHIPMENT_STATUS_DICTIONARY_KEY = 'sales.shipment_status'\nconst ACTION_EXECUTABLE_STATUSES: InboxActionStatus[] = ['pending', 'failed']\n\nexport async function executeAction(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<ExecutionResult> {\n const em = ctx.em.fork()\n\n try {\n await ensureUserCanExecuteAction(action, ctx)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unable to verify permissions'\n const statusCode = err instanceof ExecutionError ? err.statusCode : 503\n return { success: false, error: message, statusCode }\n }\n\n const claimed = await em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n proposalId: action.proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n deletedAt: null,\n },\n {\n status: 'processing',\n executionError: null,\n },\n )\n\n if (claimed === 0) {\n return { success: false, error: 'Action already processed', statusCode: 409 }\n }\n\n const freshAction = await findOneWithDecryption(\n em,\n InboxProposalAction,\n { id: action.id, deletedAt: null },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!freshAction) {\n return { success: false, error: 'Action not found', statusCode: 404 }\n }\n\n try {\n const result = await executeByType(freshAction, ctx)\n\n freshAction.status = 'executed'\n freshAction.executedAt = new Date()\n freshAction.executedByUserId = ctx.userId\n freshAction.createdEntityId = result.createdEntityId || null\n freshAction.createdEntityType = result.createdEntityType || null\n if (result.matchedEntityId !== undefined) {\n freshAction.matchedEntityId = result.matchedEntityId\n }\n if (result.matchedEntityType !== undefined) {\n freshAction.matchedEntityType = result.matchedEntityType\n }\n freshAction.executionError = null\n\n await em.flush()\n const encScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await resolveActionDiscrepancies(em, freshAction.id, encScope)\n\n // After create_contact or link_contact, resolve unknown_contact discrepancies\n // on ALL other actions in the same proposal that reference the same email\n if (freshAction.actionType === 'create_contact' || freshAction.actionType === 'link_contact') {\n const payload = freshAction.payload as Record<string, unknown> | null\n const contactEmail =\n typeof payload?.email === 'string' ? payload.email\n : typeof payload?.emailAddress === 'string' ? payload.emailAddress\n : null\n if (contactEmail) {\n await resolveUnknownContactDiscrepanciesInProposal(\n em, freshAction.proposalId, contactEmail, encScope,\n )\n }\n }\n\n await recalculateProposalStatus(em, freshAction.proposalId, encScope)\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.executed', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n createdEntityId: result.createdEntityId || null,\n createdEntityType: result.createdEntityType || null,\n executedByUserId: ctx.userId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n\n return { success: true, ...result, statusCode: 200 }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const statusCode = err instanceof ExecutionError ? err.statusCode : 500\n\n freshAction.status = 'failed'\n freshAction.executionError = message\n freshAction.executedAt = new Date()\n freshAction.executedByUserId = ctx.userId\n await em.flush()\n\n await recalculateProposalStatus(em, freshAction.proposalId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.failed', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n error: freshAction.executionError,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n\n return { success: false, error: freshAction.executionError || 'Unknown error', statusCode }\n }\n}\n\nexport async function rejectAction(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<void> {\n const em = ctx.em.fork()\n const rejectedAt = new Date()\n const claimed = await em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n proposalId: action.proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n deletedAt: null,\n },\n {\n status: 'rejected',\n executedAt: rejectedAt,\n executedByUserId: ctx.userId,\n },\n )\n if (claimed === 0) return\n\n const freshAction = await findOneWithDecryption(\n em,\n InboxProposalAction,\n { id: action.id, deletedAt: null },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!freshAction) return\n\n const encScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await resolveActionDiscrepancies(em, freshAction.id, encScope)\n await recalculateProposalStatus(em, freshAction.proposalId, encScope)\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.rejected', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n}\n\nexport async function rejectProposal(\n proposalId: string,\n ctx: ExecutionContext,\n): Promise<void> {\n const em = ctx.em.fork()\n const rejectedAt = new Date()\n\n await em.nativeUpdate(\n InboxProposalAction,\n {\n proposalId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n {\n status: 'rejected',\n executedAt: rejectedAt,\n executedByUserId: ctx.userId,\n },\n )\n\n await em.nativeUpdate(\n InboxDiscrepancy,\n {\n proposalId,\n resolved: false,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n },\n { resolved: true },\n )\n\n await recalculateProposalStatus(em, proposalId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.proposal.rejected', {\n proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n}\n\nexport async function acceptAllActions(\n proposalId: string,\n ctx: ExecutionContext,\n): Promise<{ results: ExecutionResult[]; stoppedOnFailure: boolean }> {\n const em = ctx.em.fork()\n const actions = await findWithDecryption(\n em,\n InboxProposalAction,\n {\n proposalId,\n status: 'pending',\n deletedAt: null,\n },\n { orderBy: { sortOrder: 'ASC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n const results: ExecutionResult[] = []\n let stoppedOnFailure = false\n\n for (const action of actions) {\n const result = await executeAction(action, ctx)\n results.push(result)\n\n if (!result.success) {\n stoppedOnFailure = true\n break\n }\n }\n\n return { results, stoppedOnFailure }\n}\n\n/**\n * Normalize LLM-generated payloads before validation.\n * Fixes common issues: case-sensitive enums, missing fields that can be resolved,\n * and alternate field names the LLM might use.\n */\nasync function normalizePayload(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<Record<string, unknown>> {\n const payload = { ...(action.payload as Record<string, unknown>) }\n\n // Lowercase contact type fields (LLM often outputs \"Person\" / \"Company\")\n if (typeof payload.type === 'string') {\n payload.type = payload.type.toLowerCase()\n }\n if (typeof payload.contactType === 'string') {\n payload.contactType = payload.contactType.toLowerCase()\n }\n\n // Normalize link_contact field names (LLM may use various alternatives for\n // emailAddress/contactId/contactType/contactName \u2014 e.g. from the pre-matched contacts format)\n if (action.actionType === 'link_contact') {\n if (!payload.emailAddress) {\n const alt = payload.email ?? payload.contactEmail\n if (typeof alt === 'string') payload.emailAddress = alt\n }\n if (!payload.contactId) {\n const alt = payload.id ?? payload.matchedId ?? payload.matchedContactId\n if (typeof alt === 'string') payload.contactId = alt\n }\n if (!payload.contactType) {\n const alt = payload.type ?? payload.kind ?? payload.matchedType ?? payload.matchedContactType\n if (typeof alt === 'string') payload.contactType = alt.toLowerCase()\n }\n if (!payload.contactName) {\n const alt = payload.name ?? payload.displayName\n if (typeof alt === 'string') payload.contactName = alt\n }\n }\n\n // Resolve missing currencyCode for order/quote payloads from channel\n if (action.actionType === 'create_order' || action.actionType === 'create_quote') {\n if (!payload.currencyCode) {\n const channelId = typeof payload.channelId === 'string' ? payload.channelId : null\n const resolved = await resolveChannelCurrency(ctx, channelId)\n if (resolved) payload.currencyCode = resolved\n }\n }\n\n return payload\n}\n\nasync function executeByType(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const payload = await normalizePayload(action, ctx)\n\n switch (action.actionType) {\n case 'create_order': {\n const parsed = orderPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid create_order payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeCreateDocumentAction(action, parsed.data, ctx, 'order')\n }\n case 'create_quote': {\n const parsed = orderPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid create_quote payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeCreateDocumentAction(action, parsed.data, ctx, 'quote')\n }\n case 'update_order': {\n const parsed = updateOrderPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid update_order payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeUpdateOrderAction(parsed.data, ctx)\n }\n case 'update_shipment': {\n const parsed = updateShipmentPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid update_shipment payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeUpdateShipmentAction(parsed.data, ctx)\n }\n case 'create_contact': {\n const parsed = createContactPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid create_contact payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeCreateContactAction(parsed.data, ctx)\n }\n case 'create_product': {\n const parsed = createProductPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid create_product payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeCreateProductAction(action, parsed.data, ctx)\n }\n case 'link_contact': {\n const parsed = linkContactPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid link_contact payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeLinkContactAction(parsed.data)\n }\n case 'log_activity': {\n const parsed = logActivityPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid log_activity payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeLogActivityAction(parsed.data, ctx)\n }\n case 'draft_reply': {\n const parsed = draftReplyPayloadSchema.safeParse(payload)\n if (!parsed.success) throw new ExecutionError(`Invalid draft_reply payload: ${formatZodErrors(parsed.error)}`, 400)\n return executeDraftReplyAction(action, parsed.data, ctx)\n }\n default:\n throw new ExecutionError(`Unknown action type: ${action.actionType}`, 400)\n }\n}\n\nasync function executeCreateDocumentAction(\n action: InboxProposalAction,\n payload: OrderPayload,\n ctx: ExecutionContext,\n kind: 'order' | 'quote',\n): Promise<TypeExecutionResult> {\n // Resolve channelId if not provided\n let resolvedChannelId: string | undefined = payload.channelId\n if (!resolvedChannelId) {\n resolvedChannelId = (await resolveFirstChannelId(ctx)) ?? undefined\n if (!resolvedChannelId) {\n throw new ExecutionError('No sales channel available. Create a channel first or set channelId in the payload.', 400)\n }\n }\n\n const currencyCode = payload.currencyCode.trim().toUpperCase()\n const lines = payload.lineItems.map((line, index) => {\n const quantity = parseNumberToken(line.quantity, `lineItems[${index}].quantity`)\n const unitPrice = line.unitPrice\n ? parseNumberToken(line.unitPrice, `lineItems[${index}].unitPrice`)\n : undefined\n\n const mappedLine: Record<string, unknown> = {\n lineNumber: index + 1,\n kind: line.kind ?? (line.productId ? 'product' : 'service'),\n name: line.productName,\n description: line.description,\n quantity,\n currencyCode,\n }\n\n if (line.productId) mappedLine.productId = line.productId\n if (line.variantId) mappedLine.productVariantId = line.variantId\n if (unitPrice !== undefined) mappedLine.unitPriceNet = unitPrice\n if (line.sku || line.catalogPrice) {\n mappedLine.catalogSnapshot = {\n sku: line.sku ?? null,\n catalogPrice: line.catalogPrice ?? null,\n }\n }\n\n return mappedLine\n })\n\n const metadata = buildSourceMetadata(action.id, action.proposalId)\n\n // Resolve customerEntityId: use explicit ID, or look up by email (contact may\n // have been created by a prior action in the same proposal batch)\n let resolvedCustomerEntityId = payload.customerEntityId\n if (!resolvedCustomerEntityId && payload.customerEmail) {\n resolvedCustomerEntityId = (await resolveCustomerEntityIdByEmail(ctx, payload.customerEmail)) ?? undefined\n }\n\n const createInput: Record<string, unknown> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n customerEntityId: resolvedCustomerEntityId,\n customerReference: payload.customerReference,\n channelId: resolvedChannelId,\n currencyCode,\n taxRateId: payload.taxRateId,\n comments: payload.notes,\n metadata,\n lines,\n }\n\n // Only provide a manual customerSnapshot when no entity could be resolved.\n // When customerEntityId is set, the sales command builds the proper nested\n // snapshot ({ customer: {...}, contact: {...} }) from the entity itself.\n if (!resolvedCustomerEntityId) {\n createInput.customerSnapshot = {\n displayName: payload.customerName,\n ...(payload.customerEmail && { primaryEmail: payload.customerEmail }),\n }\n }\n\n // Address resolution: explicit address from email > addressId from CRM enrichment\n const normalizedBilling = payload.billingAddress\n ? normalizeAddressSnapshot(payload.billingAddress)\n : undefined\n const normalizedShipping = payload.shippingAddress\n ? normalizeAddressSnapshot(payload.shippingAddress)\n : undefined\n\n if (normalizedShipping || normalizedBilling) {\n createInput.shippingAddressSnapshot = normalizedShipping ?? normalizedBilling\n createInput.billingAddressSnapshot = normalizedBilling ?? normalizedShipping\n } else if (payload.billingAddressId || payload.shippingAddressId) {\n createInput.billingAddressId = payload.billingAddressId ?? payload.shippingAddressId\n createInput.shippingAddressId = payload.shippingAddressId ?? payload.billingAddressId\n }\n\n const requestedDeliveryAt = parseDateToken(payload.requestedDeliveryDate ?? undefined)\n if (requestedDeliveryAt) {\n createInput.expectedDeliveryAt = requestedDeliveryAt\n }\n\n const effectiveKind = kind === 'order'\n ? await resolveEffectiveDocumentKind(ctx, resolvedChannelId)\n : kind\n\n if (effectiveKind === 'order') {\n const result = await executeCommand<Record<string, unknown>, { orderId?: string }>(\n ctx,\n 'sales.orders.create',\n createInput,\n )\n if (!result.orderId) {\n throw new ExecutionError('Order creation did not return an order ID', 500)\n }\n return {\n createdEntityId: result.orderId,\n createdEntityType: 'sales_order',\n }\n }\n\n const result = await executeCommand<Record<string, unknown>, { quoteId?: string }>(\n ctx,\n 'sales.quotes.create',\n createInput,\n )\n if (!result.quoteId) {\n throw new ExecutionError('Quote creation did not return a quote ID', 500)\n }\n return {\n createdEntityId: result.quoteId,\n createdEntityType: 'sales_quote',\n }\n}\n\nasync function executeUpdateOrderAction(\n payload: UpdateOrderPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const order = await resolveOrderByReference(\n ctx,\n payload.orderId,\n payload.orderNumber,\n )\n\n const updateInput: Record<string, unknown> = {\n id: order.id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n }\n\n const newDeliveryDate = parseDateToken(payload.deliveryDateChange?.newDate)\n if (newDeliveryDate) {\n updateInput.expectedDeliveryAt = newDeliveryDate\n }\n\n const noteLines = payload.noteAdditions?.map((note) => note.trim()).filter((note) => note.length > 0) ?? []\n if (noteLines.length > 0) {\n const mergedNotes = [order.comments ?? null, ...noteLines].filter(Boolean).join('\\n')\n updateInput.comments = mergedNotes\n }\n\n if (Object.keys(updateInput).length > 3) {\n await executeCommand<Record<string, unknown>, { orderId?: string }>(\n ctx,\n 'sales.orders.update',\n updateInput,\n )\n }\n\n const quantityChanges = payload.quantityChanges ?? []\n const orderLines = quantityChanges.length > 0 && quantityChanges.some((qc) => !qc.lineItemId)\n ? await loadOrderLineItems(ctx, order.id)\n : []\n\n for (const quantityChange of quantityChanges) {\n let lineItemId = quantityChange.lineItemId\n if (!lineItemId) {\n const matched = matchLineItemByName(orderLines, quantityChange.lineItemName)\n if (matched) {\n lineItemId = matched\n } else {\n const availableNames = orderLines.map((l) => l.name).filter(Boolean).join(', ')\n throw new ExecutionError(\n `Cannot resolve line item \"${quantityChange.lineItemName}\". Available line items: ${availableNames || 'none'}`,\n 400,\n )\n }\n }\n\n await executeCommand<{ body: Record<string, unknown> }, { orderId?: string; lineId?: string }>(\n ctx,\n 'sales.orders.lines.upsert',\n {\n body: {\n id: lineItemId,\n orderId: order.id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n quantity: parseNumberToken(quantityChange.newQuantity, 'quantityChanges.newQuantity'),\n currencyCode: order.currencyCode,\n },\n },\n )\n }\n\n return {\n createdEntityId: order.id,\n createdEntityType: 'sales_order',\n }\n}\n\nasync function executeUpdateShipmentAction(\n payload: UpdateShipmentPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const order = await resolveOrderByReference(\n ctx,\n payload.orderId,\n payload.orderNumber,\n )\n\n const SalesShipmentClass = resolveEntityClass(ctx, 'SalesShipment')\n if (!SalesShipmentClass) {\n throw new ExecutionError('Sales module entities not available', 503)\n }\n\n const shipment = await findOneWithDecryption(\n ctx.em,\n SalesShipmentClass,\n {\n order: order.id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n if (!shipment) {\n throw new ExecutionError('No shipment found for the referenced order', 404)\n }\n\n const statusEntryId = await resolveShipmentStatusEntryId(\n ctx,\n payload.statusLabel,\n )\n if (!statusEntryId) {\n throw new ExecutionError(`Shipment status \"${payload.statusLabel}\" not found`, 400)\n }\n\n const updateInput: Record<string, unknown> = {\n id: shipment.id,\n orderId: order.id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n statusEntryId,\n }\n\n if (payload.trackingNumbers) updateInput.trackingNumbers = payload.trackingNumbers\n if (payload.carrierName) updateInput.carrierName = payload.carrierName\n if (payload.notes) updateInput.notes = payload.notes\n\n const shippedAt = parseDateToken(payload.shippedAt)\n const deliveredAt = parseDateToken(payload.deliveredAt)\n if (shippedAt) updateInput.shippedAt = shippedAt\n if (deliveredAt) updateInput.deliveredAt = deliveredAt\n\n await executeCommand<Record<string, unknown>, { shipmentId?: string }>(\n ctx,\n 'sales.shipments.update',\n updateInput,\n )\n\n return {\n createdEntityId: shipment.id,\n createdEntityType: 'sales_shipment',\n }\n}\n\nasync function executeCreateContactAction(\n payload: CreateContactPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const CustomerEntityClass = resolveEntityClass(ctx, 'CustomerEntity')\n if (payload.email && CustomerEntityClass) {\n const emailLower = payload.email.trim().toLowerCase()\n // Try direct DB lookup first (works when primaryEmail is not encrypted)\n let existingContact = await findOneWithDecryption(\n ctx.em,\n CustomerEntityClass,\n {\n primaryEmail: emailLower,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n // Fallback: in-memory email check for encrypted primaryEmail fields\n if (!existingContact) {\n const candidates = await findWithDecryption(\n ctx.em,\n CustomerEntityClass,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { limit: 100, orderBy: { createdAt: 'DESC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n existingContact = candidates.find(\n (e) => e.primaryEmail && e.primaryEmail.toLowerCase() === emailLower,\n ) ?? null\n }\n if (existingContact) {\n const isCompany = existingContact.kind === 'company'\n return {\n createdEntityId: existingContact.id,\n createdEntityType: isCompany ? 'customer_company' : 'customer_person',\n matchedEntityId: existingContact.id,\n matchedEntityType: isCompany ? 'company' : 'person',\n }\n }\n }\n\n if (payload.type === 'company') {\n const result = await executeCommand<Record<string, unknown>, { entityId?: string }>(\n ctx,\n 'customers.companies.create',\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n displayName: payload.name,\n legalName: payload.companyName ?? payload.name,\n primaryEmail: payload.email,\n primaryPhone: payload.phone,\n source: payload.source,\n },\n )\n if (!result.entityId) {\n throw new ExecutionError('Company creation did not return an entity ID', 500)\n }\n return {\n createdEntityId: result.entityId,\n createdEntityType: 'customer_company',\n }\n }\n\n const { firstName, lastName } = splitPersonName(payload.name)\n const result = await executeCommand<Record<string, unknown>, { entityId?: string }>(\n ctx,\n 'customers.people.create',\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n displayName: payload.name,\n firstName,\n lastName,\n primaryEmail: payload.email,\n primaryPhone: payload.phone,\n jobTitle: payload.role,\n source: payload.source,\n },\n )\n\n if (!result.entityId) {\n throw new ExecutionError('Person creation did not return an entity ID', 500)\n }\n\n return {\n createdEntityId: result.entityId,\n createdEntityType: 'customer_person',\n }\n}\n\nasync function resolveUnknownContactDiscrepanciesInProposal(\n em: EntityManager,\n proposalId: string,\n contactEmail: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n if (!contactEmail) return\n\n const discrepancies = await findWithDecryption(\n em,\n InboxDiscrepancy,\n {\n proposalId,\n type: 'unknown_contact',\n resolved: false,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n undefined,\n scope,\n )\n\n const normalizedEmail = contactEmail.trim().toLowerCase()\n const matching = discrepancies.filter((d) => {\n const foundValue = (d.foundValue || '').trim().toLowerCase()\n return foundValue === normalizedEmail\n })\n\n for (const d of matching) {\n d.resolved = true\n }\n\n if (matching.length > 0) {\n await em.flush()\n }\n}\n\nasync function executeCreateProductAction(\n action: InboxProposalAction,\n payload: CreateProductPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const createInput: Record<string, unknown> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n title: payload.title,\n productType: 'simple',\n isActive: true,\n }\n\n if (payload.sku) createInput.sku = payload.sku\n if (payload.description) createInput.description = payload.description\n if (payload.currencyCode) createInput.primaryCurrencyCode = payload.currencyCode\n\n const result = await executeCommand<Record<string, unknown>, { productId?: string }>(\n ctx,\n 'catalog.products.create',\n createInput,\n )\n\n if (!result.productId) {\n throw new ExecutionError('Product creation did not return a product ID', 500)\n }\n\n await resolveProductDiscrepanciesInProposal(ctx.em, action.proposalId, payload.title, result.productId, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n\n return {\n createdEntityId: result.productId,\n createdEntityType: 'catalog_product',\n }\n}\n\nasync function resolveProductDiscrepanciesInProposal(\n em: EntityManager,\n proposalId: string,\n productTitle: string,\n productId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const discrepancies = await findWithDecryption(\n em,\n InboxDiscrepancy,\n {\n proposalId,\n type: 'product_not_found',\n resolved: false,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n undefined,\n scope,\n )\n\n const normalizedTitle = productTitle.toLowerCase().trim()\n const matchingDiscrepancies = discrepancies.filter((d) => {\n const foundValue = (d.foundValue || '').toLowerCase().trim()\n return foundValue === normalizedTitle\n })\n\n if (matchingDiscrepancies.length === 0) return\n\n // Phase 1: flush scalar mutations before any queries to avoid UoW tracking loss (SPEC-018)\n for (const discrepancy of matchingDiscrepancies) {\n discrepancy.resolved = true\n }\n await em.flush()\n\n // Phase 2: update line item product IDs (involves findOneWithDecryption queries)\n const actionIds = matchingDiscrepancies\n .map((d) => d.actionId)\n .filter((id): id is string => !!id)\n\n for (const actionId of actionIds) {\n await updateLineItemProductId(em, actionId, normalizedTitle, productId, scope)\n }\n\n if (actionIds.length > 0) {\n await em.flush()\n }\n}\n\nasync function updateLineItemProductId(\n em: EntityManager,\n actionId: string,\n productName: string,\n productId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const action = await findOneWithDecryption(\n em,\n InboxProposalAction,\n { id: actionId, deletedAt: null },\n undefined,\n scope,\n )\n if (!action) return\n\n const payload = action.payload as Record<string, unknown>\n const lineItems = Array.isArray(payload?.lineItems)\n ? (payload.lineItems as Record<string, unknown>[])\n : []\n\n let updated = false\n for (const item of lineItems) {\n if (item.productId) continue\n const itemName = (typeof item.productName === 'string' ? item.productName : '').toLowerCase().trim()\n if (itemName === productName) {\n item.productId = productId\n updated = true\n break\n }\n }\n\n if (updated) {\n action.payload = { ...payload, lineItems }\n }\n}\n\nfunction executeLinkContactAction(payload: LinkContactPayload): TypeExecutionResult {\n return {\n createdEntityId: payload.contactId,\n createdEntityType: payload.contactType === 'company' ? 'customer_company' : 'customer_person',\n matchedEntityId: payload.contactId,\n matchedEntityType: payload.contactType,\n }\n}\n\nasync function executeLogActivityAction(\n payload: LogActivityPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n if (!payload.contactId) {\n const resolved = await resolveContactIdByNameAndType(ctx, payload.contactName, payload.contactType)\n if (resolved) {\n payload = { ...payload, contactId: resolved }\n } else {\n throw new ExecutionError(\n `log_activity requires contactId \u2014 could not resolve contact \"${payload.contactName}\" (${payload.contactType})`,\n 400,\n )\n }\n }\n\n const result = await executeCommand<Record<string, unknown>, { activityId?: string }>(\n ctx,\n 'customers.activities.create',\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n entityId: payload.contactId,\n activityType: payload.activityType,\n subject: payload.subject,\n body: payload.body,\n authorUserId: ctx.userId,\n },\n )\n\n if (!result.activityId) {\n throw new ExecutionError('Activity creation did not return an activity ID', 500)\n }\n\n return {\n createdEntityId: result.activityId,\n createdEntityType: 'customer_activity',\n }\n}\n\nasync function executeDraftReplyAction(\n action: InboxProposalAction,\n payload: DraftReplyPayload,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n const payloadRecord = action.payload as Record<string, unknown>\n const explicitContactId = typeof payloadRecord.contactId === 'string' ? payloadRecord.contactId : null\n const contactId = explicitContactId ?? (await resolveCustomerEntityIdByEmail(ctx, payload.to))\n\n if (!contactId) {\n throw new ExecutionError(\n `No matching contact found for \"${payload.to}\". Create the contact first or link an existing one.`,\n 400,\n )\n }\n\n const details = [\n payload.body.trim(),\n '',\n '---',\n `Draft reply target: ${payload.to}`,\n `Subject: ${payload.subject}`,\n payload.context ? `Context: ${payload.context}` : null,\n `InboxOps Proposal: ${action.proposalId}`,\n `InboxOps Action: ${action.id}`,\n ]\n .filter((line) => typeof line === 'string' && line.length > 0)\n .join('\\n')\n\n const result = await executeCommand<Record<string, unknown>, { activityId?: string }>(\n ctx,\n 'customers.activities.create',\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n entityId: contactId,\n activityType: 'email',\n subject: payload.subject,\n body: details,\n authorUserId: ctx.userId,\n },\n )\n\n if (!result.activityId) {\n throw new ExecutionError('Draft reply activity did not return an activity ID', 500)\n }\n\n return {\n createdEntityId: result.activityId,\n createdEntityType: 'customer_activity',\n }\n}\n\nasync function ensureUserCanExecuteAction(action: InboxProposalAction, ctx: ExecutionContext): Promise<void> {\n const requiredFeature = getRequiredFeatureForAction(action)\n if (!requiredFeature) return\n\n const rbacService = ctx.container.resolve('rbacService') as {\n userHasAllFeatures: (\n userId: string,\n features: string[],\n scope: { tenantId: string; organizationId: string },\n ) => Promise<boolean>\n }\n\n if (!rbacService || typeof rbacService.userHasAllFeatures !== 'function') {\n throw new ExecutionError('Unable to verify permissions for action execution', 503)\n }\n\n const hasFeature = await rbacService.userHasAllFeatures(\n ctx.userId,\n [requiredFeature],\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n if (!hasFeature) {\n throw new ExecutionError(`Insufficient permissions: ${requiredFeature} required`, 403)\n }\n}\n\nasync function executeCommand<TInput, TResult>(\n ctx: ExecutionContext,\n commandId: string,\n input: TInput,\n): Promise<TResult> {\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n if (!commandBus || typeof commandBus.execute !== 'function') {\n throw new ExecutionError('Command bus is not available', 503)\n }\n\n const auth =\n ctx.auth ??\n ({\n sub: ctx.userId,\n userId: ctx.userId,\n tenantId: ctx.tenantId,\n orgId: ctx.organizationId,\n isSuperAdmin: false,\n } satisfies Exclude<AuthContext, null>)\n\n const commandContext: CommandRuntimeContext = {\n container: ctx.container,\n auth,\n organizationScope: null,\n selectedOrganizationId: ctx.organizationId,\n organizationIds: [ctx.organizationId],\n }\n\n const { result } = await commandBus.execute<TInput, TResult>(commandId, {\n input,\n ctx: commandContext,\n })\n\n return result\n}\n\nfunction buildSourceMetadata(actionId: string, proposalId: string): Record<string, unknown> {\n return {\n source: 'inbox_ops',\n inboxOpsActionId: actionId,\n inboxOpsProposalId: proposalId,\n }\n}\n\nasync function resolveOrderByReference(\n ctx: ExecutionContext,\n orderId?: string,\n orderNumber?: string,\n): Promise<{ id: string; orderNumber: string; currencyCode: string; comments?: string | null }> {\n const SalesOrderClass = resolveEntityClass(ctx, 'SalesOrder')\n if (!SalesOrderClass) {\n throw new ExecutionError('Sales module entities not available', 503)\n }\n\n const where: Record<string, unknown> = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }\n if (orderId) {\n where.id = orderId\n } else if (orderNumber && orderNumber.trim().length > 0) {\n where.orderNumber = orderNumber.trim()\n } else {\n throw new ExecutionError('Order reference is required', 400)\n }\n\n const order = await findOneWithDecryption(\n ctx.em,\n SalesOrderClass,\n where,\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!order) {\n throw new ExecutionError('Referenced order not found', 404)\n }\n return order\n}\n\nasync function resolveShipmentStatusEntryId(\n ctx: ExecutionContext,\n statusLabel: string,\n): Promise<string | null> {\n const DictionaryClass = resolveEntityClass(ctx, 'Dictionary')\n const DictionaryEntryClass = resolveEntityClass(ctx, 'DictionaryEntry')\n if (!DictionaryClass || !DictionaryEntryClass) return null\n\n const encryptionScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n\n const dictionary = await findOneWithDecryption(\n ctx.em,\n DictionaryClass,\n {\n key: SALES_SHIPMENT_STATUS_DICTIONARY_KEY,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n encryptionScope,\n )\n if (!dictionary) return null\n\n const entries = await findWithDecryption(\n ctx.em,\n DictionaryEntryClass,\n {\n dictionary: dictionary.id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n },\n undefined,\n encryptionScope,\n )\n if (!entries.length) return null\n\n const normalizedTarget = normalizeDictionaryToken(statusLabel)\n const loweredTarget = statusLabel.trim().toLowerCase()\n\n const match = entries.find((entry) => {\n const label = entry.label.trim().toLowerCase()\n const value = entry.value.trim().toLowerCase()\n return (\n entry.normalizedValue === normalizedTarget ||\n label === loweredTarget ||\n value === loweredTarget\n )\n })\n\n return match?.id ?? null\n}\n\nasync function resolveCustomerEntityIdByEmail(\n ctx: ExecutionContext,\n email: string,\n): Promise<string | null> {\n const normalized = email.trim().toLowerCase()\n if (!normalized) return null\n\n const CustomerEntityClass = resolveEntityClass(ctx, 'CustomerEntity')\n if (!CustomerEntityClass) return null\n\n // Try direct DB lookup first (works when primaryEmail is not encrypted)\n const entity = await findOneWithDecryption(\n ctx.em,\n CustomerEntityClass,\n {\n primaryEmail: normalized,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (entity) return entity.id\n\n // Fallback: in-memory email check for encrypted primaryEmail fields\n const candidates = await findWithDecryption(\n ctx.em,\n CustomerEntityClass,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { limit: 100, orderBy: { createdAt: 'DESC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const match = candidates.find(\n (e) => e.primaryEmail && e.primaryEmail.toLowerCase() === normalized,\n )\n return match?.id ?? null\n}\n\nasync function resolveEffectiveDocumentKind(\n ctx: ExecutionContext,\n channelId: string,\n): Promise<'order' | 'quote'> {\n const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')\n if (!SalesChannelClass) return 'order'\n\n const channel = await findOneWithDecryption(\n ctx.em,\n SalesChannelClass,\n {\n id: channelId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!channel) return 'order'\n\n const metadata = channel.metadata as Record<string, unknown> | null\n if (metadata?.quotesRequired === true) {\n return 'quote'\n }\n return 'order'\n}\n\nasync function resolveFirstChannelId(ctx: ExecutionContext): Promise<string | null> {\n const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')\n if (!SalesChannelClass) return null\n\n try {\n const channel = await findOneWithDecryption(\n ctx.em,\n SalesChannelClass,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { orderBy: { name: 'ASC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n return channel?.id ?? null\n } catch {\n return null\n }\n}\n\nfunction resolveEntityClass<K extends keyof CrossModuleEntities>(\n ctx: ExecutionContext,\n key: K,\n): CrossModuleEntities[K] | null {\n const fromEntities = ctx.entities?.[key]\n if (fromEntities) return fromEntities\n try { return ctx.container.resolve(key) } catch { return null }\n}\n\nasync function resolveChannelCurrency(\n ctx: ExecutionContext,\n channelId: string | null,\n): Promise<string | null> {\n const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')\n if (!SalesChannelClass) return null\n\n try {\n const where: Record<string, unknown> = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }\n if (channelId) where.id = channelId\n const channel = await findOneWithDecryption(\n ctx.em,\n SalesChannelClass,\n where,\n channelId ? undefined : { orderBy: { name: 'ASC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n return channel?.currencyCode ?? null\n } catch {\n return null\n }\n}\n\nasync function resolveContactIdByNameAndType(\n ctx: ExecutionContext,\n contactName: string,\n contactType: string,\n): Promise<string | null> {\n const CustomerEntityClass = resolveEntityClass(ctx, 'CustomerEntity')\n if (!CustomerEntityClass) return null\n\n const normalized = contactName.trim()\n if (!normalized) return null\n\n const entity = await findOneWithDecryption(\n ctx.em,\n CustomerEntityClass,\n {\n displayName: normalized,\n kind: contactType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n return entity?.id ?? null\n}\n\ninterface OrderLineItem {\n id: string\n name?: string | null\n}\n\nasync function loadOrderLineItems(\n ctx: ExecutionContext,\n orderId: string,\n): Promise<OrderLineItem[]> {\n try {\n const result = await executeCommand<Record<string, unknown>, { lines?: OrderLineItem[] }>(\n ctx,\n 'sales.orders.lines.list',\n { orderId, organizationId: ctx.organizationId, tenantId: ctx.tenantId },\n )\n return result.lines ?? []\n } catch {\n return []\n }\n}\n\nfunction matchLineItemByName(\n orderLines: OrderLineItem[],\n lineItemName: string,\n): string | null {\n const target = lineItemName.trim().toLowerCase()\n if (!target) return null\n\n const exact = orderLines.find((l) => (l.name || '').trim().toLowerCase() === target)\n if (exact) return exact.id\n\n const partial = orderLines.find((l) => {\n const name = (l.name || '').trim().toLowerCase()\n return name.includes(target) || target.includes(name)\n })\n return partial?.id ?? null\n}\n\nfunction normalizeDictionaryToken(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s-]+/g, '_')\n}\n\nfunction splitPersonName(name: string): { firstName: string; lastName: string } {\n const trimmed = name.trim()\n const parts = trimmed.split(/\\s+/).filter((item) => item.length > 0)\n if (parts.length <= 1) {\n return {\n firstName: parts[0] || trimmed,\n lastName: '',\n }\n }\n return {\n firstName: parts[0],\n lastName: parts.slice(1).join(' '),\n }\n}\n\nfunction parseNumberToken(value: string, fieldName: string): number {\n const parsed = Number(value)\n if (!Number.isFinite(parsed)) {\n throw new ExecutionError(`Invalid numeric value for ${fieldName}`, 400)\n }\n return parsed\n}\n\nfunction normalizeAddressSnapshot(\n address: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n addressLine1: address.line1 ?? address.addressLine1 ?? '',\n addressLine2: address.line2 ?? address.addressLine2 ?? null,\n companyName: address.company ?? address.companyName ?? null,\n name: address.contactName ?? address.name ?? null,\n city: address.city ?? null,\n region: address.state ?? address.region ?? null,\n postalCode: address.postalCode ?? null,\n country: address.country ?? null,\n }\n}\n\nfunction parseDateToken(value?: string | null): Date | undefined {\n if (!value) return undefined\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return undefined\n return parsed\n}\n\nasync function resolveActionDiscrepancies(\n em: EntityManager,\n actionId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const discrepancies = await findWithDecryption(\n em,\n InboxDiscrepancy,\n { actionId, resolved: false },\n undefined,\n scope,\n )\n for (const discrepancy of discrepancies) {\n discrepancy.resolved = true\n }\n if (discrepancies.length > 0) {\n await em.flush()\n }\n}\n\nexport async function recalculateProposalStatus(\n em: EntityManager,\n proposalId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const proposal = await findOneWithDecryption(\n em,\n InboxProposal,\n { id: proposalId, deletedAt: null },\n undefined,\n scope,\n )\n if (!proposal) return\n\n const actions = await findWithDecryption(\n em,\n InboxProposalAction,\n { proposalId, deletedAt: null },\n undefined,\n scope,\n )\n\n if (actions.length === 0) {\n proposal.status = 'pending'\n await em.flush()\n return\n }\n\n const statuses = actions.map((action) => action.status)\n const allAcceptedOrExecuted = statuses.every((status) => status === 'accepted' || status === 'executed')\n const allRejected = statuses.every((status) => status === 'rejected')\n const allPending = statuses.every((status) => status === 'pending')\n\n let newStatus: InboxProposalStatus\n if (allAcceptedOrExecuted) {\n newStatus = 'accepted'\n } else if (allRejected) {\n newStatus = 'rejected'\n } else if (allPending) {\n newStatus = 'pending'\n } else {\n newStatus = 'partial'\n }\n\n if (proposal.status !== newStatus) {\n proposal.status = newStatus\n await em.flush()\n }\n}\n\nexport function getRequiredFeature(actionType: InboxActionType): string {\n return REQUIRED_FEATURES_MAP[actionType]\n}\n\nfunction getRequiredFeatureForAction(action: InboxProposalAction): string {\n if (action.actionType === 'create_contact') {\n const payload = action.payload as Record<string, unknown> | null\n if (payload?.type === 'company') {\n return 'customers.companies.manage'\n }\n }\n return REQUIRED_FEATURES_MAP[action.actionType]\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityClass } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport type { EventBus } from '@open-mercato/events/types'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'\nimport { InboxProposal, InboxProposalAction, InboxDiscrepancy } from '../data/entities'\nimport type { InboxActionStatus, InboxActionType, InboxProposalStatus } from '../data/entities'\nimport { REQUIRED_FEATURES_MAP } from './constants'\nimport { formatZodErrors } from './validation'\nimport { ExecutionError, executeCommand } from './executionHelpers'\n\ninterface CommonEntityFields {\n tenantId?: string\n organizationId?: string\n deletedAt?: Date | null\n createdAt?: Date\n}\n\nexport interface CrossModuleEntities {\n CustomerEntity: EntityClass<CommonEntityFields & { id: string; kind: string; displayName: string; primaryEmail?: string | null }>\n SalesOrder: EntityClass<CommonEntityFields & { id: string; orderNumber: string; currencyCode: string; comments?: string | null; customerReference?: string | null }>\n SalesShipment: EntityClass<CommonEntityFields & { id: string; order: unknown }>\n SalesChannel: EntityClass<CommonEntityFields & { id: string; name: string; currencyCode?: string; metadata?: Record<string, unknown> | null }>\n Dictionary: EntityClass<CommonEntityFields & { id: string; key: string }>\n DictionaryEntry: EntityClass<CommonEntityFields & { id: string; label: string; value: string; normalizedValue?: string | null; dictionary: unknown }>\n}\n\ninterface ExecutionContext {\n em: EntityManager\n userId: string\n tenantId: string\n organizationId: string\n eventBus?: EventBus | null\n container: AwilixContainer\n auth?: AuthContext\n entities?: CrossModuleEntities\n}\n\ninterface ExecutionResult {\n success: boolean\n createdEntityId?: string | null\n createdEntityType?: string | null\n error?: string\n statusCode?: number\n}\n\ninterface TypeExecutionResult {\n createdEntityId?: string | null\n createdEntityType?: string | null\n matchedEntityId?: string | null\n matchedEntityType?: string | null\n}\n\nconst ACTION_EXECUTABLE_STATUSES: InboxActionStatus[] = ['pending', 'failed']\n\nexport async function executeAction(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<ExecutionResult> {\n const em = ctx.em.fork()\n\n try {\n await ensureUserCanExecuteAction(action, ctx)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unable to verify permissions'\n const statusCode = err instanceof ExecutionError ? err.statusCode : 503\n return { success: false, error: message, statusCode }\n }\n\n const claimed = await em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n proposalId: action.proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n deletedAt: null,\n },\n {\n status: 'processing',\n executionError: null,\n },\n )\n\n if (claimed === 0) {\n return { success: false, error: 'Action already processed', statusCode: 409 }\n }\n\n const freshAction = await findOneWithDecryption(\n em,\n InboxProposalAction,\n { id: action.id, deletedAt: null },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!freshAction) {\n return { success: false, error: 'Action not found', statusCode: 404 }\n }\n\n try {\n const result = await executeByType(freshAction, ctx)\n\n freshAction.status = 'executed'\n freshAction.executedAt = new Date()\n freshAction.executedByUserId = ctx.userId\n freshAction.createdEntityId = result.createdEntityId || null\n freshAction.createdEntityType = result.createdEntityType || null\n if (result.matchedEntityId !== undefined) {\n freshAction.matchedEntityId = result.matchedEntityId\n }\n if (result.matchedEntityType !== undefined) {\n freshAction.matchedEntityType = result.matchedEntityType\n }\n freshAction.executionError = null\n\n await em.flush()\n const encScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await resolveActionDiscrepancies(em, freshAction.id, encScope)\n\n // After create_contact or link_contact, resolve unknown_contact discrepancies\n // on ALL other actions in the same proposal that reference the same email\n if (freshAction.actionType === 'create_contact' || freshAction.actionType === 'link_contact') {\n const payload = freshAction.payload as Record<string, unknown> | null\n const contactEmail =\n typeof payload?.email === 'string' ? payload.email\n : typeof payload?.emailAddress === 'string' ? payload.emailAddress\n : null\n if (contactEmail) {\n await resolveUnknownContactDiscrepanciesInProposal(\n em, freshAction.proposalId, contactEmail, encScope,\n )\n }\n }\n\n await recalculateProposalStatus(em, freshAction.proposalId, encScope)\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.executed', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n createdEntityId: result.createdEntityId || null,\n createdEntityType: result.createdEntityType || null,\n executedByUserId: ctx.userId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n\n return { success: true, ...result, statusCode: 200 }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const statusCode = err instanceof ExecutionError ? err.statusCode : 500\n\n freshAction.status = 'failed'\n freshAction.executionError = message\n freshAction.executedAt = new Date()\n freshAction.executedByUserId = ctx.userId\n await em.flush()\n\n await recalculateProposalStatus(em, freshAction.proposalId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.failed', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n error: freshAction.executionError,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n\n return { success: false, error: freshAction.executionError || 'Unknown error', statusCode }\n }\n}\n\nexport async function rejectAction(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<void> {\n const em = ctx.em.fork()\n const rejectedAt = new Date()\n const claimed = await em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n proposalId: action.proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n deletedAt: null,\n },\n {\n status: 'rejected',\n executedAt: rejectedAt,\n executedByUserId: ctx.userId,\n },\n )\n if (claimed === 0) return\n\n const freshAction = await findOneWithDecryption(\n em,\n InboxProposalAction,\n { id: action.id, deletedAt: null },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!freshAction) return\n\n const encScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await resolveActionDiscrepancies(em, freshAction.id, encScope)\n await recalculateProposalStatus(em, freshAction.proposalId, encScope)\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.action.rejected', {\n actionId: freshAction.id,\n proposalId: freshAction.proposalId,\n actionType: freshAction.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n}\n\nexport async function rejectProposal(\n proposalId: string,\n ctx: ExecutionContext,\n): Promise<void> {\n const em = ctx.em.fork()\n const rejectedAt = new Date()\n\n await em.nativeUpdate(\n InboxProposalAction,\n {\n proposalId,\n status: { $in: ACTION_EXECUTABLE_STATUSES },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n {\n status: 'rejected',\n executedAt: rejectedAt,\n executedByUserId: ctx.userId,\n },\n )\n\n await em.nativeUpdate(\n InboxDiscrepancy,\n {\n proposalId,\n resolved: false,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n },\n { resolved: true },\n )\n\n await recalculateProposalStatus(em, proposalId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n\n if (ctx.eventBus) {\n await ctx.eventBus.emit('inbox_ops.proposal.rejected', {\n proposalId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n}\n\nexport async function acceptAllActions(\n proposalId: string,\n ctx: ExecutionContext,\n): Promise<{ results: ExecutionResult[]; stoppedOnFailure: boolean }> {\n const em = ctx.em.fork()\n const actions = await findWithDecryption(\n em,\n InboxProposalAction,\n {\n proposalId,\n status: 'pending',\n deletedAt: null,\n },\n { orderBy: { sortOrder: 'ASC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n const results: ExecutionResult[] = []\n let stoppedOnFailure = false\n\n for (const action of actions) {\n const result = await executeAction(action, ctx)\n results.push(result)\n\n if (!result.success) {\n stoppedOnFailure = true\n break\n }\n }\n\n return { results, stoppedOnFailure }\n}\n\n/**\n * Normalize common LLM payload issues before action-specific normalization.\n */\nfunction normalizeCommonPayloadFields(\n payload: Record<string, unknown>,\n actionType: string,\n): Record<string, unknown> {\n // Lowercase contact type fields (LLM often outputs \"Person\" / \"Company\")\n if (typeof payload.type === 'string') {\n payload.type = payload.type.toLowerCase()\n }\n if (typeof payload.contactType === 'string') {\n payload.contactType = payload.contactType.toLowerCase()\n }\n\n // Normalize link_contact field names (LLM may use various alternatives)\n if (actionType === 'link_contact') {\n if (!payload.emailAddress) {\n const alt = payload.email ?? payload.contactEmail\n if (typeof alt === 'string') payload.emailAddress = alt\n }\n if (!payload.contactId) {\n const alt = payload.id ?? payload.matchedId ?? payload.matchedContactId\n if (typeof alt === 'string') payload.contactId = alt\n }\n if (!payload.contactType) {\n const alt = payload.type ?? payload.kind ?? payload.matchedType ?? payload.matchedContactType\n if (typeof alt === 'string') payload.contactType = alt.toLowerCase()\n }\n if (!payload.contactName) {\n const alt = payload.name ?? payload.displayName\n if (typeof alt === 'string') payload.contactName = alt\n }\n }\n\n return payload\n}\n\n/**\n * Adapt the internal ExecutionContext to the shared InboxActionExecutionContext\n * for use by registered action handlers.\n */\nfunction adaptContext(ctx: ExecutionContext): InboxActionExecutionContext {\n return {\n ...ctx,\n executeCommand: <TInput, TResult>(commandId: string, input: TInput) =>\n executeCommand<TInput, TResult>(ctx, commandId, input),\n resolveEntityClass: <T>(key: string) =>\n resolveEntityClassInternal(ctx, key) as (new (...args: unknown[]) => T) | null,\n }\n}\n\nasync function executeByType(\n action: InboxProposalAction,\n ctx: ExecutionContext,\n): Promise<TypeExecutionResult> {\n // Lazy-load the generated registry to avoid circular imports at module load time\n const { getInboxAction } = await import('@/.mercato/generated/inbox-actions.generated')\n const definition = getInboxAction(action.actionType)\n if (!definition) {\n throw new ExecutionError(`Unknown action type: ${action.actionType}`, 400)\n }\n\n let payload = { ...(action.payload as Record<string, unknown>) }\n\n // Common normalization (lowercase enums, field aliases)\n payload = normalizeCommonPayloadFields(payload, action.actionType)\n\n // Action-specific normalization from the registered handler\n const actionCtx = adaptContext(ctx)\n if (definition.normalizePayload) {\n payload = await definition.normalizePayload(payload, actionCtx)\n }\n\n const parsed = definition.payloadSchema.safeParse(payload)\n if (!parsed.success) {\n throw new ExecutionError(\n `Invalid ${action.actionType} payload: ${formatZodErrors(parsed.error)}`,\n 400,\n )\n }\n\n return definition.execute(\n { id: action.id, proposalId: action.proposalId, payload: parsed.data },\n actionCtx,\n )\n}\n\nasync function resolveUnknownContactDiscrepanciesInProposal(\n em: EntityManager,\n proposalId: string,\n contactEmail: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n if (!contactEmail) return\n\n const discrepancies = await findWithDecryption(\n em,\n InboxDiscrepancy,\n {\n proposalId,\n type: 'unknown_contact',\n resolved: false,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n undefined,\n scope,\n )\n\n const normalizedEmail = contactEmail.trim().toLowerCase()\n const matching = discrepancies.filter((d) => {\n const foundValue = (d.foundValue || '').trim().toLowerCase()\n return foundValue === normalizedEmail\n })\n\n for (const d of matching) {\n d.resolved = true\n }\n\n if (matching.length > 0) {\n await em.flush()\n }\n}\n\nasync function ensureUserCanExecuteAction(action: InboxProposalAction, ctx: ExecutionContext): Promise<void> {\n const requiredFeature = getRequiredFeatureForAction(action)\n if (!requiredFeature) return\n\n const rbacService = ctx.container.resolve('rbacService') as {\n userHasAllFeatures: (\n userId: string,\n features: string[],\n scope: { tenantId: string; organizationId: string },\n ) => Promise<boolean>\n }\n\n if (!rbacService || typeof rbacService.userHasAllFeatures !== 'function') {\n throw new ExecutionError('Unable to verify permissions for action execution', 503)\n }\n\n const hasFeature = await rbacService.userHasAllFeatures(\n ctx.userId,\n [requiredFeature],\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n\n if (!hasFeature) {\n throw new ExecutionError(`Insufficient permissions: ${requiredFeature} required`, 403)\n }\n}\n\nfunction resolveEntityClassInternal(\n ctx: ExecutionContext,\n key: string,\n): unknown {\n const fromEntities = (ctx.entities as Record<string, unknown> | undefined)?.[key]\n if (fromEntities) return fromEntities\n try { return ctx.container.resolve(key) } catch { return null }\n}\n\n// Re-export splitPersonName for backward compat\nexport { splitPersonName } from './contactValidation'\n\nasync function resolveActionDiscrepancies(\n em: EntityManager,\n actionId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const discrepancies = await findWithDecryption(\n em,\n InboxDiscrepancy,\n { actionId, resolved: false },\n undefined,\n scope,\n )\n for (const discrepancy of discrepancies) {\n discrepancy.resolved = true\n }\n if (discrepancies.length > 0) {\n await em.flush()\n }\n}\n\nexport async function recalculateProposalStatus(\n em: EntityManager,\n proposalId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<void> {\n const proposal = await findOneWithDecryption(\n em,\n InboxProposal,\n { id: proposalId, deletedAt: null },\n undefined,\n scope,\n )\n if (!proposal) return\n\n const actions = await findWithDecryption(\n em,\n InboxProposalAction,\n { proposalId, deletedAt: null },\n undefined,\n scope,\n )\n\n if (actions.length === 0) {\n proposal.status = 'pending'\n await em.flush()\n return\n }\n\n const statuses = actions.map((action) => action.status)\n const allAcceptedOrExecuted = statuses.every((status) => status === 'accepted' || status === 'executed')\n const allRejected = statuses.every((status) => status === 'rejected')\n const allPending = statuses.every((status) => status === 'pending')\n\n let newStatus: InboxProposalStatus\n if (allAcceptedOrExecuted) {\n newStatus = 'accepted'\n } else if (allRejected) {\n newStatus = 'rejected'\n } else if (allPending) {\n newStatus = 'pending'\n } else {\n newStatus = 'partial'\n }\n\n if (proposal.status !== newStatus) {\n proposal.status = newStatus\n await em.flush()\n }\n}\n\nexport function getRequiredFeature(actionType: InboxActionType): string {\n return REQUIRED_FEATURES_MAP[actionType]\n}\n\nfunction getRequiredFeatureForAction(action: InboxProposalAction): string {\n if (action.actionType === 'create_contact') {\n const payload = action.payload as Record<string, unknown> | null\n if (payload?.type === 'company') {\n return 'customers.companies.manage'\n }\n }\n return REQUIRED_FEATURES_MAP[action.actionType]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { formatZodErrors } from "./validation.js";
|
|
3
|
+
function asHelperContext(ctx) {
|
|
4
|
+
return ctx;
|
|
5
|
+
}
|
|
6
|
+
class ExecutionError extends Error {
|
|
7
|
+
constructor(message, statusCode = 400) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function executeCommand(ctx, commandId, input) {
|
|
13
|
+
const commandBus = ctx.container.resolve("commandBus");
|
|
14
|
+
if (!commandBus || typeof commandBus.execute !== "function") {
|
|
15
|
+
throw new ExecutionError("Command bus is not available", 503);
|
|
16
|
+
}
|
|
17
|
+
const auth = ctx.auth ?? {
|
|
18
|
+
sub: ctx.userId,
|
|
19
|
+
userId: ctx.userId,
|
|
20
|
+
tenantId: ctx.tenantId,
|
|
21
|
+
orgId: ctx.organizationId,
|
|
22
|
+
isSuperAdmin: false
|
|
23
|
+
};
|
|
24
|
+
const commandContext = {
|
|
25
|
+
container: ctx.container,
|
|
26
|
+
auth,
|
|
27
|
+
organizationScope: null,
|
|
28
|
+
selectedOrganizationId: ctx.organizationId,
|
|
29
|
+
organizationIds: [ctx.organizationId]
|
|
30
|
+
};
|
|
31
|
+
const { result } = await commandBus.execute(commandId, {
|
|
32
|
+
input,
|
|
33
|
+
ctx: commandContext
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function resolveEntityClass(ctx, key) {
|
|
38
|
+
const fromEntities = ctx.entities?.[key];
|
|
39
|
+
if (fromEntities) return fromEntities;
|
|
40
|
+
try {
|
|
41
|
+
return ctx.container.resolve(key);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function buildSourceMetadata(actionId, proposalId) {
|
|
47
|
+
return {
|
|
48
|
+
source: "inbox_ops",
|
|
49
|
+
inboxOpsActionId: actionId,
|
|
50
|
+
inboxOpsProposalId: proposalId
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function resolveOrderByReference(ctx, orderId, orderNumber) {
|
|
54
|
+
const SalesOrderClass = resolveEntityClass(ctx, "SalesOrder");
|
|
55
|
+
if (!SalesOrderClass) {
|
|
56
|
+
throw new ExecutionError("Sales module entities not available", 503);
|
|
57
|
+
}
|
|
58
|
+
const where = {
|
|
59
|
+
tenantId: ctx.tenantId,
|
|
60
|
+
organizationId: ctx.organizationId,
|
|
61
|
+
deletedAt: null
|
|
62
|
+
};
|
|
63
|
+
if (orderId) {
|
|
64
|
+
where.id = orderId;
|
|
65
|
+
} else if (orderNumber && orderNumber.trim().length > 0) {
|
|
66
|
+
where.orderNumber = orderNumber.trim();
|
|
67
|
+
} else {
|
|
68
|
+
throw new ExecutionError("Order reference is required", 400);
|
|
69
|
+
}
|
|
70
|
+
const order = await findOneWithDecryption(
|
|
71
|
+
ctx.em,
|
|
72
|
+
SalesOrderClass,
|
|
73
|
+
where,
|
|
74
|
+
void 0,
|
|
75
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
76
|
+
);
|
|
77
|
+
if (!order) {
|
|
78
|
+
throw new ExecutionError("Referenced order not found", 404);
|
|
79
|
+
}
|
|
80
|
+
return order;
|
|
81
|
+
}
|
|
82
|
+
async function resolveFirstChannelId(ctx) {
|
|
83
|
+
const SalesChannelClass = resolveEntityClass(ctx, "SalesChannel");
|
|
84
|
+
if (!SalesChannelClass) return null;
|
|
85
|
+
try {
|
|
86
|
+
const channel = await findOneWithDecryption(
|
|
87
|
+
ctx.em,
|
|
88
|
+
SalesChannelClass,
|
|
89
|
+
{
|
|
90
|
+
tenantId: ctx.tenantId,
|
|
91
|
+
organizationId: ctx.organizationId,
|
|
92
|
+
deletedAt: null
|
|
93
|
+
},
|
|
94
|
+
{ orderBy: { name: "ASC" } },
|
|
95
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
96
|
+
);
|
|
97
|
+
return channel?.id ?? null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function resolveChannelCurrency(ctx, channelId) {
|
|
103
|
+
const SalesChannelClass = resolveEntityClass(ctx, "SalesChannel");
|
|
104
|
+
if (!SalesChannelClass) return null;
|
|
105
|
+
try {
|
|
106
|
+
const where = {
|
|
107
|
+
tenantId: ctx.tenantId,
|
|
108
|
+
organizationId: ctx.organizationId,
|
|
109
|
+
deletedAt: null
|
|
110
|
+
};
|
|
111
|
+
if (channelId) where.id = channelId;
|
|
112
|
+
const channel = await findOneWithDecryption(
|
|
113
|
+
ctx.em,
|
|
114
|
+
SalesChannelClass,
|
|
115
|
+
where,
|
|
116
|
+
channelId ? void 0 : { orderBy: { name: "ASC" } },
|
|
117
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
118
|
+
);
|
|
119
|
+
return channel?.currencyCode ?? null;
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function resolveEffectiveDocumentKind(ctx, channelId) {
|
|
125
|
+
const SalesChannelClass = resolveEntityClass(ctx, "SalesChannel");
|
|
126
|
+
if (!SalesChannelClass) return "order";
|
|
127
|
+
const channel = await findOneWithDecryption(
|
|
128
|
+
ctx.em,
|
|
129
|
+
SalesChannelClass,
|
|
130
|
+
{
|
|
131
|
+
id: channelId,
|
|
132
|
+
tenantId: ctx.tenantId,
|
|
133
|
+
organizationId: ctx.organizationId,
|
|
134
|
+
deletedAt: null
|
|
135
|
+
},
|
|
136
|
+
void 0,
|
|
137
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
138
|
+
);
|
|
139
|
+
if (!channel) return "order";
|
|
140
|
+
const metadata = channel.metadata;
|
|
141
|
+
if (metadata?.quotesRequired === true) {
|
|
142
|
+
return "quote";
|
|
143
|
+
}
|
|
144
|
+
return "order";
|
|
145
|
+
}
|
|
146
|
+
const SALES_SHIPMENT_STATUS_DICTIONARY_KEY = "sales.shipment_status";
|
|
147
|
+
async function resolveShipmentStatusEntryId(ctx, statusLabel) {
|
|
148
|
+
const DictionaryClass = resolveEntityClass(ctx, "Dictionary");
|
|
149
|
+
const DictionaryEntryClass = resolveEntityClass(ctx, "DictionaryEntry");
|
|
150
|
+
if (!DictionaryClass || !DictionaryEntryClass) return null;
|
|
151
|
+
const encryptionScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
152
|
+
const dictionary = await findOneWithDecryption(
|
|
153
|
+
ctx.em,
|
|
154
|
+
DictionaryClass,
|
|
155
|
+
{
|
|
156
|
+
key: SALES_SHIPMENT_STATUS_DICTIONARY_KEY,
|
|
157
|
+
tenantId: ctx.tenantId,
|
|
158
|
+
organizationId: ctx.organizationId,
|
|
159
|
+
deletedAt: null
|
|
160
|
+
},
|
|
161
|
+
void 0,
|
|
162
|
+
encryptionScope
|
|
163
|
+
);
|
|
164
|
+
if (!dictionary) return null;
|
|
165
|
+
const entries = await findWithDecryption(
|
|
166
|
+
ctx.em,
|
|
167
|
+
DictionaryEntryClass,
|
|
168
|
+
{
|
|
169
|
+
dictionary: dictionary.id,
|
|
170
|
+
tenantId: ctx.tenantId,
|
|
171
|
+
organizationId: ctx.organizationId
|
|
172
|
+
},
|
|
173
|
+
void 0,
|
|
174
|
+
encryptionScope
|
|
175
|
+
);
|
|
176
|
+
if (!entries.length) return null;
|
|
177
|
+
const normalizedTarget = normalizeDictionaryToken(statusLabel);
|
|
178
|
+
const loweredTarget = statusLabel.trim().toLowerCase();
|
|
179
|
+
const match = entries.find((entry) => {
|
|
180
|
+
const label = entry.label.trim().toLowerCase();
|
|
181
|
+
const value = entry.value.trim().toLowerCase();
|
|
182
|
+
return entry.normalizedValue === normalizedTarget || label === loweredTarget || value === loweredTarget;
|
|
183
|
+
});
|
|
184
|
+
return match?.id ?? null;
|
|
185
|
+
}
|
|
186
|
+
async function resolveCustomerEntityIdByEmail(ctx, email) {
|
|
187
|
+
const normalized = email.trim().toLowerCase();
|
|
188
|
+
if (!normalized) return null;
|
|
189
|
+
const CustomerEntityClass = resolveEntityClass(ctx, "CustomerEntity");
|
|
190
|
+
if (!CustomerEntityClass) return null;
|
|
191
|
+
const entity = await findOneWithDecryption(
|
|
192
|
+
ctx.em,
|
|
193
|
+
CustomerEntityClass,
|
|
194
|
+
{
|
|
195
|
+
primaryEmail: normalized,
|
|
196
|
+
tenantId: ctx.tenantId,
|
|
197
|
+
organizationId: ctx.organizationId,
|
|
198
|
+
deletedAt: null
|
|
199
|
+
},
|
|
200
|
+
void 0,
|
|
201
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
202
|
+
);
|
|
203
|
+
if (entity) return entity.id;
|
|
204
|
+
const candidates = await findWithDecryption(
|
|
205
|
+
ctx.em,
|
|
206
|
+
CustomerEntityClass,
|
|
207
|
+
{
|
|
208
|
+
tenantId: ctx.tenantId,
|
|
209
|
+
organizationId: ctx.organizationId,
|
|
210
|
+
deletedAt: null
|
|
211
|
+
},
|
|
212
|
+
{ limit: 100, orderBy: { createdAt: "DESC" } },
|
|
213
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
214
|
+
);
|
|
215
|
+
const match = candidates.find(
|
|
216
|
+
(e) => e.primaryEmail && e.primaryEmail.toLowerCase() === normalized
|
|
217
|
+
);
|
|
218
|
+
return match?.id ?? null;
|
|
219
|
+
}
|
|
220
|
+
async function resolveContactIdByNameAndType(ctx, contactName, contactType) {
|
|
221
|
+
const CustomerEntityClass = resolveEntityClass(ctx, "CustomerEntity");
|
|
222
|
+
if (!CustomerEntityClass) return null;
|
|
223
|
+
const normalized = contactName.trim();
|
|
224
|
+
if (!normalized) return null;
|
|
225
|
+
const entity = await findOneWithDecryption(
|
|
226
|
+
ctx.em,
|
|
227
|
+
CustomerEntityClass,
|
|
228
|
+
{
|
|
229
|
+
displayName: normalized,
|
|
230
|
+
kind: contactType,
|
|
231
|
+
tenantId: ctx.tenantId,
|
|
232
|
+
organizationId: ctx.organizationId,
|
|
233
|
+
deletedAt: null
|
|
234
|
+
},
|
|
235
|
+
void 0,
|
|
236
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
237
|
+
);
|
|
238
|
+
return entity?.id ?? null;
|
|
239
|
+
}
|
|
240
|
+
async function loadOrderLineItems(ctx, orderId) {
|
|
241
|
+
try {
|
|
242
|
+
const result = await executeCommand(
|
|
243
|
+
ctx,
|
|
244
|
+
"sales.orders.lines.list",
|
|
245
|
+
{ orderId, organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
246
|
+
);
|
|
247
|
+
return result.lines ?? [];
|
|
248
|
+
} catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function matchLineItemByName(orderLines, lineItemName) {
|
|
253
|
+
const target = lineItemName.trim().toLowerCase();
|
|
254
|
+
if (!target) return null;
|
|
255
|
+
const exact = orderLines.find((l) => (l.name || "").trim().toLowerCase() === target);
|
|
256
|
+
if (exact) return exact.id;
|
|
257
|
+
const partial = orderLines.find((l) => {
|
|
258
|
+
const name = (l.name || "").trim().toLowerCase();
|
|
259
|
+
return name.includes(target) || target.includes(name);
|
|
260
|
+
});
|
|
261
|
+
return partial?.id ?? null;
|
|
262
|
+
}
|
|
263
|
+
function normalizeAddressSnapshot(address) {
|
|
264
|
+
return {
|
|
265
|
+
addressLine1: address.line1 ?? address.addressLine1 ?? "",
|
|
266
|
+
addressLine2: address.line2 ?? address.addressLine2 ?? null,
|
|
267
|
+
companyName: address.company ?? address.companyName ?? null,
|
|
268
|
+
name: address.contactName ?? address.name ?? null,
|
|
269
|
+
city: address.city ?? null,
|
|
270
|
+
region: address.state ?? address.region ?? null,
|
|
271
|
+
postalCode: address.postalCode ?? null,
|
|
272
|
+
country: address.country ?? null
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function parseDateToken(value) {
|
|
276
|
+
if (!value) return void 0;
|
|
277
|
+
const parsed = new Date(value);
|
|
278
|
+
if (Number.isNaN(parsed.getTime())) return void 0;
|
|
279
|
+
return parsed;
|
|
280
|
+
}
|
|
281
|
+
function parseNumberToken(value, fieldName) {
|
|
282
|
+
const parsed = Number(value);
|
|
283
|
+
if (!Number.isFinite(parsed)) {
|
|
284
|
+
throw new ExecutionError(`Invalid numeric value for ${fieldName}`, 400);
|
|
285
|
+
}
|
|
286
|
+
return parsed;
|
|
287
|
+
}
|
|
288
|
+
function normalizeDictionaryToken(value) {
|
|
289
|
+
return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
290
|
+
}
|
|
291
|
+
async function resolveProductDiscrepanciesInProposal(em, proposalId, productTitle, productId, scope) {
|
|
292
|
+
const { InboxDiscrepancy, InboxProposalAction } = await import("../data/entities.js");
|
|
293
|
+
const discrepancies = await findWithDecryption(
|
|
294
|
+
em,
|
|
295
|
+
InboxDiscrepancy,
|
|
296
|
+
{
|
|
297
|
+
proposalId,
|
|
298
|
+
type: "product_not_found",
|
|
299
|
+
resolved: false,
|
|
300
|
+
tenantId: scope.tenantId,
|
|
301
|
+
organizationId: scope.organizationId
|
|
302
|
+
},
|
|
303
|
+
void 0,
|
|
304
|
+
scope
|
|
305
|
+
);
|
|
306
|
+
const normalizedTitle = productTitle.toLowerCase().trim();
|
|
307
|
+
const matchingDiscrepancies = discrepancies.filter((d) => {
|
|
308
|
+
const foundValue = (d.foundValue || "").toLowerCase().trim();
|
|
309
|
+
return foundValue === normalizedTitle;
|
|
310
|
+
});
|
|
311
|
+
if (matchingDiscrepancies.length === 0) return;
|
|
312
|
+
for (const discrepancy of matchingDiscrepancies) {
|
|
313
|
+
discrepancy.resolved = true;
|
|
314
|
+
}
|
|
315
|
+
await em.flush();
|
|
316
|
+
const actionIds = matchingDiscrepancies.map((d) => d.actionId).filter((id) => !!id);
|
|
317
|
+
for (const actionId of actionIds) {
|
|
318
|
+
const action = await findOneWithDecryption(
|
|
319
|
+
em,
|
|
320
|
+
InboxProposalAction,
|
|
321
|
+
{ id: actionId, deletedAt: null },
|
|
322
|
+
void 0,
|
|
323
|
+
scope
|
|
324
|
+
);
|
|
325
|
+
if (!action) continue;
|
|
326
|
+
const payload = action.payload;
|
|
327
|
+
const lineItems = Array.isArray(payload?.lineItems) ? payload.lineItems : [];
|
|
328
|
+
let updated = false;
|
|
329
|
+
for (const item of lineItems) {
|
|
330
|
+
if (item.productId) continue;
|
|
331
|
+
const itemName = (typeof item.productName === "string" ? item.productName : "").toLowerCase().trim();
|
|
332
|
+
if (itemName === normalizedTitle) {
|
|
333
|
+
item.productId = productId;
|
|
334
|
+
updated = true;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (updated) {
|
|
339
|
+
action.payload = { ...payload, lineItems };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (actionIds.length > 0) {
|
|
343
|
+
await em.flush();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
ExecutionError,
|
|
348
|
+
asHelperContext,
|
|
349
|
+
buildSourceMetadata,
|
|
350
|
+
executeCommand,
|
|
351
|
+
formatZodErrors,
|
|
352
|
+
loadOrderLineItems,
|
|
353
|
+
matchLineItemByName,
|
|
354
|
+
normalizeAddressSnapshot,
|
|
355
|
+
normalizeDictionaryToken,
|
|
356
|
+
parseDateToken,
|
|
357
|
+
parseNumberToken,
|
|
358
|
+
resolveChannelCurrency,
|
|
359
|
+
resolveContactIdByNameAndType,
|
|
360
|
+
resolveCustomerEntityIdByEmail,
|
|
361
|
+
resolveEffectiveDocumentKind,
|
|
362
|
+
resolveEntityClass,
|
|
363
|
+
resolveFirstChannelId,
|
|
364
|
+
resolveOrderByReference,
|
|
365
|
+
resolveProductDiscrepanciesInProposal,
|
|
366
|
+
resolveShipmentStatusEntryId
|
|
367
|
+
};
|
|
368
|
+
//# sourceMappingURL=executionHelpers.js.map
|