@open-mercato/core 0.4.8-develop-641703d2a6 → 0.4.8-develop-280c02b529
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/generated/entities/inbox_proposal/index.js +2 -0
- package/dist/generated/entities/inbox_proposal/index.js.map +2 -2
- package/dist/modules/catalog/inbox-actions.js +49 -0
- package/dist/modules/catalog/inbox-actions.js.map +2 -2
- package/dist/modules/customers/inbox-actions.js +69 -27
- package/dist/modules/customers/inbox-actions.js.map +3 -3
- package/dist/modules/inbox_ops/ai-tools.js +346 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +7 -0
- package/dist/modules/inbox_ops/api/extract/route.js +3 -2
- package/dist/modules/inbox_ops/api/extract/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/accept/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/accept/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/complete/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/complete/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/reject/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/reject/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +59 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +7 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/reject/route.js +4 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/reject/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js +34 -14
- package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/counts/route.js +49 -4
- package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/route.js +13 -0
- package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +33 -2
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +28 -3
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +103 -5
- package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +2 -2
- package/dist/modules/inbox_ops/components/messages/InboxEmailContent.js +24 -0
- package/dist/modules/inbox_ops/components/messages/InboxEmailContent.js.map +7 -0
- package/dist/modules/inbox_ops/components/messages/InboxEmailPreview.js +29 -0
- package/dist/modules/inbox_ops/components/messages/InboxEmailPreview.js.map +7 -0
- package/dist/modules/inbox_ops/components/proposals/CategoryBadge.js +59 -0
- package/dist/modules/inbox_ops/components/proposals/CategoryBadge.js.map +7 -0
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +3 -1
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/inbox_ops/data/entities.js +4 -0
- package/dist/modules/inbox_ops/data/entities.js.map +2 -2
- package/dist/modules/inbox_ops/data/validators.js +30 -5
- package/dist/modules/inbox_ops/data/validators.js.map +2 -2
- package/dist/modules/inbox_ops/lib/cache.js +53 -0
- package/dist/modules/inbox_ops/lib/cache.js.map +7 -0
- package/dist/modules/inbox_ops/lib/contactValidation.js +38 -3
- package/dist/modules/inbox_ops/lib/contactValidation.js.map +2 -2
- package/dist/modules/inbox_ops/lib/executionHelpers.js +28 -1
- package/dist/modules/inbox_ops/lib/executionHelpers.js.map +2 -2
- package/dist/modules/inbox_ops/lib/extractionPrompt.js +2 -1
- package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +2 -2
- package/dist/modules/inbox_ops/lib/messageObjectPreviews.js +52 -0
- package/dist/modules/inbox_ops/lib/messageObjectPreviews.js.map +7 -0
- package/dist/modules/inbox_ops/lib/messagesIntegration.js +155 -0
- package/dist/modules/inbox_ops/lib/messagesIntegration.js.map +7 -0
- package/dist/modules/inbox_ops/message-objects.js +36 -0
- package/dist/modules/inbox_ops/message-objects.js.map +7 -0
- package/dist/modules/inbox_ops/message-types.js +38 -0
- package/dist/modules/inbox_ops/message-types.js.map +7 -0
- package/dist/modules/inbox_ops/migrations/Migration20260303173020.js +13 -0
- package/dist/modules/inbox_ops/migrations/Migration20260303173020.js.map +7 -0
- package/dist/modules/inbox_ops/migrations/Migration20260303173215.js +15 -0
- package/dist/modules/inbox_ops/migrations/Migration20260303173215.js.map +7 -0
- package/dist/modules/inbox_ops/search.js +5 -3
- package/dist/modules/inbox_ops/search.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js +65 -3
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
- package/generated/entities/inbox_proposal/index.ts +1 -0
- package/package.json +3 -3
- package/src/modules/catalog/inbox-actions.ts +55 -0
- package/src/modules/customers/inbox-actions.ts +86 -27
- package/src/modules/inbox_ops/ai-tools.ts +451 -0
- package/src/modules/inbox_ops/api/extract/route.ts +3 -2
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/accept/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/complete/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/reject/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +61 -0
- package/src/modules/inbox_ops/api/proposals/[id]/reject/route.ts +5 -0
- package/src/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.ts +36 -16
- package/src/modules/inbox_ops/api/proposals/counts/route.ts +60 -5
- package/src/modules/inbox_ops/api/proposals/route.ts +14 -1
- package/src/modules/inbox_ops/api/settings/route.ts +36 -2
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +31 -3
- package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +103 -1
- package/src/modules/inbox_ops/components/messages/InboxEmailContent.tsx +45 -0
- package/src/modules/inbox_ops/components/messages/InboxEmailPreview.tsx +40 -0
- package/src/modules/inbox_ops/components/proposals/CategoryBadge.tsx +59 -0
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +3 -1
- package/src/modules/inbox_ops/components/proposals/types.ts +1 -0
- package/src/modules/inbox_ops/data/entities.ts +14 -1
- package/src/modules/inbox_ops/data/validators.ts +41 -5
- package/src/modules/inbox_ops/lib/cache.ts +60 -0
- package/src/modules/inbox_ops/lib/contactValidation.ts +31 -2
- package/src/modules/inbox_ops/lib/executionHelpers.ts +40 -0
- package/src/modules/inbox_ops/lib/extractionPrompt.ts +2 -1
- package/src/modules/inbox_ops/lib/messageObjectPreviews.ts +61 -0
- package/src/modules/inbox_ops/lib/messagesIntegration.ts +231 -0
- package/src/modules/inbox_ops/message-objects.ts +34 -0
- package/src/modules/inbox_ops/message-types.ts +36 -0
- package/src/modules/inbox_ops/migrations/Migration20260303173020.ts +13 -0
- package/src/modules/inbox_ops/migrations/Migration20260303173215.ts +15 -0
- package/src/modules/inbox_ops/search.ts +5 -3
- package/src/modules/inbox_ops/subscribers/extractionWorker.ts +75 -1
|
@@ -4,6 +4,7 @@ const summary = "summary";
|
|
|
4
4
|
const participants = "participants";
|
|
5
5
|
const confidence = "confidence";
|
|
6
6
|
const detected_language = "detected_language";
|
|
7
|
+
const category = "category";
|
|
7
8
|
const status = "status";
|
|
8
9
|
const possibly_incomplete = "possibly_incomplete";
|
|
9
10
|
const reviewed_by_user_id = "reviewed_by_user_id";
|
|
@@ -20,6 +21,7 @@ const created_at = "created_at";
|
|
|
20
21
|
const updated_at = "updated_at";
|
|
21
22
|
const deleted_at = "deleted_at";
|
|
22
23
|
export {
|
|
24
|
+
category,
|
|
23
25
|
confidence,
|
|
24
26
|
created_at,
|
|
25
27
|
deleted_at,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../generated/entities/inbox_proposal/index.ts"],
|
|
4
|
-
"sourcesContent": ["export const id = 'id'\nexport const inbox_email_id = 'inbox_email_id'\nexport const summary = 'summary'\nexport const participants = 'participants'\nexport const confidence = 'confidence'\nexport const detected_language = 'detected_language'\nexport const status = 'status'\nexport const possibly_incomplete = 'possibly_incomplete'\nexport const reviewed_by_user_id = 'reviewed_by_user_id'\nexport const reviewed_at = 'reviewed_at'\nexport const llm_model = 'llm_model'\nexport const llm_tokens_used = 'llm_tokens_used'\nexport const working_language = 'working_language'\nexport const translations = 'translations'\nexport const is_active = 'is_active'\nexport const metadata = 'metadata'\nexport const organization_id = 'organization_id'\nexport const tenant_id = 'tenant_id'\nexport const created_at = 'created_at'\nexport const updated_at = 'updated_at'\nexport const deleted_at = 'deleted_at'\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,KAAK;AACX,MAAM,iBAAiB;AACvB,MAAM,UAAU;AAChB,MAAM,eAAe;AACrB,MAAM,aAAa;AACnB,MAAM,oBAAoB;AAC1B,MAAM,SAAS;AACf,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,cAAc;AACpB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AACzB,MAAM,eAAe;AACrB,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,aAAa;",
|
|
4
|
+
"sourcesContent": ["export const id = 'id'\nexport const inbox_email_id = 'inbox_email_id'\nexport const summary = 'summary'\nexport const participants = 'participants'\nexport const confidence = 'confidence'\nexport const detected_language = 'detected_language'\nexport const category = 'category'\nexport const status = 'status'\nexport const possibly_incomplete = 'possibly_incomplete'\nexport const reviewed_by_user_id = 'reviewed_by_user_id'\nexport const reviewed_at = 'reviewed_at'\nexport const llm_model = 'llm_model'\nexport const llm_tokens_used = 'llm_tokens_used'\nexport const working_language = 'working_language'\nexport const translations = 'translations'\nexport const is_active = 'is_active'\nexport const metadata = 'metadata'\nexport const organization_id = 'organization_id'\nexport const tenant_id = 'tenant_id'\nexport const created_at = 'created_at'\nexport const updated_at = 'updated_at'\nexport const deleted_at = 'deleted_at'\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,KAAK;AACX,MAAM,iBAAiB;AACvB,MAAM,UAAU;AAChB,MAAM,eAAe;AACrB,MAAM,aAAa;AACnB,MAAM,oBAAoB;AAC1B,MAAM,WAAW;AACjB,MAAM,SAAS;AACf,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,cAAc;AACpB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AACzB,MAAM,eAAe;AACrB,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
1
2
|
import { createProductPayloadSchema } from "../inbox_ops/data/validators.js";
|
|
2
3
|
import {
|
|
3
4
|
asHelperContext,
|
|
@@ -5,6 +6,7 @@ import {
|
|
|
5
6
|
executeCommand,
|
|
6
7
|
resolveProductDiscrepanciesInProposal
|
|
7
8
|
} from "../inbox_ops/lib/executionHelpers.js";
|
|
9
|
+
import { CatalogPriceKind } from "./data/entities.js";
|
|
8
10
|
async function executeCreateProductAction(action, ctx) {
|
|
9
11
|
const hCtx = asHelperContext(ctx);
|
|
10
12
|
const payload = action.payload;
|
|
@@ -26,6 +28,53 @@ async function executeCreateProductAction(action, ctx) {
|
|
|
26
28
|
if (!result.productId) {
|
|
27
29
|
throw new ExecutionError("Product creation did not return a product ID", 500);
|
|
28
30
|
}
|
|
31
|
+
let variantId = null;
|
|
32
|
+
try {
|
|
33
|
+
const variantResult = await executeCommand(
|
|
34
|
+
hCtx,
|
|
35
|
+
"catalog.variants.create",
|
|
36
|
+
{
|
|
37
|
+
productId: result.productId,
|
|
38
|
+
organizationId: hCtx.organizationId,
|
|
39
|
+
tenantId: hCtx.tenantId,
|
|
40
|
+
name: "Default",
|
|
41
|
+
isDefault: true,
|
|
42
|
+
isActive: true,
|
|
43
|
+
sku: payload.sku || void 0
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
variantId = variantResult.variantId ?? null;
|
|
47
|
+
} catch (variantErr) {
|
|
48
|
+
console.warn("[catalog:inbox-action] Failed to create default variant (non-fatal):", variantErr instanceof Error ? variantErr.message : variantErr);
|
|
49
|
+
}
|
|
50
|
+
if (variantId && payload.unitPrice && payload.currencyCode) {
|
|
51
|
+
try {
|
|
52
|
+
const priceKind = await findOneWithDecryption(
|
|
53
|
+
hCtx.em,
|
|
54
|
+
CatalogPriceKind,
|
|
55
|
+
{
|
|
56
|
+
code: "regular",
|
|
57
|
+
tenantId: hCtx.tenantId,
|
|
58
|
+
deletedAt: null
|
|
59
|
+
},
|
|
60
|
+
void 0,
|
|
61
|
+
{ tenantId: hCtx.tenantId, organizationId: hCtx.organizationId }
|
|
62
|
+
);
|
|
63
|
+
if (priceKind) {
|
|
64
|
+
await executeCommand(hCtx, "catalog.prices.create", {
|
|
65
|
+
variantId,
|
|
66
|
+
productId: result.productId,
|
|
67
|
+
organizationId: hCtx.organizationId,
|
|
68
|
+
tenantId: hCtx.tenantId,
|
|
69
|
+
priceKindId: priceKind.id,
|
|
70
|
+
currencyCode: payload.currencyCode,
|
|
71
|
+
unitPriceNet: Number(payload.unitPrice)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} catch (priceErr) {
|
|
75
|
+
console.warn("[catalog:inbox-action] Failed to set price on default variant (non-fatal):", priceErr);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
29
78
|
await resolveProductDiscrepanciesInProposal(hCtx.em, action.proposalId, payload.title, result.productId, {
|
|
30
79
|
tenantId: hCtx.tenantId,
|
|
31
80
|
organizationId: hCtx.organizationId
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/catalog/inbox-actions.ts"],
|
|
4
|
-
"sourcesContent": ["import type { InboxActionDefinition, InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'\nimport { createProductPayloadSchema } from '../inbox_ops/data/validators'\nimport type { CreateProductPayload } from '../inbox_ops/data/validators'\nimport {\n asHelperContext,\n ExecutionError,\n executeCommand,\n resolveProductDiscrepanciesInProposal,\n} from '../inbox_ops/lib/executionHelpers'\n\nasync function executeCreateProductAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as CreateProductPayload\n\n const createInput: Record<string, unknown> = {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.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 hCtx,\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(hCtx.em, action.proposalId, payload.title, result.productId, {\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n })\n\n return { createdEntityId: result.productId, createdEntityType: 'catalog_product' }\n}\n\nexport const inboxActions: InboxActionDefinition[] = [\n {\n type: 'create_product',\n requiredFeature: 'catalog.products.manage',\n payloadSchema: createProductPayloadSchema,\n label: 'Create Product',\n promptSchema: `create_product payload:\n{ title: string, sku?: string, unitPrice?: string, currencyCode?: string (3-letter ISO), kind?: \"product\"|\"service\", description?: string }`,\n execute: executeCreateProductAction,\n },\n]\n\nexport default inboxActions\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,kCAAkC;AAE3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { InboxActionDefinition, InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createProductPayloadSchema } from '../inbox_ops/data/validators'\nimport type { CreateProductPayload } from '../inbox_ops/data/validators'\nimport {\n asHelperContext,\n ExecutionError,\n executeCommand,\n resolveProductDiscrepanciesInProposal,\n} from '../inbox_ops/lib/executionHelpers'\nimport { CatalogPriceKind } from './data/entities'\n\nasync function executeCreateProductAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as CreateProductPayload\n\n const createInput: Record<string, unknown> = {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.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 hCtx,\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 // Create default variant so the product works with quotes/orders (issue #891)\n // No separate permission check \u2014 the user already passed catalog.products.manage\n // in the execution engine, and variant/price creation is an integral part of\n // product setup, not a separate user action.\n let variantId: string | null = null\n try {\n const variantResult = await executeCommand<Record<string, unknown>, { variantId?: string }>(\n hCtx,\n 'catalog.variants.create',\n {\n productId: result.productId,\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n name: 'Default',\n isDefault: true,\n isActive: true,\n sku: payload.sku || undefined,\n },\n )\n variantId = variantResult.variantId ?? null\n } catch (variantErr) {\n console.warn('[catalog:inbox-action] Failed to create default variant (non-fatal):', variantErr instanceof Error ? variantErr.message : variantErr)\n }\n\n if (variantId && payload.unitPrice && payload.currencyCode) {\n try {\n const priceKind = await findOneWithDecryption(\n hCtx.em,\n CatalogPriceKind,\n {\n code: 'regular',\n tenantId: hCtx.tenantId,\n deletedAt: null,\n },\n undefined,\n { tenantId: hCtx.tenantId, organizationId: hCtx.organizationId },\n )\n if (priceKind) {\n await executeCommand(hCtx, 'catalog.prices.create', {\n variantId,\n productId: result.productId,\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n priceKindId: priceKind.id,\n currencyCode: payload.currencyCode,\n unitPriceNet: Number(payload.unitPrice),\n })\n }\n } catch (priceErr) {\n console.warn('[catalog:inbox-action] Failed to set price on default variant (non-fatal):', priceErr)\n }\n }\n\n await resolveProductDiscrepanciesInProposal(hCtx.em, action.proposalId, payload.title, result.productId, {\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n })\n\n return { createdEntityId: result.productId, createdEntityType: 'catalog_product' }\n}\n\nexport const inboxActions: InboxActionDefinition[] = [\n {\n type: 'create_product',\n requiredFeature: 'catalog.products.manage',\n payloadSchema: createProductPayloadSchema,\n label: 'Create Product',\n promptSchema: `create_product payload:\n{ title: string, sku?: string, unitPrice?: string, currencyCode?: string (3-letter ISO), kind?: \"product\"|\"service\", description?: string }`,\n execute: executeCreateProductAction,\n },\n]\n\nexport default inboxActions\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,kCAAkC;AAE3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;AAEjC,eAAe,2BACb,QACA,KACiF;AACjF,QAAM,OAAO,gBAAgB,GAAG;AAChC,QAAM,UAAU,OAAO;AAEvB,QAAM,cAAuC;AAAA,IAC3C,gBAAgB,KAAK;AAAA,IACrB,UAAU,KAAK;AAAA,IACf,OAAO,QAAQ;AAAA,IACf,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAEA,MAAI,QAAQ,IAAK,aAAY,MAAM,QAAQ;AAC3C,MAAI,QAAQ,YAAa,aAAY,cAAc,QAAQ;AAC3D,MAAI,QAAQ,aAAc,aAAY,sBAAsB,QAAQ;AAEpE,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,WAAW;AACrB,UAAM,IAAI,eAAe,gDAAgD,GAAG;AAAA,EAC9E;AAMA,MAAI,YAA2B;AAC/B,MAAI;AACF,UAAM,gBAAgB,MAAM;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAW,OAAO;AAAA,QAClB,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK;AAAA,QACf,MAAM;AAAA,QACN,WAAW;AAAA,QACX,UAAU;AAAA,QACV,KAAK,QAAQ,OAAO;AAAA,MACtB;AAAA,IACF;AACA,gBAAY,cAAc,aAAa;AAAA,EACzC,SAAS,YAAY;AACnB,YAAQ,KAAK,wEAAwE,sBAAsB,QAAQ,WAAW,UAAU,UAAU;AAAA,EACpJ;AAEA,MAAI,aAAa,QAAQ,aAAa,QAAQ,cAAc;AAC1D,QAAI;AACF,YAAM,YAAY,MAAM;AAAA,QACtB,KAAK;AAAA,QACL;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,UAAU,KAAK;AAAA,UACf,WAAW;AAAA,QACb;AAAA,QACA;AAAA,QACA,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,eAAe;AAAA,MACjE;AACA,UAAI,WAAW;AACb,cAAM,eAAe,MAAM,yBAAyB;AAAA,UAClD;AAAA,UACA,WAAW,OAAO;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,UAAU,KAAK;AAAA,UACf,aAAa,UAAU;AAAA,UACvB,cAAc,QAAQ;AAAA,UACtB,cAAc,OAAO,QAAQ,SAAS;AAAA,QACxC,CAAC;AAAA,MACH;AAAA,IACF,SAAS,UAAU;AACjB,cAAQ,KAAK,8EAA8E,QAAQ;AAAA,IACrG;AAAA,EACF;AAEA,QAAM,sCAAsC,KAAK,IAAI,OAAO,YAAY,QAAQ,OAAO,OAAO,WAAW;AAAA,IACvG,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AAED,SAAO,EAAE,iBAAiB,OAAO,WAAW,mBAAmB,kBAAkB;AACnF;AAEO,MAAM,eAAwC;AAAA,EACnD;AAAA,IACE,MAAM;AAAA,IACN,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,OAAO;AAAA,IACP,cAAc;AAAA;AAAA,IAEd,SAAS;AAAA,EACX;AACF;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,19 +12,61 @@ import {
|
|
|
12
12
|
resolveCustomerEntityIdByEmail,
|
|
13
13
|
resolveContactIdByNameAndType
|
|
14
14
|
} from "../inbox_ops/lib/executionHelpers.js";
|
|
15
|
-
import { splitPersonName } from "../inbox_ops/lib/contactValidation.js";
|
|
15
|
+
import { splitPersonName, stripTitleFromName } from "../inbox_ops/lib/contactValidation.js";
|
|
16
16
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
17
|
+
async function resolveOrCreateCompany(hCtx, companyName) {
|
|
18
|
+
const CustomerEntityClass = resolveEntityClass(hCtx, "CustomerEntity");
|
|
19
|
+
if (CustomerEntityClass) {
|
|
20
|
+
const allCompanies = await findWithDecryption(
|
|
21
|
+
hCtx.em,
|
|
22
|
+
CustomerEntityClass,
|
|
23
|
+
{
|
|
24
|
+
kind: "company",
|
|
25
|
+
tenantId: hCtx.tenantId,
|
|
26
|
+
organizationId: hCtx.organizationId,
|
|
27
|
+
deletedAt: null
|
|
28
|
+
},
|
|
29
|
+
{ limit: 500 },
|
|
30
|
+
{ tenantId: hCtx.tenantId, organizationId: hCtx.organizationId }
|
|
31
|
+
);
|
|
32
|
+
const trimmedLower = companyName.trim().toLowerCase();
|
|
33
|
+
const existing = allCompanies.find(
|
|
34
|
+
(c) => c.displayName && c.displayName.toLowerCase() === trimmedLower
|
|
35
|
+
);
|
|
36
|
+
if (existing) return existing.id;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const result = await executeCommand(
|
|
40
|
+
hCtx,
|
|
41
|
+
"customers.companies.create",
|
|
42
|
+
{
|
|
43
|
+
organizationId: hCtx.organizationId,
|
|
44
|
+
tenantId: hCtx.tenantId,
|
|
45
|
+
displayName: companyName.trim(),
|
|
46
|
+
legalName: companyName.trim(),
|
|
47
|
+
source: "ai_inbox",
|
|
48
|
+
status: "active"
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
return result.entityId ?? null;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.warn("[customers:inbox-action] Failed to create company (non-fatal):", err);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
17
57
|
async function executeCreateContactAction(action, ctx) {
|
|
18
58
|
const hCtx = asHelperContext(ctx);
|
|
19
59
|
const payload = action.payload;
|
|
20
60
|
const CustomerEntityClass = resolveEntityClass(hCtx, "CustomerEntity");
|
|
21
61
|
if (payload.email && CustomerEntityClass) {
|
|
22
62
|
const emailLower = payload.email.trim().toLowerCase();
|
|
63
|
+
const targetKind = payload.type === "company" ? "company" : "person";
|
|
23
64
|
let existingContact = await findOneWithDecryption(
|
|
24
65
|
hCtx.em,
|
|
25
66
|
CustomerEntityClass,
|
|
26
67
|
{
|
|
27
68
|
primaryEmail: emailLower,
|
|
69
|
+
kind: targetKind,
|
|
28
70
|
tenantId: hCtx.tenantId,
|
|
29
71
|
organizationId: hCtx.organizationId,
|
|
30
72
|
deletedAt: null
|
|
@@ -37,6 +79,7 @@ async function executeCreateContactAction(action, ctx) {
|
|
|
37
79
|
hCtx.em,
|
|
38
80
|
CustomerEntityClass,
|
|
39
81
|
{
|
|
82
|
+
kind: targetKind,
|
|
40
83
|
tenantId: hCtx.tenantId,
|
|
41
84
|
organizationId: hCtx.organizationId,
|
|
42
85
|
deletedAt: null
|
|
@@ -59,39 +102,38 @@ async function executeCreateContactAction(action, ctx) {
|
|
|
59
102
|
}
|
|
60
103
|
}
|
|
61
104
|
if (payload.type === "company") {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
"customers.companies.create",
|
|
65
|
-
{
|
|
66
|
-
organizationId: hCtx.organizationId,
|
|
67
|
-
tenantId: hCtx.tenantId,
|
|
68
|
-
displayName: payload.name,
|
|
69
|
-
legalName: payload.companyName ?? payload.name,
|
|
70
|
-
primaryEmail: payload.email,
|
|
71
|
-
primaryPhone: payload.phone,
|
|
72
|
-
source: payload.source
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
if (!result2.entityId) {
|
|
105
|
+
const companyId = await resolveOrCreateCompany(hCtx, payload.name);
|
|
106
|
+
if (!companyId) {
|
|
76
107
|
throw new ExecutionError("Company creation did not return an entity ID", 500);
|
|
77
108
|
}
|
|
78
|
-
return { createdEntityId:
|
|
109
|
+
return { createdEntityId: companyId, createdEntityType: "customer_company" };
|
|
79
110
|
}
|
|
111
|
+
const { cleanedName } = stripTitleFromName(payload.name);
|
|
80
112
|
const { firstName, lastName } = splitPersonName(payload.name, payload.email);
|
|
113
|
+
let companyEntityId = null;
|
|
114
|
+
if (payload.companyName) {
|
|
115
|
+
companyEntityId = await resolveOrCreateCompany(hCtx, payload.companyName);
|
|
116
|
+
}
|
|
117
|
+
const personInput = {
|
|
118
|
+
organizationId: hCtx.organizationId,
|
|
119
|
+
tenantId: hCtx.tenantId,
|
|
120
|
+
displayName: cleanedName,
|
|
121
|
+
firstName,
|
|
122
|
+
lastName,
|
|
123
|
+
primaryEmail: payload.email,
|
|
124
|
+
primaryPhone: payload.phone,
|
|
125
|
+
jobTitle: payload.role || void 0,
|
|
126
|
+
source: "ai_inbox",
|
|
127
|
+
status: "active",
|
|
128
|
+
lifecycleStage: "lead"
|
|
129
|
+
};
|
|
130
|
+
if (companyEntityId) {
|
|
131
|
+
personInput.companyEntityId = companyEntityId;
|
|
132
|
+
}
|
|
81
133
|
const result = await executeCommand(
|
|
82
134
|
hCtx,
|
|
83
135
|
"customers.people.create",
|
|
84
|
-
|
|
85
|
-
organizationId: hCtx.organizationId,
|
|
86
|
-
tenantId: hCtx.tenantId,
|
|
87
|
-
displayName: payload.name,
|
|
88
|
-
firstName,
|
|
89
|
-
lastName,
|
|
90
|
-
primaryEmail: payload.email,
|
|
91
|
-
primaryPhone: payload.phone,
|
|
92
|
-
jobTitle: payload.role,
|
|
93
|
-
source: payload.source
|
|
94
|
-
}
|
|
136
|
+
personInput
|
|
95
137
|
);
|
|
96
138
|
if (!result.entityId) {
|
|
97
139
|
throw new ExecutionError("Person creation did not return an entity ID", 500);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/customers/inbox-actions.ts"],
|
|
4
|
-
"sourcesContent": ["import type { InboxActionDefinition, InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'\nimport {\n createContactPayloadSchema,\n linkContactPayloadSchema,\n logActivityPayloadSchema,\n draftReplyPayloadSchema,\n} from '../inbox_ops/data/validators'\nimport type {\n CreateContactPayload,\n LinkContactPayload,\n LogActivityPayload,\n DraftReplyPayload,\n} from '../inbox_ops/data/validators'\nimport {\n asHelperContext,\n ExecutionError,\n executeCommand,\n resolveEntityClass,\n resolveCustomerEntityIdByEmail,\n resolveContactIdByNameAndType,\n} from '../inbox_ops/lib/executionHelpers'\nimport { splitPersonName } from '../inbox_ops/lib/contactValidation'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\n// ---------------------------------------------------------------------------\n// create_contact\n// ---------------------------------------------------------------------------\n\nasync function executeCreateContactAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null; matchedEntityId?: string | null; matchedEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as CreateContactPayload\n\n const CustomerEntityClass = resolveEntityClass(hCtx, 'CustomerEntity')\n if (payload.email && CustomerEntityClass) {\n const emailLower = payload.email.trim().toLowerCase()\n let existingContact = await findOneWithDecryption(\n hCtx.em,\n CustomerEntityClass,\n {\n primaryEmail: emailLower,\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: hCtx.tenantId, organizationId: hCtx.organizationId },\n )\n if (!existingContact) {\n const candidates = await findWithDecryption(\n hCtx.em,\n CustomerEntityClass,\n {\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n deletedAt: null,\n },\n { limit: 100, orderBy: { createdAt: 'DESC' } },\n { tenantId: hCtx.tenantId, organizationId: hCtx.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 hCtx,\n 'customers.companies.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.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 { createdEntityId: result.entityId, createdEntityType: 'customer_company' }\n }\n\n const { firstName, lastName } = splitPersonName(payload.name, payload.email)\n const result = await executeCommand<Record<string, unknown>, { entityId?: string }>(\n hCtx,\n 'customers.people.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.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 { createdEntityId: result.entityId, createdEntityType: 'customer_person' }\n}\n\n// ---------------------------------------------------------------------------\n// link_contact\n// ---------------------------------------------------------------------------\n\nfunction executeLinkContactAction(\n action: { id: string; proposalId: string; payload: unknown },\n): { createdEntityId?: string | null; createdEntityType?: string | null; matchedEntityId?: string | null; matchedEntityType?: string | null } {\n const payload = action.payload as LinkContactPayload\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\n// ---------------------------------------------------------------------------\n// log_activity\n// ---------------------------------------------------------------------------\n\nasync function executeLogActivityAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n let payload = action.payload as LogActivityPayload\n\n if (!payload.contactId) {\n const resolved = await resolveContactIdByNameAndType(hCtx, 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 hCtx,\n 'customers.activities.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n entityId: payload.contactId,\n activityType: payload.activityType,\n subject: payload.subject,\n body: payload.body,\n authorUserId: hCtx.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 { createdEntityId: result.activityId, createdEntityType: 'customer_activity' }\n}\n\n// ---------------------------------------------------------------------------\n// draft_reply\n// ---------------------------------------------------------------------------\n\nasync function executeDraftReplyAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as DraftReplyPayload\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(hCtx, 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 hCtx,\n 'customers.activities.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n entityId: contactId,\n activityType: 'email',\n subject: payload.subject,\n body: details,\n authorUserId: hCtx.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 { createdEntityId: result.activityId, createdEntityType: 'customer_activity' }\n}\n\n// ---------------------------------------------------------------------------\n// Exported action definitions\n// ---------------------------------------------------------------------------\n\nexport const inboxActions: InboxActionDefinition[] = [\n {\n type: 'create_contact',\n requiredFeature: 'customers.people.manage',\n payloadSchema: createContactPayloadSchema,\n label: 'Create Contact',\n promptSchema: `create_contact payload:\n{ type: \"person\"|\"company\", name: string, email?: string, phone?: string, companyName?: string, role?: string, source: \"inbox_ops\" }`,\n promptRules: [\n 'For create_contact: always include email when available from the thread. Set source to \"inbox_ops\", type must be lowercase \"person\" or \"company\".',\n 'For create_contact with type \"person\": if the sender\\'s display name is not available in the email header or signature, attempt to derive a human-readable name from the email address (e.g., john.doe@company.com -> \"John Doe\", m.smith@corp.net -> \"M Smith\"). If the email address does not contain a derivable name (e.g., info@, noreply@), use the full email address as the name. Always aim to provide both a first and last name when possible.',\n ],\n execute: executeCreateContactAction,\n },\n {\n type: 'link_contact',\n requiredFeature: 'customers.people.manage',\n payloadSchema: linkContactPayloadSchema,\n label: 'Link Contact',\n promptSchema: `link_contact payload:\n{ emailAddress: string (email), contactId: uuid, contactType: \"person\"|\"company\", contactName: string }`,\n execute: (action) => Promise.resolve(executeLinkContactAction(action)),\n },\n {\n type: 'log_activity',\n requiredFeature: 'customers.activities.manage',\n payloadSchema: logActivityPayloadSchema,\n label: 'Log Activity',\n promptSchema: `log_activity payload:\n{ contactId?: uuid, contactType: \"person\"|\"company\", contactName: string, activityType: \"email\"|\"call\"|\"meeting\"|\"note\", subject: string, body: string }`,\n execute: executeLogActivityAction,\n },\n {\n type: 'draft_reply',\n requiredFeature: 'inbox_ops.replies.send',\n payloadSchema: draftReplyPayloadSchema,\n label: 'Draft Reply',\n promptSchema: `draft_reply payload:\n{ to: string (email), toName?: string, subject: string, body: string, context?: string }`,\n promptRules: ['For draft_reply: include ERP context when available.'],\n execute: executeDraftReplyAction,\n },\n]\n\nexport default inboxActions\n"],
|
|
5
|
-
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["import type { InboxActionDefinition, InboxActionExecutionContext } from '@open-mercato/shared/modules/inbox-actions'\nimport {\n createContactPayloadSchema,\n linkContactPayloadSchema,\n logActivityPayloadSchema,\n draftReplyPayloadSchema,\n} from '../inbox_ops/data/validators'\nimport type {\n CreateContactPayload,\n LinkContactPayload,\n LogActivityPayload,\n DraftReplyPayload,\n} from '../inbox_ops/data/validators'\nimport {\n asHelperContext,\n ExecutionError,\n executeCommand,\n resolveEntityClass,\n resolveCustomerEntityIdByEmail,\n resolveContactIdByNameAndType,\n} from '../inbox_ops/lib/executionHelpers'\nimport { splitPersonName, stripTitleFromName } from '../inbox_ops/lib/contactValidation'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function resolveOrCreateCompany(\n hCtx: ReturnType<typeof asHelperContext>,\n companyName: string,\n): Promise<string | null> {\n const CustomerEntityClass = resolveEntityClass(hCtx, 'CustomerEntity')\n if (CustomerEntityClass) {\n const allCompanies = await findWithDecryption(\n hCtx.em,\n CustomerEntityClass,\n {\n kind: 'company',\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n deletedAt: null,\n },\n { limit: 500 },\n { tenantId: hCtx.tenantId, organizationId: hCtx.organizationId },\n )\n const trimmedLower = companyName.trim().toLowerCase()\n const existing = allCompanies.find(\n (c) => c.displayName && c.displayName.toLowerCase() === trimmedLower,\n )\n if (existing) return existing.id\n }\n\n try {\n const result = await executeCommand<Record<string, unknown>, { entityId?: string }>(\n hCtx,\n 'customers.companies.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n displayName: companyName.trim(),\n legalName: companyName.trim(),\n source: 'ai_inbox',\n status: 'active',\n },\n )\n return result.entityId ?? null\n } catch (err) {\n console.warn('[customers:inbox-action] Failed to create company (non-fatal):', err)\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// create_contact\n// ---------------------------------------------------------------------------\n\nasync function executeCreateContactAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null; matchedEntityId?: string | null; matchedEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as CreateContactPayload\n\n const CustomerEntityClass = resolveEntityClass(hCtx, 'CustomerEntity')\n if (payload.email && CustomerEntityClass) {\n const emailLower = payload.email.trim().toLowerCase()\n const targetKind = payload.type === 'company' ? 'company' : 'person'\n let existingContact = await findOneWithDecryption(\n hCtx.em,\n CustomerEntityClass,\n {\n primaryEmail: emailLower,\n kind: targetKind,\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: hCtx.tenantId, organizationId: hCtx.organizationId },\n )\n if (!existingContact) {\n const candidates = await findWithDecryption(\n hCtx.em,\n CustomerEntityClass,\n {\n kind: targetKind,\n tenantId: hCtx.tenantId,\n organizationId: hCtx.organizationId,\n deletedAt: null,\n },\n { limit: 100, orderBy: { createdAt: 'DESC' } },\n { tenantId: hCtx.tenantId, organizationId: hCtx.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 // Use find-or-create to prevent duplicates when a person action with the\n // same companyName already created this company (or vice versa).\n const companyId = await resolveOrCreateCompany(hCtx, payload.name)\n if (!companyId) {\n throw new ExecutionError('Company creation did not return an entity ID', 500)\n }\n return { createdEntityId: companyId, createdEntityType: 'customer_company' }\n }\n\n const { cleanedName } = stripTitleFromName(payload.name)\n const { firstName, lastName } = splitPersonName(payload.name, payload.email)\n\n // If company name is provided, find or create the company and link it to the person.\n // No separate permission check \u2014 the user already passed customers.people.manage\n // in the execution engine, and company creation is an integral part of contact setup.\n let companyEntityId: string | null = null\n if (payload.companyName) {\n companyEntityId = await resolveOrCreateCompany(hCtx, payload.companyName)\n }\n\n const personInput: Record<string, unknown> = {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n displayName: cleanedName,\n firstName,\n lastName,\n primaryEmail: payload.email,\n primaryPhone: payload.phone,\n jobTitle: payload.role || undefined,\n source: 'ai_inbox',\n status: 'active',\n lifecycleStage: 'lead',\n }\n if (companyEntityId) {\n personInput.companyEntityId = companyEntityId\n }\n\n const result = await executeCommand<Record<string, unknown>, { entityId?: string }>(\n hCtx,\n 'customers.people.create',\n personInput,\n )\n\n if (!result.entityId) {\n throw new ExecutionError('Person creation did not return an entity ID', 500)\n }\n\n return { createdEntityId: result.entityId, createdEntityType: 'customer_person' }\n}\n\n// ---------------------------------------------------------------------------\n// link_contact\n// ---------------------------------------------------------------------------\n\nfunction executeLinkContactAction(\n action: { id: string; proposalId: string; payload: unknown },\n): { createdEntityId?: string | null; createdEntityType?: string | null; matchedEntityId?: string | null; matchedEntityType?: string | null } {\n const payload = action.payload as LinkContactPayload\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\n// ---------------------------------------------------------------------------\n// log_activity\n// ---------------------------------------------------------------------------\n\nasync function executeLogActivityAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n let payload = action.payload as LogActivityPayload\n\n if (!payload.contactId) {\n const resolved = await resolveContactIdByNameAndType(hCtx, 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 hCtx,\n 'customers.activities.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n entityId: payload.contactId,\n activityType: payload.activityType,\n subject: payload.subject,\n body: payload.body,\n authorUserId: hCtx.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 { createdEntityId: result.activityId, createdEntityType: 'customer_activity' }\n}\n\n// ---------------------------------------------------------------------------\n// draft_reply\n// ---------------------------------------------------------------------------\n\nasync function executeDraftReplyAction(\n action: { id: string; proposalId: string; payload: unknown },\n ctx: InboxActionExecutionContext,\n): Promise<{ createdEntityId?: string | null; createdEntityType?: string | null }> {\n const hCtx = asHelperContext(ctx)\n const payload = action.payload as DraftReplyPayload\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(hCtx, 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 hCtx,\n 'customers.activities.create',\n {\n organizationId: hCtx.organizationId,\n tenantId: hCtx.tenantId,\n entityId: contactId,\n activityType: 'email',\n subject: payload.subject,\n body: details,\n authorUserId: hCtx.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 { createdEntityId: result.activityId, createdEntityType: 'customer_activity' }\n}\n\n// ---------------------------------------------------------------------------\n// Exported action definitions\n// ---------------------------------------------------------------------------\n\nexport const inboxActions: InboxActionDefinition[] = [\n {\n type: 'create_contact',\n requiredFeature: 'customers.people.manage',\n payloadSchema: createContactPayloadSchema,\n label: 'Create Contact',\n promptSchema: `create_contact payload:\n{ type: \"person\"|\"company\", name: string, email?: string, phone?: string, companyName?: string, role?: string, source: \"inbox_ops\" }`,\n promptRules: [\n 'For create_contact: always include email when available from the thread. Set source to \"inbox_ops\", type must be lowercase \"person\" or \"company\".',\n 'For create_contact with type \"person\": if the sender\\'s display name is not available in the email header or signature, attempt to derive a human-readable name from the email address (e.g., john.doe@company.com -> \"John Doe\", m.smith@corp.net -> \"M Smith\"). If the email address does not contain a derivable name (e.g., info@, noreply@), use the full email address as the name. Always aim to provide both a first and last name when possible.',\n ],\n execute: executeCreateContactAction,\n },\n {\n type: 'link_contact',\n requiredFeature: 'customers.people.manage',\n payloadSchema: linkContactPayloadSchema,\n label: 'Link Contact',\n promptSchema: `link_contact payload:\n{ emailAddress: string (email), contactId: uuid, contactType: \"person\"|\"company\", contactName: string }`,\n execute: (action) => Promise.resolve(executeLinkContactAction(action)),\n },\n {\n type: 'log_activity',\n requiredFeature: 'customers.activities.manage',\n payloadSchema: logActivityPayloadSchema,\n label: 'Log Activity',\n promptSchema: `log_activity payload:\n{ contactId?: uuid, contactType: \"person\"|\"company\", contactName: string, activityType: \"email\"|\"call\"|\"meeting\"|\"note\", subject: string, body: string }`,\n execute: executeLogActivityAction,\n },\n {\n type: 'draft_reply',\n requiredFeature: 'inbox_ops.replies.send',\n payloadSchema: draftReplyPayloadSchema,\n label: 'Draft Reply',\n promptSchema: `draft_reply payload:\n{ to: string (email), toName?: string, subject: string, body: string, context?: string }`,\n promptRules: ['For draft_reply: include ERP context when available.'],\n execute: executeDraftReplyAction,\n },\n]\n\nexport default inboxActions\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|