@open-mercato/core 0.6.6-develop.6313.1.618a49a6b4 → 0.6.6-develop.6317.1.b3be10ab84
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/modules/customers/ai-tools/_shared.js +158 -0
- package/dist/modules/customers/ai-tools/_shared.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +15 -121
- package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/people-pack.js +9 -107
- package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +92 -37
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +12 -1
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +3 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/types.js.map +1 -1
- package/dist/modules/customers/components/formConfig.js +7 -1
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/lib/dictionaries.js +10 -0
- package/dist/modules/customers/lib/dictionaries.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/ai-tools/_shared.ts +270 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +17 -157
- package/src/modules/customers/ai-tools/people-pack.ts +11 -133
- package/src/modules/customers/components/DictionarySettings.tsx +60 -2
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -0
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +29 -11
- package/src/modules/customers/components/calendar/TimeGrid.tsx +3 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/formConfig.tsx +8 -2
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/dictionaries.ts +10 -0
|
@@ -21,6 +21,13 @@ const CUSTOMER_DICTIONARY_KINDS = [
|
|
|
21
21
|
"renewal-quarters",
|
|
22
22
|
"person-company-roles"
|
|
23
23
|
];
|
|
24
|
+
const CUSTOMER_DICTIONARIES_MANAGE_HREF = "/backend/config/customers";
|
|
25
|
+
function getCustomerDictionarySettingsSectionId(kind) {
|
|
26
|
+
return `customer-dictionary-${kind}`;
|
|
27
|
+
}
|
|
28
|
+
function getCustomerDictionaryManageHref(kind) {
|
|
29
|
+
return `${CUSTOMER_DICTIONARIES_MANAGE_HREF}#${getCustomerDictionarySettingsSectionId(kind)}`;
|
|
30
|
+
}
|
|
24
31
|
function createEmptyCustomerDictionaryMaps() {
|
|
25
32
|
return CUSTOMER_DICTIONARY_KINDS.reduce(
|
|
26
33
|
(acc, kind) => {
|
|
@@ -31,12 +38,15 @@ function createEmptyCustomerDictionaryMaps() {
|
|
|
31
38
|
);
|
|
32
39
|
}
|
|
33
40
|
export {
|
|
41
|
+
CUSTOMER_DICTIONARIES_MANAGE_HREF,
|
|
34
42
|
CUSTOMER_DICTIONARY_KINDS,
|
|
35
43
|
DictionaryValue,
|
|
36
44
|
ICON_LIBRARY,
|
|
37
45
|
ICON_SUGGESTIONS,
|
|
38
46
|
createDictionaryMap,
|
|
39
47
|
createEmptyCustomerDictionaryMaps,
|
|
48
|
+
getCustomerDictionaryManageHref,
|
|
49
|
+
getCustomerDictionarySettingsSectionId,
|
|
40
50
|
normalizeDictionaryEntries,
|
|
41
51
|
renderDictionaryColor,
|
|
42
52
|
renderDictionaryIcon
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/lib/dictionaries.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n type DictionaryDisplayEntry,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n DictionaryValue,\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nexport const CUSTOMER_DICTIONARY_KINDS = [\n 'statuses',\n 'sources',\n 'lifecycle-stages',\n 'address-types',\n 'activity-types',\n 'deal-statuses',\n 'pipeline-stages',\n 'job-titles',\n 'industries',\n 'temperature',\n 'renewal-quarters',\n 'person-company-roles',\n] as const\n\nexport type CustomerDictionaryKind = typeof CUSTOMER_DICTIONARY_KINDS[number]\nexport type CustomerDictionaryDisplayEntry = DictionaryDisplayEntry\nexport type CustomerDictionaryMap = DictionaryMap\n\nexport function createEmptyCustomerDictionaryMaps(): Record<CustomerDictionaryKind, CustomerDictionaryMap> {\n return CUSTOMER_DICTIONARY_KINDS.reduce<Record<CustomerDictionaryKind, CustomerDictionaryMap>>(\n (acc, kind) => {\n acc[kind] = {}\n return acc\n },\n {} as Record<CustomerDictionaryKind, CustomerDictionaryMap>,\n )\n}\n\nexport {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n DictionaryValue,\n createDictionaryMap,\n normalizeDictionaryEntries,\n renderDictionaryColor,\n renderDictionaryIcon,\n}\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,SAAS,oCAA2F;AACzG,SAAO,0BAA0B;AAAA,IAC/B,CAAC,KAAK,SAAS;AACb,UAAI,IAAI,IAAI,CAAC;AACb,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AACF;",
|
|
4
|
+
"sourcesContent": ["import {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n type DictionaryDisplayEntry,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n DictionaryValue,\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nexport const CUSTOMER_DICTIONARY_KINDS = [\n 'statuses',\n 'sources',\n 'lifecycle-stages',\n 'address-types',\n 'activity-types',\n 'deal-statuses',\n 'pipeline-stages',\n 'job-titles',\n 'industries',\n 'temperature',\n 'renewal-quarters',\n 'person-company-roles',\n] as const\n\nexport type CustomerDictionaryKind = typeof CUSTOMER_DICTIONARY_KINDS[number]\nexport type CustomerDictionaryDisplayEntry = DictionaryDisplayEntry\nexport type CustomerDictionaryMap = DictionaryMap\n\nexport const CUSTOMER_DICTIONARIES_MANAGE_HREF = '/backend/config/customers'\n\nexport function getCustomerDictionarySettingsSectionId(kind: CustomerDictionaryKind) {\n return `customer-dictionary-${kind}`\n}\n\nexport function getCustomerDictionaryManageHref(kind: CustomerDictionaryKind) {\n return `${CUSTOMER_DICTIONARIES_MANAGE_HREF}#${getCustomerDictionarySettingsSectionId(kind)}`\n}\n\nexport function createEmptyCustomerDictionaryMaps(): Record<CustomerDictionaryKind, CustomerDictionaryMap> {\n return CUSTOMER_DICTIONARY_KINDS.reduce<Record<CustomerDictionaryKind, CustomerDictionaryMap>>(\n (acc, kind) => {\n acc[kind] = {}\n return acc\n },\n {} as Record<CustomerDictionaryKind, CustomerDictionaryMap>,\n )\n}\n\nexport {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n DictionaryValue,\n createDictionaryMap,\n normalizeDictionaryEntries,\n renderDictionaryColor,\n renderDictionaryIcon,\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,MAAM,oCAAoC;AAE1C,SAAS,uCAAuC,MAA8B;AACnF,SAAO,uBAAuB,IAAI;AACpC;AAEO,SAAS,gCAAgC,MAA8B;AAC5E,SAAO,GAAG,iCAAiC,IAAI,uCAAuC,IAAI,CAAC;AAC7F;AAEO,SAAS,oCAA2F;AACzG,SAAO,0BAA0B;AAAA,IAC/B,CAAC,KAAK,SAAS;AACb,UAAI,IAAI,IAAI,CAAC;AACb,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6317.1.b3be10ab84",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -246,16 +246,16 @@
|
|
|
246
246
|
"zod": "^4.4.3"
|
|
247
247
|
},
|
|
248
248
|
"peerDependencies": {
|
|
249
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
250
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
251
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
249
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6317.1.b3be10ab84",
|
|
250
|
+
"@open-mercato/shared": "0.6.6-develop.6317.1.b3be10ab84",
|
|
251
|
+
"@open-mercato/ui": "0.6.6-develop.6317.1.b3be10ab84",
|
|
252
252
|
"react": "^19.0.0",
|
|
253
253
|
"react-dom": "^19.0.0"
|
|
254
254
|
},
|
|
255
255
|
"devDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6317.1.b3be10ab84",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6317.1.b3be10ab84",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6317.1.b3be10ab84",
|
|
259
259
|
"@testing-library/dom": "^10.4.1",
|
|
260
260
|
"@testing-library/jest-dom": "^6.9.1",
|
|
261
261
|
"@testing-library/react": "^16.3.1",
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for customers AI tool packs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the catalog precedent (`packages/core/src/modules/catalog/ai-tools/_shared.ts`):
|
|
5
|
+
* the company and people packs previously carried byte-for-byte-identical copies
|
|
6
|
+
* of the date-to-ISO helper, the `resolveEm` / `buildScope` accessors, the
|
|
7
|
+
* list-row → summary mapper, and the related-records builder
|
|
8
|
+
* (addresses / activities / notes / tasks / interactions / tags / deals, plus a
|
|
9
|
+
* companies-only `people` mapper). Centralizing them here gives both packs one
|
|
10
|
+
* source of truth so a change to the related-records output shape no longer has
|
|
11
|
+
* to be applied — and kept in sync — in two places.
|
|
12
|
+
*
|
|
13
|
+
* This is a pure internal refactor: tool names, input schemas, `requiredFeatures`,
|
|
14
|
+
* and emitted output shapes stay identical.
|
|
15
|
+
*/
|
|
16
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
17
|
+
import type { AiToolExecutionContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
|
|
18
|
+
import type { CustomersToolContext } from './types'
|
|
19
|
+
|
|
20
|
+
export function toIso(value: unknown): string | null {
|
|
21
|
+
if (!value) return null
|
|
22
|
+
const dt = value instanceof Date ? value : new Date(String(value))
|
|
23
|
+
if (Number.isNaN(dt.getTime())) return null
|
|
24
|
+
return dt.toISOString()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {
|
|
28
|
+
return ctx.container.resolve<EntityManager>('em')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {
|
|
32
|
+
return {
|
|
33
|
+
tenantId,
|
|
34
|
+
organizationId: ctx.organizationId,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* -------------------------------------------------------------------------- */
|
|
39
|
+
/* List-row summary mapper */
|
|
40
|
+
/* -------------------------------------------------------------------------- */
|
|
41
|
+
|
|
42
|
+
export type CustomerListApiItemBase = {
|
|
43
|
+
id?: string
|
|
44
|
+
display_name?: string | null
|
|
45
|
+
displayName?: string | null
|
|
46
|
+
primary_email?: string | null
|
|
47
|
+
primaryEmail?: string | null
|
|
48
|
+
primary_phone?: string | null
|
|
49
|
+
primaryPhone?: string | null
|
|
50
|
+
status?: string | null
|
|
51
|
+
lifecycle_stage?: string | null
|
|
52
|
+
lifecycleStage?: string | null
|
|
53
|
+
source?: string | null
|
|
54
|
+
owner_user_id?: string | null
|
|
55
|
+
ownerUserId?: string | null
|
|
56
|
+
organization_id?: string | null
|
|
57
|
+
organizationId?: string | null
|
|
58
|
+
tenant_id?: string | null
|
|
59
|
+
tenantId?: string | null
|
|
60
|
+
created_at?: string | null
|
|
61
|
+
createdAt?: string | null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type CustomerListSummary = {
|
|
65
|
+
id: string | undefined
|
|
66
|
+
displayName: string | null
|
|
67
|
+
primaryEmail: string | null
|
|
68
|
+
primaryPhone: string | null
|
|
69
|
+
status: string | null
|
|
70
|
+
lifecycleStage: string | null
|
|
71
|
+
source: string | null
|
|
72
|
+
ownerUserId: string | null
|
|
73
|
+
organizationId: string | null
|
|
74
|
+
tenantId: string | null
|
|
75
|
+
createdAt: string | null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function toCustomerListSummary(row: CustomerListApiItemBase): CustomerListSummary {
|
|
79
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null
|
|
80
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
|
|
81
|
+
return {
|
|
82
|
+
id: row.id,
|
|
83
|
+
displayName: row.display_name ?? row.displayName ?? null,
|
|
84
|
+
primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
|
|
85
|
+
primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
|
|
86
|
+
status: row.status ?? null,
|
|
87
|
+
lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
|
|
88
|
+
source: row.source ?? null,
|
|
89
|
+
ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
|
|
90
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
91
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
92
|
+
createdAt,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* -------------------------------------------------------------------------- */
|
|
97
|
+
/* Related-records mappers */
|
|
98
|
+
/* -------------------------------------------------------------------------- */
|
|
99
|
+
|
|
100
|
+
function asRows(value: unknown): Array<Record<string, unknown>> {
|
|
101
|
+
return Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function mapAddresses(rows: Array<Record<string, unknown>>) {
|
|
105
|
+
return rows.map((address) => ({
|
|
106
|
+
id: address.id,
|
|
107
|
+
name: address.name ?? null,
|
|
108
|
+
purpose: address.purpose ?? null,
|
|
109
|
+
addressLine1: address.addressLine1 ?? null,
|
|
110
|
+
addressLine2: address.addressLine2 ?? null,
|
|
111
|
+
city: address.city ?? null,
|
|
112
|
+
region: address.region ?? null,
|
|
113
|
+
postalCode: address.postalCode ?? null,
|
|
114
|
+
country: address.country ?? null,
|
|
115
|
+
isPrimary: !!address.isPrimary,
|
|
116
|
+
}))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function mapActivities(rows: Array<Record<string, unknown>>) {
|
|
120
|
+
return rows.map((activity) => ({
|
|
121
|
+
id: activity.id,
|
|
122
|
+
activityType: activity.activityType,
|
|
123
|
+
subject: activity.subject ?? null,
|
|
124
|
+
body: activity.body ?? null,
|
|
125
|
+
occurredAt: toIso(activity.occurredAt),
|
|
126
|
+
createdAt: toIso(activity.createdAt),
|
|
127
|
+
}))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function mapNotes(rows: Array<Record<string, unknown>>) {
|
|
131
|
+
return rows.map((comment) => ({
|
|
132
|
+
id: comment.id,
|
|
133
|
+
body: comment.body,
|
|
134
|
+
authorUserId: comment.authorUserId ?? null,
|
|
135
|
+
createdAt: toIso(comment.createdAt),
|
|
136
|
+
}))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function mapTasks(rows: Array<Record<string, unknown>>) {
|
|
140
|
+
return rows.map((task) => ({
|
|
141
|
+
id: task.id,
|
|
142
|
+
todoId: task.todoId ?? task.id,
|
|
143
|
+
todoSource: task.todoSource ?? null,
|
|
144
|
+
createdAt: toIso(task.createdAt),
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function mapInteractions(rows: Array<Record<string, unknown>>) {
|
|
149
|
+
return rows.map((interaction) => ({
|
|
150
|
+
id: interaction.id,
|
|
151
|
+
interactionType: interaction.interactionType,
|
|
152
|
+
title: interaction.title ?? null,
|
|
153
|
+
status: interaction.status,
|
|
154
|
+
scheduledAt: toIso(interaction.scheduledAt),
|
|
155
|
+
occurredAt: toIso(interaction.occurredAt),
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type RelatedTag = { id: string; slug: string; label: string; color: string | null }
|
|
160
|
+
|
|
161
|
+
export function mapTags(rows: Array<Record<string, unknown>>): RelatedTag[] {
|
|
162
|
+
return rows
|
|
163
|
+
.map((tag) => {
|
|
164
|
+
if (!tag || typeof tag !== 'object') return null
|
|
165
|
+
const id = typeof tag.id === 'string' ? tag.id : null
|
|
166
|
+
const label = typeof tag.label === 'string' ? tag.label : null
|
|
167
|
+
if (!id || !label) return null
|
|
168
|
+
const slug = typeof tag.slug === 'string' ? tag.slug : label
|
|
169
|
+
const color = typeof tag.color === 'string' ? tag.color : null
|
|
170
|
+
return { id, slug, label, color }
|
|
171
|
+
})
|
|
172
|
+
.filter((entry): entry is RelatedTag => entry !== null)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type RelatedDeal = {
|
|
176
|
+
id: string
|
|
177
|
+
title: string
|
|
178
|
+
status: string | null
|
|
179
|
+
pipelineStageId: string | null
|
|
180
|
+
valueAmount: string | null
|
|
181
|
+
valueCurrency: string | null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function mapDeals(rows: Array<Record<string, unknown>>): RelatedDeal[] {
|
|
185
|
+
return rows
|
|
186
|
+
.map((deal) => {
|
|
187
|
+
if (!deal || typeof deal !== 'object') return null
|
|
188
|
+
const id = typeof deal.id === 'string' ? deal.id : null
|
|
189
|
+
if (!id) return null
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
title: typeof deal.title === 'string' ? deal.title : '',
|
|
193
|
+
status: typeof deal.status === 'string' ? deal.status : null,
|
|
194
|
+
pipelineStageId: typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
|
|
195
|
+
valueAmount:
|
|
196
|
+
typeof deal.valueAmount === 'string'
|
|
197
|
+
? deal.valueAmount
|
|
198
|
+
: deal.valueAmount === null || deal.valueAmount === undefined
|
|
199
|
+
? null
|
|
200
|
+
: String(deal.valueAmount),
|
|
201
|
+
valueCurrency: typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
.filter((value): value is RelatedDeal => value !== null)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export type RelatedPerson = {
|
|
208
|
+
id: string
|
|
209
|
+
displayName: string
|
|
210
|
+
primaryEmail: string | null
|
|
211
|
+
primaryPhone: string | null
|
|
212
|
+
jobTitle: string | null
|
|
213
|
+
department: string | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function mapPeople(rows: Array<Record<string, unknown>>): RelatedPerson[] {
|
|
217
|
+
return rows
|
|
218
|
+
.map((person) => {
|
|
219
|
+
if (!person || typeof person !== 'object') return null
|
|
220
|
+
const id = typeof person.id === 'string' ? person.id : null
|
|
221
|
+
const displayName = typeof person.displayName === 'string' ? person.displayName : null
|
|
222
|
+
if (!id || !displayName) return null
|
|
223
|
+
return {
|
|
224
|
+
id,
|
|
225
|
+
displayName,
|
|
226
|
+
primaryEmail: typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
|
|
227
|
+
primaryPhone: typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
|
|
228
|
+
jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
|
|
229
|
+
department: typeof person.department === 'string' ? person.department : null,
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
.filter((value): value is RelatedPerson => value !== null)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type CustomerRelatedRecords = {
|
|
236
|
+
addresses: ReturnType<typeof mapAddresses>
|
|
237
|
+
activities: ReturnType<typeof mapActivities>
|
|
238
|
+
notes: ReturnType<typeof mapNotes>
|
|
239
|
+
tasks: ReturnType<typeof mapTasks>
|
|
240
|
+
interactions: ReturnType<typeof mapInteractions>
|
|
241
|
+
tags: RelatedTag[]
|
|
242
|
+
deals: RelatedDeal[]
|
|
243
|
+
people?: RelatedPerson[]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Builds the related-records block from a customers detail API payload
|
|
248
|
+
* (`addresses`, `activities`, `comments`, `todos`, `interactions`, `tags`,
|
|
249
|
+
* `deals`, and — for companies — `people`). The key order matches the
|
|
250
|
+
* pre-refactor literals so the emitted shape is unchanged. Pass
|
|
251
|
+
* `includePeople: true` to add the companies-only `people` collection.
|
|
252
|
+
*/
|
|
253
|
+
export function buildRelatedRecords(
|
|
254
|
+
data: Record<string, unknown>,
|
|
255
|
+
options: { includePeople?: boolean } = {},
|
|
256
|
+
): CustomerRelatedRecords {
|
|
257
|
+
const related: CustomerRelatedRecords = {
|
|
258
|
+
addresses: mapAddresses(asRows(data.addresses)),
|
|
259
|
+
activities: mapActivities(asRows(data.activities)),
|
|
260
|
+
notes: mapNotes(asRows(data.comments)),
|
|
261
|
+
tasks: mapTasks(asRows(data.todos)),
|
|
262
|
+
interactions: mapInteractions(asRows(data.interactions)),
|
|
263
|
+
tags: mapTags(asRows(data.tags)),
|
|
264
|
+
deals: mapDeals(asRows(data.deals)),
|
|
265
|
+
}
|
|
266
|
+
if (options.includePeople) {
|
|
267
|
+
related.people = mapPeople(asRows(data.people))
|
|
268
|
+
}
|
|
269
|
+
return related
|
|
270
|
+
}
|
|
@@ -19,6 +19,12 @@ import {
|
|
|
19
19
|
type AiToolExecutionContext,
|
|
20
20
|
} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
|
|
21
21
|
import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
|
|
22
|
+
import {
|
|
23
|
+
buildRelatedRecords,
|
|
24
|
+
toCustomerListSummary,
|
|
25
|
+
toIso,
|
|
26
|
+
type CustomerRelatedRecords,
|
|
27
|
+
} from './_shared'
|
|
22
28
|
|
|
23
29
|
const listCompaniesInput = z
|
|
24
30
|
.object({
|
|
@@ -108,27 +114,13 @@ const listCompaniesTool = defineApiBackedAiTool<
|
|
|
108
114
|
const data = (response.data ?? {}) as ListCompaniesApiResponse
|
|
109
115
|
const rawItems: ListCompaniesApiItem[] = Array.isArray(data.items) ? data.items : []
|
|
110
116
|
return {
|
|
111
|
-
items: rawItems.map((row) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
|
|
119
|
-
status: row.status ?? null,
|
|
120
|
-
lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
|
|
121
|
-
source: row.source ?? null,
|
|
122
|
-
ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
|
|
123
|
-
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
124
|
-
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
125
|
-
domain: row.domain ?? null,
|
|
126
|
-
websiteUrl: row.website_url ?? row.websiteUrl ?? null,
|
|
127
|
-
industry: row.industry ?? null,
|
|
128
|
-
sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
|
|
129
|
-
createdAt,
|
|
130
|
-
}
|
|
131
|
-
}),
|
|
117
|
+
items: rawItems.map((row) => ({
|
|
118
|
+
...toCustomerListSummary(row),
|
|
119
|
+
domain: row.domain ?? null,
|
|
120
|
+
websiteUrl: row.website_url ?? row.websiteUrl ?? null,
|
|
121
|
+
industry: row.industry ?? null,
|
|
122
|
+
sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
|
|
123
|
+
})),
|
|
132
124
|
total: typeof data.total === 'number' ? data.total : 0,
|
|
133
125
|
limit,
|
|
134
126
|
offset,
|
|
@@ -146,13 +138,6 @@ const getCompanyInput = z.object({
|
|
|
146
138
|
|
|
147
139
|
type GetCompanyInput = z.infer<typeof getCompanyInput>
|
|
148
140
|
|
|
149
|
-
function toIsoCompany(value: unknown): string | null {
|
|
150
|
-
if (!value) return null
|
|
151
|
-
const dt = value instanceof Date ? value : new Date(String(value))
|
|
152
|
-
if (Number.isNaN(dt.getTime())) return null
|
|
153
|
-
return dt.toISOString()
|
|
154
|
-
}
|
|
155
|
-
|
|
156
141
|
const getCompanyTool: CustomersAiToolDefinition = {
|
|
157
142
|
name: 'customers.get_company',
|
|
158
143
|
displayName: 'Get company',
|
|
@@ -193,134 +178,9 @@ const getCompanyTool: CustomersAiToolDefinition = {
|
|
|
193
178
|
const profileRow = (data.profile ?? null) as Record<string, unknown> | null
|
|
194
179
|
const customFields = (data.customFields ?? {}) as Record<string, unknown>
|
|
195
180
|
|
|
196
|
-
let related:
|
|
181
|
+
let related: CustomerRelatedRecords | null = null
|
|
197
182
|
if (includeRelated) {
|
|
198
|
-
|
|
199
|
-
const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []
|
|
200
|
-
const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []
|
|
201
|
-
const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []
|
|
202
|
-
const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []
|
|
203
|
-
const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []
|
|
204
|
-
const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []
|
|
205
|
-
const peopleRows = Array.isArray(data.people) ? (data.people as Array<Record<string, unknown>>) : []
|
|
206
|
-
related = {
|
|
207
|
-
addresses: addresses.map((address) => ({
|
|
208
|
-
id: address.id,
|
|
209
|
-
name: address.name ?? null,
|
|
210
|
-
purpose: address.purpose ?? null,
|
|
211
|
-
addressLine1: address.addressLine1 ?? null,
|
|
212
|
-
addressLine2: address.addressLine2 ?? null,
|
|
213
|
-
city: address.city ?? null,
|
|
214
|
-
region: address.region ?? null,
|
|
215
|
-
postalCode: address.postalCode ?? null,
|
|
216
|
-
country: address.country ?? null,
|
|
217
|
-
isPrimary: !!address.isPrimary,
|
|
218
|
-
})),
|
|
219
|
-
activities: activities.map((activity) => ({
|
|
220
|
-
id: activity.id,
|
|
221
|
-
activityType: activity.activityType,
|
|
222
|
-
subject: activity.subject ?? null,
|
|
223
|
-
body: activity.body ?? null,
|
|
224
|
-
occurredAt: toIsoCompany(activity.occurredAt),
|
|
225
|
-
createdAt: toIsoCompany(activity.createdAt),
|
|
226
|
-
})),
|
|
227
|
-
notes: notes.map((comment) => ({
|
|
228
|
-
id: comment.id,
|
|
229
|
-
body: comment.body,
|
|
230
|
-
authorUserId: comment.authorUserId ?? null,
|
|
231
|
-
createdAt: toIsoCompany(comment.createdAt),
|
|
232
|
-
})),
|
|
233
|
-
tasks: todos.map((task) => ({
|
|
234
|
-
id: task.id,
|
|
235
|
-
todoId: task.todoId ?? task.id,
|
|
236
|
-
todoSource: task.todoSource ?? null,
|
|
237
|
-
createdAt: toIsoCompany(task.createdAt),
|
|
238
|
-
})),
|
|
239
|
-
interactions: interactions.map((interaction) => ({
|
|
240
|
-
id: interaction.id,
|
|
241
|
-
interactionType: interaction.interactionType,
|
|
242
|
-
title: interaction.title ?? null,
|
|
243
|
-
status: interaction.status,
|
|
244
|
-
scheduledAt: toIsoCompany(interaction.scheduledAt),
|
|
245
|
-
occurredAt: toIsoCompany(interaction.occurredAt),
|
|
246
|
-
})),
|
|
247
|
-
tags: tagsRows
|
|
248
|
-
.map((tag) => {
|
|
249
|
-
if (!tag || typeof tag !== 'object') return null
|
|
250
|
-
const id = typeof tag.id === 'string' ? tag.id : null
|
|
251
|
-
const label = typeof tag.label === 'string' ? tag.label : null
|
|
252
|
-
if (!id || !label) return null
|
|
253
|
-
const slug = typeof tag.slug === 'string' ? tag.slug : label
|
|
254
|
-
const color = typeof tag.color === 'string' ? tag.color : null
|
|
255
|
-
return { id, slug, label, color }
|
|
256
|
-
})
|
|
257
|
-
.filter(
|
|
258
|
-
(entry): entry is { id: string; slug: string; label: string; color: string | null } =>
|
|
259
|
-
entry !== null,
|
|
260
|
-
),
|
|
261
|
-
deals: dealsRows
|
|
262
|
-
.map((deal) => {
|
|
263
|
-
if (!deal || typeof deal !== 'object') return null
|
|
264
|
-
const id = typeof deal.id === 'string' ? deal.id : null
|
|
265
|
-
if (!id) return null
|
|
266
|
-
return {
|
|
267
|
-
id,
|
|
268
|
-
title: typeof deal.title === 'string' ? deal.title : '',
|
|
269
|
-
status: typeof deal.status === 'string' ? deal.status : null,
|
|
270
|
-
pipelineStageId:
|
|
271
|
-
typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
|
|
272
|
-
valueAmount:
|
|
273
|
-
typeof deal.valueAmount === 'string'
|
|
274
|
-
? deal.valueAmount
|
|
275
|
-
: deal.valueAmount === null || deal.valueAmount === undefined
|
|
276
|
-
? null
|
|
277
|
-
: String(deal.valueAmount),
|
|
278
|
-
valueCurrency:
|
|
279
|
-
typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
|
|
280
|
-
}
|
|
281
|
-
})
|
|
282
|
-
.filter(
|
|
283
|
-
(
|
|
284
|
-
value,
|
|
285
|
-
): value is {
|
|
286
|
-
id: string
|
|
287
|
-
title: string
|
|
288
|
-
status: string | null
|
|
289
|
-
pipelineStageId: string | null
|
|
290
|
-
valueAmount: string | null
|
|
291
|
-
valueCurrency: string | null
|
|
292
|
-
} => value !== null,
|
|
293
|
-
),
|
|
294
|
-
people: peopleRows
|
|
295
|
-
.map((person) => {
|
|
296
|
-
if (!person || typeof person !== 'object') return null
|
|
297
|
-
const id = typeof person.id === 'string' ? person.id : null
|
|
298
|
-
const displayName = typeof person.displayName === 'string' ? person.displayName : null
|
|
299
|
-
if (!id || !displayName) return null
|
|
300
|
-
return {
|
|
301
|
-
id,
|
|
302
|
-
displayName,
|
|
303
|
-
primaryEmail:
|
|
304
|
-
typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
|
|
305
|
-
primaryPhone:
|
|
306
|
-
typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
|
|
307
|
-
jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
|
|
308
|
-
department: typeof person.department === 'string' ? person.department : null,
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
.filter(
|
|
312
|
-
(
|
|
313
|
-
value,
|
|
314
|
-
): value is {
|
|
315
|
-
id: string
|
|
316
|
-
displayName: string
|
|
317
|
-
primaryEmail: string | null
|
|
318
|
-
primaryPhone: string | null
|
|
319
|
-
jobTitle: string | null
|
|
320
|
-
department: string | null
|
|
321
|
-
} => value !== null,
|
|
322
|
-
),
|
|
323
|
-
}
|
|
183
|
+
related = buildRelatedRecords(data, { includePeople: true })
|
|
324
184
|
}
|
|
325
185
|
return {
|
|
326
186
|
found: true as const,
|
|
@@ -336,8 +196,8 @@ const getCompanyTool: CustomersAiToolDefinition = {
|
|
|
336
196
|
ownerUserId: companyRow.ownerUserId ?? null,
|
|
337
197
|
organizationId: companyRow.organizationId ?? null,
|
|
338
198
|
tenantId: companyRow.tenantId ?? null,
|
|
339
|
-
createdAt:
|
|
340
|
-
updatedAt:
|
|
199
|
+
createdAt: toIso(companyRow.createdAt),
|
|
200
|
+
updatedAt: toIso(companyRow.updatedAt),
|
|
341
201
|
},
|
|
342
202
|
profile: profileRow
|
|
343
203
|
? {
|