@open-mercato/core 0.4.5-develop-636d33c995 → 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/backend/catalog/categories/[id]/edit/page.js +17 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +30 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/inbox-actions.js +51 -0
- package/dist/modules/catalog/inbox-actions.js.map +7 -0
- package/dist/modules/catalog/lib/messageObjectPreviews.js +146 -0
- package/dist/modules/catalog/lib/messageObjectPreviews.js.map +7 -0
- package/dist/modules/catalog/message-objects.js +95 -0
- package/dist/modules/catalog/message-objects.js.map +7 -0
- package/dist/modules/currencies/backend/currencies/[id]/page.js +21 -0
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/lib/messageObjectPreviews.js +51 -0
- package/dist/modules/currencies/lib/messageObjectPreviews.js.map +7 -0
- package/dist/modules/currencies/message-objects.js +41 -0
- package/dist/modules/currencies/message-objects.js.map +7 -0
- package/dist/modules/customers/backend/customers/companies/[id]/page.js +20 -0
- package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js +20 -0
- package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyHighlights.js +18 -14
- package/dist/modules/customers/components/detail/CompanyHighlights.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonHighlights.js +18 -14
- package/dist/modules/customers/components/detail/PersonHighlights.js.map +2 -2
- package/dist/modules/customers/inbox-actions.js +230 -0
- package/dist/modules/customers/inbox-actions.js.map +7 -0
- package/dist/modules/customers/lib/messageObjectPreviews.js +41 -5
- package/dist/modules/customers/lib/messageObjectPreviews.js.map +2 -2
- package/dist/modules/customers/message-objects.js +31 -11
- package/dist/modules/customers/message-objects.js.map +2 -2
- 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/messages/commands/messages.js +3 -0
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/objects-panel.js +6 -1
- package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js +4 -1
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
- package/dist/modules/messages/frontend/messages/view/[token]/page.js +1 -0
- package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -7
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/lib/messageObjectPreviews.js +43 -0
- package/dist/modules/resources/lib/messageObjectPreviews.js.map +7 -0
- package/dist/modules/resources/message-objects.js +37 -0
- package/dist/modules/resources/message-objects.js.map +7 -0
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +19 -0
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +23 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/quotes/[id]/page.js +1 -1
- package/dist/modules/sales/backend/sales/quotes/[id]/page.js.map +2 -2
- package/dist/modules/sales/inbox-actions.js +278 -0
- package/dist/modules/sales/inbox-actions.js.map +7 -0
- package/dist/modules/sales/lib/messageObjectPreviews.js +49 -4
- package/dist/modules/sales/lib/messageObjectPreviews.js.map +2 -2
- package/dist/modules/sales/message-objects.js +44 -2
- package/dist/modules/sales/message-objects.js.map +2 -2
- package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +59 -30
- package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +2 -2
- package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +1 -1
- package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +1 -1
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +8 -30
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-availability/page.js +13 -0
- package/dist/modules/staff/backend/staff/my-availability/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +8 -31
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +32 -10
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +14 -1
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +14 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/components/TeamForm.js +4 -2
- package/dist/modules/staff/components/TeamForm.js.map +2 -2
- package/dist/modules/staff/components/TeamRoleForm.js +4 -2
- package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
- package/dist/modules/staff/lib/messageObjectPreviews.js +111 -2
- package/dist/modules/staff/lib/messageObjectPreviews.js.map +2 -2
- package/dist/modules/staff/message-objects.js +79 -8
- package/dist/modules/staff/message-objects.js.map +2 -2
- package/jest.config.cjs +1 -0
- package/jest.mocks/inbox-actions.generated.js +5 -0
- package/package.json +2 -2
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +19 -5
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +14 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +40 -0
- package/src/modules/catalog/inbox-actions.ts +60 -0
- package/src/modules/catalog/lib/messageObjectPreviews.ts +176 -0
- package/src/modules/catalog/message-objects.ts +102 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -0
- package/src/modules/currencies/lib/messageObjectPreviews.ts +65 -0
- package/src/modules/currencies/message-objects.ts +40 -0
- package/src/modules/customers/backend/customers/companies/[id]/page.tsx +19 -0
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +13 -0
- package/src/modules/customers/backend/customers/people/[id]/page.tsx +19 -0
- package/src/modules/customers/components/detail/CompanyHighlights.tsx +14 -9
- package/src/modules/customers/components/detail/PersonHighlights.tsx +14 -9
- package/src/modules/customers/inbox-actions.ts +285 -0
- package/src/modules/customers/lib/messageObjectPreviews.ts +43 -3
- package/src/modules/customers/message-objects.ts +31 -11
- 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/messages/commands/messages.ts +4 -0
- package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +8 -1
- package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +3 -0
- package/src/modules/messages/frontend/messages/view/[token]/page.tsx +1 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +20 -4
- package/src/modules/resources/lib/messageObjectPreviews.ts +55 -0
- package/src/modules/resources/message-objects.ts +36 -0
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +18 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -0
- package/src/modules/sales/backend/sales/quotes/[id]/page.tsx +1 -1
- package/src/modules/sales/inbox-actions.ts +359 -0
- package/src/modules/sales/lib/messageObjectPreviews.ts +54 -4
- package/src/modules/sales/message-objects.ts +44 -2
- package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +72 -34
- package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +1 -1
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +7 -29
- package/src/modules/staff/backend/staff/my-availability/page.tsx +14 -0
- package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +8 -30
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +28 -7
- package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +12 -0
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +12 -0
- package/src/modules/staff/components/TeamForm.tsx +3 -0
- package/src/modules/staff/components/TeamRoleForm.tsx +3 -0
- package/src/modules/staff/lib/messageObjectPreviews.ts +133 -2
- package/src/modules/staff/message-objects.ts +79 -8
- package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +0 -51
- package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +0 -7
- package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +0 -35
- package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +0 -7
- package/dist/modules/customers/widgets/messages/index.js +0 -7
- package/dist/modules/customers/widgets/messages/index.js.map +0 -7
- package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +0 -51
- package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +0 -7
- package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +0 -34
- package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +0 -7
- package/dist/modules/staff/widgets/messages/index.js +0 -7
- package/dist/modules/staff/widgets/messages/index.js.map +0 -7
- package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +0 -57
- package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +0 -49
- package/src/modules/customers/widgets/messages/index.ts +0 -2
- package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +0 -57
- package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +0 -44
- package/src/modules/staff/widgets/messages/index.ts +0 -2
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import type { EntityClass } from '@mikro-orm/core'
|
|
3
|
+
import type { AwilixContainer } from 'awilix'
|
|
4
|
+
import type { EventBus } from '@open-mercato/events/types'
|
|
5
|
+
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
6
|
+
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
8
|
+
import type { InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'
|
|
9
|
+
import type { CrossModuleEntities } from './executionEngine'
|
|
10
|
+
export { formatZodErrors } from './validation'
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Context type used by helper functions (concrete types for ORM/DI access)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface ExecutionHelperContext {
|
|
17
|
+
em: EntityManager
|
|
18
|
+
userId: string
|
|
19
|
+
tenantId: string
|
|
20
|
+
organizationId: string
|
|
21
|
+
eventBus?: EventBus | null
|
|
22
|
+
container: AwilixContainer
|
|
23
|
+
auth?: AuthContext
|
|
24
|
+
entities?: CrossModuleEntities
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cast InboxActionExecutionContext (from shared) to the concrete helper context.
|
|
29
|
+
* The inbox-actions.ts handlers receive InboxActionExecutionContext but helpers
|
|
30
|
+
* need concrete EntityManager / AwilixContainer types.
|
|
31
|
+
*/
|
|
32
|
+
export function asHelperContext(ctx: InboxActionExecutionContext): ExecutionHelperContext {
|
|
33
|
+
return ctx as unknown as ExecutionHelperContext
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Error
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export class ExecutionError extends Error {
|
|
41
|
+
statusCode: number
|
|
42
|
+
|
|
43
|
+
constructor(message: string, statusCode = 400) {
|
|
44
|
+
super(message)
|
|
45
|
+
this.statusCode = statusCode
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Command execution
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
export async function executeCommand<TInput, TResult>(
|
|
54
|
+
ctx: ExecutionHelperContext,
|
|
55
|
+
commandId: string,
|
|
56
|
+
input: TInput,
|
|
57
|
+
): Promise<TResult> {
|
|
58
|
+
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
59
|
+
if (!commandBus || typeof commandBus.execute !== 'function') {
|
|
60
|
+
throw new ExecutionError('Command bus is not available', 503)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const auth =
|
|
64
|
+
ctx.auth ??
|
|
65
|
+
({
|
|
66
|
+
sub: ctx.userId,
|
|
67
|
+
userId: ctx.userId,
|
|
68
|
+
tenantId: ctx.tenantId,
|
|
69
|
+
orgId: ctx.organizationId,
|
|
70
|
+
isSuperAdmin: false,
|
|
71
|
+
} satisfies Exclude<AuthContext, null>)
|
|
72
|
+
|
|
73
|
+
const commandContext: CommandRuntimeContext = {
|
|
74
|
+
container: ctx.container,
|
|
75
|
+
auth,
|
|
76
|
+
organizationScope: null,
|
|
77
|
+
selectedOrganizationId: ctx.organizationId,
|
|
78
|
+
organizationIds: [ctx.organizationId],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { result } = await commandBus.execute<TInput, TResult>(commandId, {
|
|
82
|
+
input,
|
|
83
|
+
ctx: commandContext,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Entity resolution
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
export function resolveEntityClass<K extends keyof CrossModuleEntities>(
|
|
94
|
+
ctx: ExecutionHelperContext,
|
|
95
|
+
key: K,
|
|
96
|
+
): CrossModuleEntities[K] | null {
|
|
97
|
+
const fromEntities = ctx.entities?.[key]
|
|
98
|
+
if (fromEntities) return fromEntities
|
|
99
|
+
try { return ctx.container.resolve(key) } catch { return null }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Source metadata
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
export function buildSourceMetadata(actionId: string, proposalId: string): Record<string, unknown> {
|
|
107
|
+
return {
|
|
108
|
+
source: 'inbox_ops',
|
|
109
|
+
inboxOpsActionId: actionId,
|
|
110
|
+
inboxOpsProposalId: proposalId,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Order resolution
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
export async function resolveOrderByReference(
|
|
119
|
+
ctx: ExecutionHelperContext,
|
|
120
|
+
orderId?: string,
|
|
121
|
+
orderNumber?: string,
|
|
122
|
+
): Promise<{ id: string; orderNumber: string; currencyCode: string; comments?: string | null }> {
|
|
123
|
+
const SalesOrderClass = resolveEntityClass(ctx, 'SalesOrder')
|
|
124
|
+
if (!SalesOrderClass) {
|
|
125
|
+
throw new ExecutionError('Sales module entities not available', 503)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const where: Record<string, unknown> = {
|
|
129
|
+
tenantId: ctx.tenantId,
|
|
130
|
+
organizationId: ctx.organizationId,
|
|
131
|
+
deletedAt: null,
|
|
132
|
+
}
|
|
133
|
+
if (orderId) {
|
|
134
|
+
where.id = orderId
|
|
135
|
+
} else if (orderNumber && orderNumber.trim().length > 0) {
|
|
136
|
+
where.orderNumber = orderNumber.trim()
|
|
137
|
+
} else {
|
|
138
|
+
throw new ExecutionError('Order reference is required', 400)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const order = await findOneWithDecryption(
|
|
142
|
+
ctx.em,
|
|
143
|
+
SalesOrderClass,
|
|
144
|
+
where,
|
|
145
|
+
undefined,
|
|
146
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
147
|
+
)
|
|
148
|
+
if (!order) {
|
|
149
|
+
throw new ExecutionError('Referenced order not found', 404)
|
|
150
|
+
}
|
|
151
|
+
return order
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Channel resolution
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
export async function resolveFirstChannelId(ctx: ExecutionHelperContext): Promise<string | null> {
|
|
159
|
+
const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')
|
|
160
|
+
if (!SalesChannelClass) return null
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const channel = await findOneWithDecryption(
|
|
164
|
+
ctx.em,
|
|
165
|
+
SalesChannelClass,
|
|
166
|
+
{
|
|
167
|
+
tenantId: ctx.tenantId,
|
|
168
|
+
organizationId: ctx.organizationId,
|
|
169
|
+
deletedAt: null,
|
|
170
|
+
},
|
|
171
|
+
{ orderBy: { name: 'ASC' } },
|
|
172
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
173
|
+
)
|
|
174
|
+
return channel?.id ?? null
|
|
175
|
+
} catch {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function resolveChannelCurrency(
|
|
181
|
+
ctx: ExecutionHelperContext,
|
|
182
|
+
channelId: string | null,
|
|
183
|
+
): Promise<string | null> {
|
|
184
|
+
const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')
|
|
185
|
+
if (!SalesChannelClass) return null
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const where: Record<string, unknown> = {
|
|
189
|
+
tenantId: ctx.tenantId,
|
|
190
|
+
organizationId: ctx.organizationId,
|
|
191
|
+
deletedAt: null,
|
|
192
|
+
}
|
|
193
|
+
if (channelId) where.id = channelId
|
|
194
|
+
const channel = await findOneWithDecryption(
|
|
195
|
+
ctx.em,
|
|
196
|
+
SalesChannelClass,
|
|
197
|
+
where,
|
|
198
|
+
channelId ? undefined : { orderBy: { name: 'ASC' } },
|
|
199
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
200
|
+
)
|
|
201
|
+
return channel?.currencyCode ?? null
|
|
202
|
+
} catch {
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function resolveEffectiveDocumentKind(
|
|
208
|
+
ctx: ExecutionHelperContext,
|
|
209
|
+
channelId: string,
|
|
210
|
+
): Promise<'order' | 'quote'> {
|
|
211
|
+
const SalesChannelClass = resolveEntityClass(ctx, 'SalesChannel')
|
|
212
|
+
if (!SalesChannelClass) return 'order'
|
|
213
|
+
|
|
214
|
+
const channel = await findOneWithDecryption(
|
|
215
|
+
ctx.em,
|
|
216
|
+
SalesChannelClass,
|
|
217
|
+
{
|
|
218
|
+
id: channelId,
|
|
219
|
+
tenantId: ctx.tenantId,
|
|
220
|
+
organizationId: ctx.organizationId,
|
|
221
|
+
deletedAt: null,
|
|
222
|
+
},
|
|
223
|
+
undefined,
|
|
224
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
225
|
+
)
|
|
226
|
+
if (!channel) return 'order'
|
|
227
|
+
|
|
228
|
+
const metadata = channel.metadata as Record<string, unknown> | null
|
|
229
|
+
if (metadata?.quotesRequired === true) {
|
|
230
|
+
return 'quote'
|
|
231
|
+
}
|
|
232
|
+
return 'order'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Shipment status resolution
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
const SALES_SHIPMENT_STATUS_DICTIONARY_KEY = 'sales.shipment_status'
|
|
240
|
+
|
|
241
|
+
export async function resolveShipmentStatusEntryId(
|
|
242
|
+
ctx: ExecutionHelperContext,
|
|
243
|
+
statusLabel: string,
|
|
244
|
+
): Promise<string | null> {
|
|
245
|
+
const DictionaryClass = resolveEntityClass(ctx, 'Dictionary')
|
|
246
|
+
const DictionaryEntryClass = resolveEntityClass(ctx, 'DictionaryEntry')
|
|
247
|
+
if (!DictionaryClass || !DictionaryEntryClass) return null
|
|
248
|
+
|
|
249
|
+
const encryptionScope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
250
|
+
|
|
251
|
+
const dictionary = await findOneWithDecryption(
|
|
252
|
+
ctx.em,
|
|
253
|
+
DictionaryClass,
|
|
254
|
+
{
|
|
255
|
+
key: SALES_SHIPMENT_STATUS_DICTIONARY_KEY,
|
|
256
|
+
tenantId: ctx.tenantId,
|
|
257
|
+
organizationId: ctx.organizationId,
|
|
258
|
+
deletedAt: null,
|
|
259
|
+
},
|
|
260
|
+
undefined,
|
|
261
|
+
encryptionScope,
|
|
262
|
+
)
|
|
263
|
+
if (!dictionary) return null
|
|
264
|
+
|
|
265
|
+
const entries = await findWithDecryption(
|
|
266
|
+
ctx.em,
|
|
267
|
+
DictionaryEntryClass,
|
|
268
|
+
{
|
|
269
|
+
dictionary: dictionary.id,
|
|
270
|
+
tenantId: ctx.tenantId,
|
|
271
|
+
organizationId: ctx.organizationId,
|
|
272
|
+
},
|
|
273
|
+
undefined,
|
|
274
|
+
encryptionScope,
|
|
275
|
+
)
|
|
276
|
+
if (!entries.length) return null
|
|
277
|
+
|
|
278
|
+
const normalizedTarget = normalizeDictionaryToken(statusLabel)
|
|
279
|
+
const loweredTarget = statusLabel.trim().toLowerCase()
|
|
280
|
+
|
|
281
|
+
const match = entries.find((entry) => {
|
|
282
|
+
const label = entry.label.trim().toLowerCase()
|
|
283
|
+
const value = entry.value.trim().toLowerCase()
|
|
284
|
+
return (
|
|
285
|
+
entry.normalizedValue === normalizedTarget ||
|
|
286
|
+
label === loweredTarget ||
|
|
287
|
+
value === loweredTarget
|
|
288
|
+
)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
return match?.id ?? null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Customer / contact resolution
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
export async function resolveCustomerEntityIdByEmail(
|
|
299
|
+
ctx: ExecutionHelperContext,
|
|
300
|
+
email: string,
|
|
301
|
+
): Promise<string | null> {
|
|
302
|
+
const normalized = email.trim().toLowerCase()
|
|
303
|
+
if (!normalized) return null
|
|
304
|
+
|
|
305
|
+
const CustomerEntityClass = resolveEntityClass(ctx, 'CustomerEntity')
|
|
306
|
+
if (!CustomerEntityClass) return null
|
|
307
|
+
|
|
308
|
+
const entity = await findOneWithDecryption(
|
|
309
|
+
ctx.em,
|
|
310
|
+
CustomerEntityClass,
|
|
311
|
+
{
|
|
312
|
+
primaryEmail: normalized,
|
|
313
|
+
tenantId: ctx.tenantId,
|
|
314
|
+
organizationId: ctx.organizationId,
|
|
315
|
+
deletedAt: null,
|
|
316
|
+
},
|
|
317
|
+
undefined,
|
|
318
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
319
|
+
)
|
|
320
|
+
if (entity) return entity.id
|
|
321
|
+
|
|
322
|
+
const candidates = await findWithDecryption(
|
|
323
|
+
ctx.em,
|
|
324
|
+
CustomerEntityClass,
|
|
325
|
+
{
|
|
326
|
+
tenantId: ctx.tenantId,
|
|
327
|
+
organizationId: ctx.organizationId,
|
|
328
|
+
deletedAt: null,
|
|
329
|
+
},
|
|
330
|
+
{ limit: 100, orderBy: { createdAt: 'DESC' } },
|
|
331
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
332
|
+
)
|
|
333
|
+
const match = candidates.find(
|
|
334
|
+
(e) => e.primaryEmail && e.primaryEmail.toLowerCase() === normalized,
|
|
335
|
+
)
|
|
336
|
+
return match?.id ?? null
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function resolveContactIdByNameAndType(
|
|
340
|
+
ctx: ExecutionHelperContext,
|
|
341
|
+
contactName: string,
|
|
342
|
+
contactType: string,
|
|
343
|
+
): Promise<string | null> {
|
|
344
|
+
const CustomerEntityClass = resolveEntityClass(ctx, 'CustomerEntity')
|
|
345
|
+
if (!CustomerEntityClass) return null
|
|
346
|
+
|
|
347
|
+
const normalized = contactName.trim()
|
|
348
|
+
if (!normalized) return null
|
|
349
|
+
|
|
350
|
+
const entity = await findOneWithDecryption(
|
|
351
|
+
ctx.em,
|
|
352
|
+
CustomerEntityClass,
|
|
353
|
+
{
|
|
354
|
+
displayName: normalized,
|
|
355
|
+
kind: contactType,
|
|
356
|
+
tenantId: ctx.tenantId,
|
|
357
|
+
organizationId: ctx.organizationId,
|
|
358
|
+
deletedAt: null,
|
|
359
|
+
},
|
|
360
|
+
undefined,
|
|
361
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return entity?.id ?? null
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Order line items
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
export interface OrderLineItem {
|
|
372
|
+
id: string
|
|
373
|
+
name?: string | null
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function loadOrderLineItems(
|
|
377
|
+
ctx: ExecutionHelperContext,
|
|
378
|
+
orderId: string,
|
|
379
|
+
): Promise<OrderLineItem[]> {
|
|
380
|
+
try {
|
|
381
|
+
const result = await executeCommand<Record<string, unknown>, { lines?: OrderLineItem[] }>(
|
|
382
|
+
ctx,
|
|
383
|
+
'sales.orders.lines.list',
|
|
384
|
+
{ orderId, organizationId: ctx.organizationId, tenantId: ctx.tenantId },
|
|
385
|
+
)
|
|
386
|
+
return result.lines ?? []
|
|
387
|
+
} catch {
|
|
388
|
+
return []
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function matchLineItemByName(
|
|
393
|
+
orderLines: OrderLineItem[],
|
|
394
|
+
lineItemName: string,
|
|
395
|
+
): string | null {
|
|
396
|
+
const target = lineItemName.trim().toLowerCase()
|
|
397
|
+
if (!target) return null
|
|
398
|
+
|
|
399
|
+
const exact = orderLines.find((l) => (l.name || '').trim().toLowerCase() === target)
|
|
400
|
+
if (exact) return exact.id
|
|
401
|
+
|
|
402
|
+
const partial = orderLines.find((l) => {
|
|
403
|
+
const name = (l.name || '').trim().toLowerCase()
|
|
404
|
+
return name.includes(target) || target.includes(name)
|
|
405
|
+
})
|
|
406
|
+
return partial?.id ?? null
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
// Data normalization utilities
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
export function normalizeAddressSnapshot(
|
|
414
|
+
address: Record<string, unknown>,
|
|
415
|
+
): Record<string, unknown> {
|
|
416
|
+
return {
|
|
417
|
+
addressLine1: address.line1 ?? address.addressLine1 ?? '',
|
|
418
|
+
addressLine2: address.line2 ?? address.addressLine2 ?? null,
|
|
419
|
+
companyName: address.company ?? address.companyName ?? null,
|
|
420
|
+
name: address.contactName ?? address.name ?? null,
|
|
421
|
+
city: address.city ?? null,
|
|
422
|
+
region: address.state ?? address.region ?? null,
|
|
423
|
+
postalCode: address.postalCode ?? null,
|
|
424
|
+
country: address.country ?? null,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function parseDateToken(value?: string | null): Date | undefined {
|
|
429
|
+
if (!value) return undefined
|
|
430
|
+
const parsed = new Date(value)
|
|
431
|
+
if (Number.isNaN(parsed.getTime())) return undefined
|
|
432
|
+
return parsed
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function parseNumberToken(value: string, fieldName: string): number {
|
|
436
|
+
const parsed = Number(value)
|
|
437
|
+
if (!Number.isFinite(parsed)) {
|
|
438
|
+
throw new ExecutionError(`Invalid numeric value for ${fieldName}`, 400)
|
|
439
|
+
}
|
|
440
|
+
return parsed
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function normalizeDictionaryToken(value: string): string {
|
|
444
|
+
return value.trim().toLowerCase().replace(/[\s-]+/g, '_')
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
// Product discrepancy resolution (used by catalog inbox action handler)
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
export async function resolveProductDiscrepanciesInProposal(
|
|
452
|
+
em: EntityManager,
|
|
453
|
+
proposalId: string,
|
|
454
|
+
productTitle: string,
|
|
455
|
+
productId: string,
|
|
456
|
+
scope: { tenantId: string; organizationId: string },
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
const { InboxDiscrepancy, InboxProposalAction } = await import('../data/entities')
|
|
459
|
+
|
|
460
|
+
const discrepancies = await findWithDecryption(
|
|
461
|
+
em,
|
|
462
|
+
InboxDiscrepancy,
|
|
463
|
+
{
|
|
464
|
+
proposalId,
|
|
465
|
+
type: 'product_not_found',
|
|
466
|
+
resolved: false,
|
|
467
|
+
tenantId: scope.tenantId,
|
|
468
|
+
organizationId: scope.organizationId,
|
|
469
|
+
},
|
|
470
|
+
undefined,
|
|
471
|
+
scope,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
const normalizedTitle = productTitle.toLowerCase().trim()
|
|
475
|
+
const matchingDiscrepancies = discrepancies.filter((d) => {
|
|
476
|
+
const foundValue = (d.foundValue || '').toLowerCase().trim()
|
|
477
|
+
return foundValue === normalizedTitle
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
if (matchingDiscrepancies.length === 0) return
|
|
481
|
+
|
|
482
|
+
// Phase 1: flush scalar mutations before any queries to avoid UoW tracking loss (SPEC-018)
|
|
483
|
+
for (const discrepancy of matchingDiscrepancies) {
|
|
484
|
+
discrepancy.resolved = true
|
|
485
|
+
}
|
|
486
|
+
await em.flush()
|
|
487
|
+
|
|
488
|
+
// Phase 2: update line item product IDs (involves findOneWithDecryption queries)
|
|
489
|
+
const actionIds = matchingDiscrepancies
|
|
490
|
+
.map((d) => d.actionId)
|
|
491
|
+
.filter((id): id is string => !!id)
|
|
492
|
+
|
|
493
|
+
for (const actionId of actionIds) {
|
|
494
|
+
const action = await findOneWithDecryption(
|
|
495
|
+
em,
|
|
496
|
+
InboxProposalAction,
|
|
497
|
+
{ id: actionId, deletedAt: null },
|
|
498
|
+
undefined,
|
|
499
|
+
scope,
|
|
500
|
+
)
|
|
501
|
+
if (!action) continue
|
|
502
|
+
|
|
503
|
+
const payload = action.payload as Record<string, unknown>
|
|
504
|
+
const lineItems = Array.isArray(payload?.lineItems)
|
|
505
|
+
? (payload.lineItems as Record<string, unknown>[])
|
|
506
|
+
: []
|
|
507
|
+
|
|
508
|
+
let updated = false
|
|
509
|
+
for (const item of lineItems) {
|
|
510
|
+
if (item.productId) continue
|
|
511
|
+
const itemName = (typeof item.productName === 'string' ? item.productName : '').toLowerCase().trim()
|
|
512
|
+
if (itemName === normalizedTitle) {
|
|
513
|
+
item.productId = productId
|
|
514
|
+
updated = true
|
|
515
|
+
break
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (updated) {
|
|
520
|
+
action.payload = { ...payload, lineItems }
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (actionIds.length > 0) {
|
|
525
|
+
await em.flush()
|
|
526
|
+
}
|
|
527
|
+
}
|
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
import type { ContactMatchResult } from './contactMatcher'
|
|
2
|
-
import {
|
|
2
|
+
import type { InboxActionDefinition } from '@open-mercato/shared/modules/inbox-actions'
|
|
3
3
|
|
|
4
4
|
const LANGUAGE_NAMES: Record<string, string> = { en: 'English', de: 'German', es: 'Spanish', pl: 'Polish' }
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Lazily load registered inbox action definitions from the generated registry.
|
|
8
|
+
* Uses dynamic import to avoid circular dependencies at module load time.
|
|
9
|
+
*/
|
|
10
|
+
async function loadRegisteredActions(): Promise<InboxActionDefinition[]> {
|
|
11
|
+
try {
|
|
12
|
+
const registry = await import('@/.mercato/generated/inbox-actions.generated')
|
|
13
|
+
return registry.inboxActions ?? []
|
|
14
|
+
} catch {
|
|
15
|
+
return []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildFeaturesSection(actions: InboxActionDefinition[]): string {
|
|
20
|
+
return actions
|
|
21
|
+
.map((a) => `- ${a.type} (requires: ${a.requiredFeature})`)
|
|
22
|
+
.join('\n')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildPayloadSchemasSection(actions: InboxActionDefinition[]): string {
|
|
26
|
+
return actions
|
|
27
|
+
.filter((a) => a.promptSchema && a.promptSchema !== '(shared with create_order)' && a.promptSchema !== '(shared with create_order above)')
|
|
28
|
+
.map((a) => a.promptSchema)
|
|
29
|
+
.join('\n\n')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildActionRulesSection(actions: InboxActionDefinition[]): string {
|
|
33
|
+
const rules = actions.flatMap((a) => a.promptRules ?? [])
|
|
34
|
+
return rules.map((r) => `- ${r}`).join('\n')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function buildExtractionSystemPrompt(
|
|
7
38
|
matchedContacts: ContactMatchResult[],
|
|
8
39
|
catalogProducts: { id: string; name: string; sku?: string; price?: string }[],
|
|
9
40
|
channelId?: string,
|
|
10
41
|
workingLanguage?: string,
|
|
11
|
-
|
|
42
|
+
registeredActions?: InboxActionDefinition[],
|
|
43
|
+
): Promise<string> {
|
|
44
|
+
const actions = registeredActions ?? await loadRegisteredActions()
|
|
45
|
+
|
|
46
|
+
const featuresSection = buildFeaturesSection(actions)
|
|
47
|
+
const payloadSchemasSection = buildPayloadSchemasSection(actions)
|
|
48
|
+
const actionRulesSection = buildActionRulesSection(actions)
|
|
49
|
+
|
|
12
50
|
const contactsSection = matchedContacts.length > 0
|
|
13
51
|
? `\nPre-matched contacts from CRM:\n${JSON.stringify(
|
|
14
52
|
matchedContacts.map((match) => ({
|
|
@@ -36,7 +74,7 @@ You are an email-to-ERP extraction agent.
|
|
|
36
74
|
</role>
|
|
37
75
|
|
|
38
76
|
<required_features>
|
|
39
|
-
${
|
|
77
|
+
${featuresSection}
|
|
40
78
|
</required_features>
|
|
41
79
|
|
|
42
80
|
<safety>
|
|
@@ -46,41 +84,13 @@ ${Object.entries(REQUIRED_FEATURES_MAP).map(([actionType, feature]) => `- ${acti
|
|
|
46
84
|
</safety>
|
|
47
85
|
|
|
48
86
|
<payload_schemas>
|
|
49
|
-
|
|
50
|
-
{ customerName: string, customerEmail?: string, customerEntityId?: uuid, channelId?: uuid, currencyCode: string (3-letter ISO), taxRateId?: uuid, lineItems: [{ productName: string (REQUIRED), productId?: uuid, variantId?: uuid, sku?: string, quantity: string, unitPrice?: string, kind?: "product"|"service", description?: string }], requestedDeliveryDate?: string, notes?: string, customerReference?: string (customer's own PO number or reference code — only set if explicitly stated in the email, do NOT use the email subject), shippingAddress?: { line1?: string, line2?: string, city?: string, state?: string, postalCode?: string, country?: string, company?: string, contactName?: string }, billingAddress?: { line1?: string, line2?: string, city?: string, state?: string, postalCode?: string, country?: string, company?: string, contactName?: string } }
|
|
51
|
-
|
|
52
|
-
create_contact payload:
|
|
53
|
-
{ type: "person"|"company", name: string, email?: string, phone?: string, companyName?: string, role?: string, source: "inbox_ops" }
|
|
54
|
-
|
|
55
|
-
create_product payload:
|
|
56
|
-
{ title: string, sku?: string, unitPrice?: string, currencyCode?: string (3-letter ISO), kind?: "product"|"service", description?: string }
|
|
57
|
-
|
|
58
|
-
link_contact payload:
|
|
59
|
-
{ emailAddress: string (email), contactId: uuid, contactType: "person"|"company", contactName: string }
|
|
60
|
-
|
|
61
|
-
update_order payload:
|
|
62
|
-
{ orderId?: uuid, orderNumber?: string, quantityChanges?: [{ lineItemName: string, lineItemId?: uuid, oldQuantity?: string, newQuantity: string }], deliveryDateChange?: { oldDate?: string, newDate: string }, noteAdditions?: string[] }
|
|
63
|
-
|
|
64
|
-
update_shipment payload:
|
|
65
|
-
{ orderId?: uuid, orderNumber?: string, trackingNumbers?: string[], carrierName?: string, statusLabel: string, shippedAt?: string, deliveredAt?: string, estimatedDelivery?: string, notes?: string }
|
|
66
|
-
|
|
67
|
-
log_activity payload:
|
|
68
|
-
{ contactId?: uuid, contactType: "person"|"company", contactName: string, activityType: "email"|"call"|"meeting"|"note", subject: string, body: string }
|
|
69
|
-
|
|
70
|
-
draft_reply payload:
|
|
71
|
-
{ to: string (email), toName?: string, subject: string, body: string, context?: string }
|
|
87
|
+
${payloadSchemasSection}
|
|
72
88
|
</payload_schemas>
|
|
73
89
|
|
|
74
90
|
<rules>
|
|
75
91
|
- Extract only details explicitly stated or strongly implied in the thread.
|
|
76
92
|
- Do not fabricate values; omit values that are not present.
|
|
77
|
-
|
|
78
|
-
- Use create_order when the customer has clearly confirmed they want to proceed (e.g., "let's go ahead", "please process", "confirmed"). Use create_quote when the customer is still inquiring, requesting pricing, asking for a proposal, or negotiating (e.g., "could you send a quote", "what would it cost", "we're interested in", "can you offer"). When in doubt, prefer create_quote.
|
|
79
|
-
- For create_order / create_quote: each line item MUST have "productName" (the product name goes here, NOT in "description"). Include currencyCode and customerName.
|
|
80
|
-
- For update_shipment: use statusLabel text only.
|
|
81
|
-
- For create_order / create_quote: extract shippingAddress and billingAddress as structured objects when addresses are mentioned. Parse street, city, postal code, country from the text. Do NOT put address data in notes.
|
|
82
|
-
- For create_contact: always include email when available from the thread. Set source to "inbox_ops", type must be lowercase "person" or "company".
|
|
83
|
-
- For draft_reply: include ERP context when available.
|
|
93
|
+
${actionRulesSection}
|
|
84
94
|
- Set requiredFeature on each action from the mapping above.
|
|
85
95
|
- Set confidence in [0.0, 1.0].
|
|
86
96
|
- Write summary and all action descriptions in ${LANGUAGE_NAMES[workingLanguage || 'en'] || 'English'} even if the original thread is in another language.
|
|
@@ -110,4 +120,5 @@ ${cleanedText}
|
|
|
110
120
|
</output_requirements>`
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
/** @deprecated Use the generated inbox action registry instead */
|
|
113
124
|
export { REQUIRED_FEATURES_MAP } from './constants'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare module '@/.mercato/generated/inbox-actions.generated' {
|
|
2
|
+
import type { InboxActionDefinition } from '@open-mercato/shared/modules/inbox-actions'
|
|
3
|
+
|
|
4
|
+
export const inboxActionConfigEntries: Array<{
|
|
5
|
+
moduleId: string
|
|
6
|
+
actions: InboxActionDefinition[]
|
|
7
|
+
}>
|
|
8
|
+
export const inboxActions: InboxActionDefinition[]
|
|
9
|
+
export function getInboxAction(type: string): InboxActionDefinition | undefined
|
|
10
|
+
export function getRegisteredActionTypes(): string[]
|
|
11
|
+
}
|