@open-mercato/core 0.10.1-develop.7672.1.c2e4c0b5df → 0.10.1-develop.7676.1.e75439fc4f
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/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/queue.js +22 -1
- package/dist/helpers/integration/queue.js.map +2 -2
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +64 -66
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/activity-read-model.js +193 -0
- package/dist/modules/customers/ai-tools/activity-read-model.js.map +7 -0
- package/dist/modules/customers/commands/companies.js +6 -0
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/people.js +6 -0
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/queue.ts +25 -1
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +65 -75
- package/src/modules/customers/ai-tools/activity-read-model.ts +228 -0
- package/src/modules/customers/commands/companies.ts +9 -0
- package/src/modules/customers/commands/people.ts +9 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { expect } from "@playwright/test";
|
|
5
|
+
import { createQueue, getQueuePendingProbe } from "@open-mercato/queue";
|
|
6
|
+
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
5
7
|
const DEFAULT_JOB_LIMIT = 100;
|
|
6
8
|
function resolveAppRoot(input) {
|
|
7
9
|
return path.resolve(input?.trim() || process.env.OM_TEST_APP_ROOT?.trim() || path.resolve(process.cwd(), "apps/mercato"));
|
|
8
10
|
}
|
|
9
11
|
async function drainQueueInCurrentProcess(queueName, options) {
|
|
12
|
+
const { drainQueueFromAppRoot } = await import("./queue-runner.js");
|
|
10
13
|
return drainQueueFromAppRoot(queueName, options);
|
|
11
14
|
}
|
|
12
15
|
function resolveAppRunnerPath(appRoot) {
|
|
@@ -62,6 +65,22 @@ ${stderr || stdout}`));
|
|
|
62
65
|
});
|
|
63
66
|
});
|
|
64
67
|
}
|
|
68
|
+
async function waitForManagedQueue(queueName, options) {
|
|
69
|
+
const baseDir = process.env.QUEUE_BASE_DIR?.trim() || path.resolve(options.appRoot, ".mercato/queue");
|
|
70
|
+
const queue = createQueue(queueName, "local", { baseDir });
|
|
71
|
+
try {
|
|
72
|
+
const initial = await queue.getJobCounts();
|
|
73
|
+
await expect.poll(async () => {
|
|
74
|
+
const pending = await getQueuePendingProbe(queueName, "local", { baseDir });
|
|
75
|
+
if (pending.error) throw new Error(`[internal] Cannot observe integration queue ${queueName}: ${pending.errorMessage}`);
|
|
76
|
+
return pending.ready + pending.active;
|
|
77
|
+
}, { message: `Integration queue ${queueName} should finish processing` }).toBe(0);
|
|
78
|
+
const final = await queue.getJobCounts();
|
|
79
|
+
return final.completed + final.failed - initial.completed - initial.failed;
|
|
80
|
+
} finally {
|
|
81
|
+
await queue.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
65
84
|
async function drainIntegrationQueue(queueName, options = {}) {
|
|
66
85
|
const resolved = {
|
|
67
86
|
appRoot: resolveAppRoot(options.appRoot),
|
|
@@ -70,6 +89,8 @@ async function drainIntegrationQueue(queueName, options = {}) {
|
|
|
70
89
|
if (process.env.OM_TEST_APP_ROOT?.trim()) {
|
|
71
90
|
return drainQueueInAppProcess(queueName, resolved);
|
|
72
91
|
}
|
|
92
|
+
const managed = parseBooleanToken(process.env.AUTO_SPAWN_WORKERS) ?? parseBooleanToken(process.env.OM_AUTO_SPAWN_WORKERS) ?? true;
|
|
93
|
+
if (managed) return waitForManagedQueue(queueName, resolved);
|
|
73
94
|
return drainQueueInCurrentProcess(queueName, resolved);
|
|
74
95
|
}
|
|
75
96
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/queue.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawn } from 'node:child_process'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,
|
|
4
|
+
"sourcesContent": ["import { spawn } from 'node:child_process'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { expect } from '@playwright/test'\nimport { createQueue, getQueuePendingProbe } from '@open-mercato/queue'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\ntype DrainQueueOptions = {\n appRoot?: string\n jobLimit?: number\n}\n\nconst DEFAULT_JOB_LIMIT = 100\n\nfunction resolveAppRoot(input?: string): string {\n return path.resolve(input?.trim() || process.env.OM_TEST_APP_ROOT?.trim() || path.resolve(process.cwd(), 'apps/mercato'))\n}\n\nasync function drainQueueInCurrentProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n const { drainQueueFromAppRoot } = await import('./queue-runner')\n return drainQueueFromAppRoot(queueName, options)\n}\n\nfunction resolveAppRunnerPath(appRoot: string): string {\n const requireFromApp = createRequire(path.join(appRoot, 'package.json'))\n return requireFromApp.resolve('@open-mercato/core/helpers/integration/queue-runner')\n}\n\nfunction drainQueueInAppProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [resolveAppRunnerPath(options.appRoot)], {\n cwd: options.appRoot,\n env: {\n ...process.env,\n OM_INTEGRATION_APP_ROOT: options.appRoot,\n OM_INTEGRATION_QUEUE_NAME: queueName,\n OM_INTEGRATION_QUEUE_JOB_LIMIT: String(options.jobLimit),\n QUEUE_BASE_DIR: process.env.QUEUE_BASE_DIR?.trim() || path.resolve(options.appRoot, '.mercato/queue'),\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n let stdout = ''\n let stderr = ''\n child.stdout.on('data', (chunk) => { stdout += chunk.toString() })\n child.stderr.on('data', (chunk) => { stderr += chunk.toString() })\n child.on('error', reject)\n child.on('close', (code) => {\n // The child's diagnostics (pino job logs on stdout, cache-fallback\n // console.warn on stderr) are otherwise discarded on exit 0, which makes\n // silently-failing jobs and silent memory-cache fallbacks in the drain\n // child undebuggable from CI logs. OM_DRAIN_DEBUG=1 forwards everything;\n // a non-empty stderr is forwarded unconditionally because it only carries\n // warning/error-class output.\n if (process.env.OM_DRAIN_DEBUG === '1') {\n process.stderr.write(`[drain-child ${queueName} exit=${code}] STDOUT:\\n${stdout}\\nSTDERR:\\n${stderr}\\n`)\n } else if (stderr.trim().length > 0) {\n process.stderr.write(`[drain-child ${queueName} exit=${code}] STDERR:\\n${stderr}\\n`)\n }\n if (code !== 0) {\n reject(new Error(`Queue drain failed for \"${queueName}\" in ${options.appRoot} (exit ${code}).\\n${stderr || stdout}`))\n return\n }\n const lastLine = stdout.trim().split(/\\r?\\n/).reverse().find((line) => line.trim().startsWith('{')) ?? '{}'\n try {\n const parsed = JSON.parse(lastLine) as { processed?: unknown }\n resolve(Number(parsed.processed ?? 0))\n } catch {\n resolve(0)\n }\n })\n })\n}\n\nasync function waitForManagedQueue(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n const baseDir = process.env.QUEUE_BASE_DIR?.trim() || path.resolve(options.appRoot, '.mercato/queue')\n const queue = createQueue(queueName, 'local', { baseDir })\n try {\n const initial = await queue.getJobCounts()\n await expect.poll(async () => {\n const pending = await getQueuePendingProbe(queueName, 'local', { baseDir })\n if (pending.error) throw new Error(`[internal] Cannot observe integration queue ${queueName}: ${pending.errorMessage}`)\n return pending.ready + pending.active\n }, { message: `Integration queue ${queueName} should finish processing` }).toBe(0)\n const final = await queue.getJobCounts()\n return final.completed + final.failed - initial.completed - initial.failed\n } finally {\n await queue.close()\n }\n}\n\nexport async function drainIntegrationQueue(queueName: string, options: DrainQueueOptions = {}): Promise<number> {\n const resolved = {\n appRoot: resolveAppRoot(options.appRoot),\n jobLimit: options.jobLimit ?? DEFAULT_JOB_LIMIT,\n }\n if (process.env.OM_TEST_APP_ROOT?.trim()) {\n return drainQueueInAppProcess(queueName, resolved)\n }\n const managed = parseBooleanToken(process.env.AUTO_SPAWN_WORKERS)\n ?? parseBooleanToken(process.env.OM_AUTO_SPAWN_WORKERS)\n ?? true\n if (managed) return waitForManagedQueue(queueName, resolved)\n return drainQueueInCurrentProcess(queueName, resolved)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,cAAc;AACvB,SAAS,aAAa,4BAA4B;AAClD,SAAS,yBAAyB;AAOlC,MAAM,oBAAoB;AAE1B,SAAS,eAAe,OAAwB;AAC9C,SAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,IAAI,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc,CAAC;AAC1H;AAEA,eAAe,2BAA2B,WAAmB,SAAuD;AAClH,QAAM,EAAE,sBAAsB,IAAI,MAAM,OAAO,gBAAgB;AAC/D,SAAO,sBAAsB,WAAW,OAAO;AACjD;AAEA,SAAS,qBAAqB,SAAyB;AACrD,QAAM,iBAAiB,cAAc,KAAK,KAAK,SAAS,cAAc,CAAC;AACvE,SAAO,eAAe,QAAQ,qDAAqD;AACrF;AAEA,SAAS,uBAAuB,WAAmB,SAAuD;AACxG,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,qBAAqB,QAAQ,OAAO,CAAC,GAAG;AAAA,MAC7E,KAAK,QAAQ;AAAA,MACb,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,yBAAyB,QAAQ;AAAA,QACjC,2BAA2B;AAAA,QAC3B,gCAAgC,OAAO,QAAQ,QAAQ;AAAA,QACvD,gBAAgB,QAAQ,IAAI,gBAAgB,KAAK,KAAK,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,MACtG;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,CAAC,SAAS;AAO1B,UAAI,QAAQ,IAAI,mBAAmB,KAAK;AACtC,gBAAQ,OAAO,MAAM,gBAAgB,SAAS,SAAS,IAAI;AAAA,EAAc,MAAM;AAAA;AAAA,EAAc,MAAM;AAAA,CAAI;AAAA,MACzG,WAAW,OAAO,KAAK,EAAE,SAAS,GAAG;AACnC,gBAAQ,OAAO,MAAM,gBAAgB,SAAS,SAAS,IAAI;AAAA,EAAc,MAAM;AAAA,CAAI;AAAA,MACrF;AACA,UAAI,SAAS,GAAG;AACd,eAAO,IAAI,MAAM,2BAA2B,SAAS,QAAQ,QAAQ,OAAO,UAAU,IAAI;AAAA,EAAO,UAAU,MAAM,EAAE,CAAC;AACpH;AAAA,MACF;AACA,YAAM,WAAW,OAAO,KAAK,EAAE,MAAM,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,WAAW,GAAG,CAAC,KAAK;AACvG,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,gBAAQ,OAAO,OAAO,aAAa,CAAC,CAAC;AAAA,MACvC,QAAQ;AACN,gBAAQ,CAAC;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAe,oBAAoB,WAAmB,SAAuD;AAC3G,QAAM,UAAU,QAAQ,IAAI,gBAAgB,KAAK,KAAK,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AACpG,QAAM,QAAQ,YAAY,WAAW,SAAS,EAAE,QAAQ,CAAC;AACzD,MAAI;AACF,UAAM,UAAU,MAAM,MAAM,aAAa;AACzC,UAAM,OAAO,KAAK,YAAY;AAC5B,YAAM,UAAU,MAAM,qBAAqB,WAAW,SAAS,EAAE,QAAQ,CAAC;AAC1E,UAAI,QAAQ,MAAO,OAAM,IAAI,MAAM,+CAA+C,SAAS,KAAK,QAAQ,YAAY,EAAE;AACtH,aAAO,QAAQ,QAAQ,QAAQ;AAAA,IACjC,GAAG,EAAE,SAAS,qBAAqB,SAAS,4BAA4B,CAAC,EAAE,KAAK,CAAC;AACjF,UAAM,QAAQ,MAAM,MAAM,aAAa;AACvC,WAAO,MAAM,YAAY,MAAM,SAAS,QAAQ,YAAY,QAAQ;AAAA,EACtE,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF;AAEA,eAAsB,sBAAsB,WAAmB,UAA6B,CAAC,GAAoB;AAC/G,QAAM,WAAW;AAAA,IACf,SAAS,eAAe,QAAQ,OAAO;AAAA,IACvC,UAAU,QAAQ,YAAY;AAAA,EAChC;AACA,MAAI,QAAQ,IAAI,kBAAkB,KAAK,GAAG;AACxC,WAAO,uBAAuB,WAAW,QAAQ;AAAA,EACnD;AACA,QAAM,UAAU,kBAAkB,QAAQ,IAAI,kBAAkB,KAC3D,kBAAkB,QAAQ,IAAI,qBAAqB,KACnD;AACL,MAAI,QAAS,QAAO,oBAAoB,WAAW,QAAQ;AAC3D,SAAO,2BAA2B,WAAW,QAAQ;AACvD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
createAiApiOperationRunner
|
|
4
4
|
} from "@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner";
|
|
5
5
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { authorizeFeatures } from "@open-mercato/shared/security/featurePolicy";
|
|
6
7
|
import { TERMINAL_INTERACTION_STATUS_LIST } from "../lib/interactionStatus.js";
|
|
8
|
+
import { resolveCustomerInteractionFeatureFlags } from "../lib/interactionFeatureFlags.js";
|
|
9
|
+
import { CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE } from "../lib/interactionCompatibility.js";
|
|
10
|
+
import { listActivitiesForScope, loadActivityForScope } from "./activity-read-model.js";
|
|
7
11
|
import {
|
|
8
|
-
CustomerActivity,
|
|
9
12
|
CustomerComment,
|
|
10
13
|
CustomerDeal,
|
|
11
14
|
CustomerDealCompanyLink,
|
|
@@ -93,28 +96,49 @@ async function loadCommentForScope(em, ctx, tenantId, commentId) {
|
|
|
93
96
|
if (ctx.organizationId && row.organizationId !== ctx.organizationId) return null;
|
|
94
97
|
return row;
|
|
95
98
|
}
|
|
96
|
-
async function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
async function runActivityOperation(ctx, request) {
|
|
100
|
+
const { tenantId } = assertTenantScope(ctx);
|
|
101
|
+
const flags = await resolveCustomerInteractionFeatureFlags(ctx.container, tenantId);
|
|
102
|
+
const executionContext = ctx;
|
|
103
|
+
if (flags.legacyAdapters) {
|
|
104
|
+
return createAiApiOperationRunner(executionContext).run(request);
|
|
105
|
+
}
|
|
106
|
+
const requiredFeatures = ["customers.interactions.manage"];
|
|
107
|
+
if (!authorizeFeatures(requiredFeatures, {
|
|
108
|
+
grantedFeatures: ctx.userFeatures,
|
|
109
|
+
unrestricted: ctx.isSuperAdmin
|
|
110
|
+
})) {
|
|
111
|
+
throw new Error("[internal] Canonical interaction management permission is required");
|
|
112
|
+
}
|
|
113
|
+
const { activityType, subject, ...body } = request.body ?? {};
|
|
114
|
+
const runner = createAiApiOperationRunner({
|
|
115
|
+
...executionContext,
|
|
116
|
+
tool: { ...executionContext.tool, requiredFeatures }
|
|
117
|
+
});
|
|
118
|
+
return runner.run({
|
|
119
|
+
...request,
|
|
120
|
+
path: "/customers/interactions",
|
|
121
|
+
body: {
|
|
122
|
+
...body,
|
|
123
|
+
...activityType !== void 0 ? { interactionType: activityType } : {},
|
|
124
|
+
...subject !== void 0 ? { title: subject } : {},
|
|
125
|
+
...request.method === "POST" ? {
|
|
126
|
+
source: CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,
|
|
127
|
+
status: body.occurredAt ? "done" : "planned"
|
|
128
|
+
} : {},
|
|
129
|
+
...request.method === "PUT" && body.occurredAt ? { status: "done" } : {}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
109
132
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
function activitySnapshot(activity) {
|
|
134
|
+
return activity ? {
|
|
135
|
+
activityType: activity.activityType,
|
|
136
|
+
subject: activity.subject,
|
|
137
|
+
body: activity.body,
|
|
138
|
+
occurredAt: activity.occurredAt?.toISOString() ?? null
|
|
139
|
+
} : null;
|
|
116
140
|
}
|
|
117
|
-
function
|
|
141
|
+
function commentEntityIdOf(row) {
|
|
118
142
|
const ent = row.entity;
|
|
119
143
|
if (!ent) return null;
|
|
120
144
|
if (typeof ent === "string") return ent;
|
|
@@ -142,33 +166,17 @@ const listActivitiesTool = {
|
|
|
142
166
|
const em = resolveEm(ctx);
|
|
143
167
|
const limit = input.limit ?? 50;
|
|
144
168
|
const offset = input.offset ?? 0;
|
|
145
|
-
const
|
|
146
|
-
if (ctx.organizationId) where.organizationId = ctx.organizationId;
|
|
147
|
-
const entityId = input.personId ?? input.companyId ?? null;
|
|
148
|
-
if (entityId) where.entity = entityId;
|
|
149
|
-
if (input.dealId) where.deal = input.dealId;
|
|
150
|
-
if (input.activityType) where.activityType = input.activityType;
|
|
151
|
-
const [rows, total] = await Promise.all([
|
|
152
|
-
findWithDecryption(
|
|
153
|
-
em,
|
|
154
|
-
CustomerActivity,
|
|
155
|
-
where,
|
|
156
|
-
{ limit, offset, orderBy: { occurredAt: "desc", createdAt: "desc" } },
|
|
157
|
-
buildScope(ctx, tenantId)
|
|
158
|
-
),
|
|
159
|
-
em.count(CustomerActivity, where)
|
|
160
|
-
]);
|
|
161
|
-
const filtered = rows.filter((row) => row.tenantId === tenantId);
|
|
169
|
+
const { items: rows, total } = await listActivitiesForScope(em, ctx, tenantId, { ...input, limit, offset });
|
|
162
170
|
return {
|
|
163
|
-
items:
|
|
171
|
+
items: rows.map((row) => ({
|
|
164
172
|
id: row.id,
|
|
165
173
|
activityType: row.activityType,
|
|
166
174
|
subject: row.subject ?? null,
|
|
167
175
|
body: row.body ?? null,
|
|
168
176
|
occurredAt: row.occurredAt ? new Date(row.occurredAt).toISOString() : null,
|
|
169
177
|
authorUserId: row.authorUserId ?? null,
|
|
170
|
-
entityId:
|
|
171
|
-
dealId: row.
|
|
178
|
+
entityId: row.entityId,
|
|
179
|
+
dealId: row.dealId,
|
|
172
180
|
organizationId: row.organizationId ?? null,
|
|
173
181
|
tenantId: row.tenantId ?? null,
|
|
174
182
|
createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null
|
|
@@ -503,14 +511,14 @@ const manageDealActivityTool = {
|
|
|
503
511
|
if (!existing) return null;
|
|
504
512
|
return {
|
|
505
513
|
recordId: existing.id,
|
|
506
|
-
entityType:
|
|
514
|
+
entityType: existing.entityType,
|
|
507
515
|
recordVersion: recordVersionFromUpdatedAt(existing.updatedAt),
|
|
508
516
|
before: {
|
|
509
517
|
activityType: existing.activityType,
|
|
510
518
|
subject: existing.subject ?? null,
|
|
511
519
|
body: existing.body ?? null,
|
|
512
520
|
occurredAt: existing.occurredAt ? new Date(existing.occurredAt).toISOString() : null,
|
|
513
|
-
dealId: existing.
|
|
521
|
+
dealId: existing.dealId
|
|
514
522
|
}
|
|
515
523
|
};
|
|
516
524
|
},
|
|
@@ -518,7 +526,6 @@ const manageDealActivityTool = {
|
|
|
518
526
|
const { tenantId } = assertTenantScope(ctx);
|
|
519
527
|
const input = manageDealActivityInput.parse(rawInput);
|
|
520
528
|
const em = resolveEm(ctx);
|
|
521
|
-
const runner = createAiApiOperationRunner(ctx);
|
|
522
529
|
if (input.operation === "create") {
|
|
523
530
|
const deal = await loadDealForScope(em, ctx, tenantId, input.dealId);
|
|
524
531
|
if (!deal) throw new Error(`Deal "${input.dealId}" is not accessible to the caller.`);
|
|
@@ -538,23 +545,19 @@ const manageDealActivityTool = {
|
|
|
538
545
|
if (input.subject) body2.subject = input.subject;
|
|
539
546
|
if (input.body) body2.body = input.body;
|
|
540
547
|
if (input.occurredAt) body2.occurredAt = input.occurredAt;
|
|
541
|
-
const response2 = await
|
|
548
|
+
const response2 = await runActivityOperation(ctx, { method: "POST", path: "/customers/activities", body: body2 });
|
|
542
549
|
if (!response2.success) {
|
|
543
550
|
throw new Error(response2.error ?? "Failed to create activity");
|
|
544
551
|
}
|
|
545
552
|
const result = response2.data ?? {};
|
|
553
|
+
const created = result.id ? await loadActivityForScope(em, ctx, tenantId, result.id) : null;
|
|
546
554
|
return {
|
|
547
555
|
operation: "create",
|
|
548
556
|
activityId: result.id ?? null,
|
|
549
557
|
dealId: deal.id,
|
|
550
558
|
commandName: "customers.activities.create",
|
|
551
559
|
before: null,
|
|
552
|
-
after:
|
|
553
|
-
activityType: input.activityType ?? null,
|
|
554
|
-
subject: input.subject ?? null,
|
|
555
|
-
body: input.body ?? null,
|
|
556
|
-
occurredAt: input.occurredAt ?? null
|
|
557
|
-
}
|
|
560
|
+
after: activitySnapshot(created)
|
|
558
561
|
};
|
|
559
562
|
}
|
|
560
563
|
if (input.operation === "update") {
|
|
@@ -567,7 +570,7 @@ const manageDealActivityTool = {
|
|
|
567
570
|
if (input.subject !== void 0) body2.subject = input.subject;
|
|
568
571
|
if (input.body !== void 0) body2.body = input.body;
|
|
569
572
|
if (input.occurredAt !== void 0) body2.occurredAt = input.occurredAt;
|
|
570
|
-
const response2 = await
|
|
573
|
+
const response2 = await runActivityOperation(ctx, { method: "PUT", path: "/customers/activities", body: body2 });
|
|
571
574
|
if (!response2.success) {
|
|
572
575
|
throw new Error(response2.error ?? `Failed to update activity "${existing2.id}"`);
|
|
573
576
|
}
|
|
@@ -593,7 +596,7 @@ const manageDealActivityTool = {
|
|
|
593
596
|
const existing = await loadActivityForScope(em, ctx, tenantId, input.activityId);
|
|
594
597
|
if (!existing) throw new Error(`Activity "${input.activityId}" is not accessible to the caller.`);
|
|
595
598
|
const body = { id: existing.id };
|
|
596
|
-
const response = await
|
|
599
|
+
const response = await runActivityOperation(ctx, { method: "DELETE", path: "/customers/activities", body });
|
|
597
600
|
if (!response.success) {
|
|
598
601
|
throw new Error(response.error ?? `Failed to delete activity "${existing.id}"`);
|
|
599
602
|
}
|
|
@@ -897,15 +900,15 @@ const manageRecordActivityTool = {
|
|
|
897
900
|
if (!existing) return null;
|
|
898
901
|
return {
|
|
899
902
|
recordId: existing.id,
|
|
900
|
-
entityType:
|
|
903
|
+
entityType: existing.entityType,
|
|
901
904
|
recordVersion: recordVersionFromUpdatedAt(existing.updatedAt),
|
|
902
905
|
before: {
|
|
903
906
|
activityType: existing.activityType,
|
|
904
907
|
subject: existing.subject ?? null,
|
|
905
908
|
body: existing.body ?? null,
|
|
906
909
|
occurredAt: existing.occurredAt ? new Date(existing.occurredAt).toISOString() : null,
|
|
907
|
-
entityId:
|
|
908
|
-
dealId: existing.
|
|
910
|
+
entityId: existing.entityId,
|
|
911
|
+
dealId: existing.dealId
|
|
909
912
|
}
|
|
910
913
|
};
|
|
911
914
|
},
|
|
@@ -913,7 +916,6 @@ const manageRecordActivityTool = {
|
|
|
913
916
|
const { tenantId } = assertTenantScope(ctx);
|
|
914
917
|
const input = manageRecordActivityInput.parse(rawInput);
|
|
915
918
|
const em = resolveEm(ctx);
|
|
916
|
-
const runner = createAiApiOperationRunner(ctx);
|
|
917
919
|
if (input.operation === "create") {
|
|
918
920
|
const entityId = input.personId ?? input.companyId;
|
|
919
921
|
let organizationId = ctx.organizationId;
|
|
@@ -937,11 +939,12 @@ const manageRecordActivityTool = {
|
|
|
937
939
|
if (input.subject) body2.subject = input.subject;
|
|
938
940
|
if (input.body) body2.body = input.body;
|
|
939
941
|
if (input.occurredAt) body2.occurredAt = input.occurredAt;
|
|
940
|
-
const response2 = await
|
|
942
|
+
const response2 = await runActivityOperation(ctx, { method: "POST", path: "/customers/activities", body: body2 });
|
|
941
943
|
if (!response2.success) {
|
|
942
944
|
throw new Error(response2.error ?? "Failed to create activity");
|
|
943
945
|
}
|
|
944
946
|
const result = response2.data ?? {};
|
|
947
|
+
const created = result.id ? await loadActivityForScope(em, ctx, tenantId, result.id) : null;
|
|
945
948
|
return {
|
|
946
949
|
operation: "create",
|
|
947
950
|
activityId: result.id ?? null,
|
|
@@ -949,12 +952,7 @@ const manageRecordActivityTool = {
|
|
|
949
952
|
dealId,
|
|
950
953
|
commandName: "customers.activities.create",
|
|
951
954
|
before: null,
|
|
952
|
-
after:
|
|
953
|
-
activityType: input.activityType ?? null,
|
|
954
|
-
subject: input.subject ?? null,
|
|
955
|
-
body: input.body ?? null,
|
|
956
|
-
occurredAt: input.occurredAt ?? null
|
|
957
|
-
}
|
|
955
|
+
after: activitySnapshot(created)
|
|
958
956
|
};
|
|
959
957
|
}
|
|
960
958
|
if (input.operation === "update") {
|
|
@@ -967,7 +965,7 @@ const manageRecordActivityTool = {
|
|
|
967
965
|
if (input.subject !== void 0) body2.subject = input.subject;
|
|
968
966
|
if (input.body !== void 0) body2.body = input.body;
|
|
969
967
|
if (input.occurredAt !== void 0) body2.occurredAt = input.occurredAt;
|
|
970
|
-
const response2 = await
|
|
968
|
+
const response2 = await runActivityOperation(ctx, { method: "PUT", path: "/customers/activities", body: body2 });
|
|
971
969
|
if (!response2.success) {
|
|
972
970
|
throw new Error(response2.error ?? `Failed to update activity "${existing2.id}"`);
|
|
973
971
|
}
|
|
@@ -993,7 +991,7 @@ const manageRecordActivityTool = {
|
|
|
993
991
|
const existing = await loadActivityForScope(em, ctx, tenantId, input.activityId);
|
|
994
992
|
if (!existing) throw new Error(`Activity "${input.activityId}" is not accessible to the caller.`);
|
|
995
993
|
const body = { id: existing.id };
|
|
996
|
-
const response = await
|
|
994
|
+
const response = await runActivityOperation(ctx, { method: "DELETE", path: "/customers/activities", body });
|
|
997
995
|
if (!response.success) {
|
|
998
996
|
throw new Error(response.error ?? `Failed to delete activity "${existing.id}"`);
|
|
999
997
|
}
|