@open-mercato/core 0.4.11-develop.1355.50152f3ee9 → 0.4.11-develop.1362.574a071900
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customers/api/companies/route.js +141 -3
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +52 -3
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +145 -3
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +195 -0
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +171 -6
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +100 -7
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +180 -7
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +7 -0
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +1 -0
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +81 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customers/api/companies/route.ts +151 -3
- package/src/modules/customers/api/deals/route.ts +54 -3
- package/src/modules/customers/api/people/route.ts +160 -3
- package/src/modules/customers/api/utils.ts +286 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +184 -9
- package/src/modules/customers/backend/customers/deals/page.tsx +127 -35
- package/src/modules/customers/backend/customers/people/page.tsx +191 -10
- package/src/modules/customers/commands/interactions.ts +7 -0
- package/src/modules/customers/components/detail/DealForm.tsx +1 -0
- package/src/modules/customers/i18n/de.json +12 -0
- package/src/modules/customers/i18n/en.json +15 -3
- package/src/modules/customers/i18n/es.json +12 -0
- package/src/modules/customers/i18n/pl.json +12 -0
- package/src/modules/query_index/lib/engine.ts +95 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.11-develop.
|
|
3
|
+
"version": "0.4.11-develop.1362.574a071900",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -230,10 +230,10 @@
|
|
|
230
230
|
"ts-pattern": "^5.0.0"
|
|
231
231
|
},
|
|
232
232
|
"peerDependencies": {
|
|
233
|
-
"@open-mercato/shared": "0.4.11-develop.
|
|
233
|
+
"@open-mercato/shared": "0.4.11-develop.1362.574a071900"
|
|
234
234
|
},
|
|
235
235
|
"devDependencies": {
|
|
236
|
-
"@open-mercato/shared": "0.4.11-develop.
|
|
236
|
+
"@open-mercato/shared": "0.4.11-develop.1362.574a071900",
|
|
237
237
|
"@testing-library/dom": "^10.4.1",
|
|
238
238
|
"@testing-library/jest-dom": "^6.9.1",
|
|
239
239
|
"@testing-library/react": "^16.3.1",
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
4
4
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
-
import { CustomerEntity } from '../../data/entities'
|
|
5
|
+
import { CustomerCompanyProfile, CustomerEntity } from '../../data/entities'
|
|
6
6
|
import { E } from '#generated/entities.ids.generated'
|
|
7
7
|
import { companyCreateSchema, companyUpdateSchema } from '../../data/validators'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyEntityIdRestriction,
|
|
11
|
+
consumeAdvancedFilterState,
|
|
12
|
+
findMatchingEntityIdsWithQueryEngine,
|
|
13
|
+
findMatchingEntityIdsBySearchTokensAcrossSources,
|
|
14
|
+
withScopedPayload,
|
|
15
|
+
} from '../utils'
|
|
10
16
|
import {
|
|
11
17
|
buildCustomFieldFiltersFromQuery,
|
|
12
18
|
extractAllCustomFieldEntries,
|
|
@@ -14,6 +20,8 @@ import {
|
|
|
14
20
|
} from '@open-mercato/shared/lib/crud/custom-fields'
|
|
15
21
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
16
22
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
23
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
24
|
+
import { mergeAdvancedFilters } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
17
25
|
import {
|
|
18
26
|
createCustomersCrudOpenApi,
|
|
19
27
|
createPagedListResponseSchema,
|
|
@@ -93,10 +101,67 @@ const crud = makeCrudRoute({
|
|
|
93
101
|
updatedAt: 'updated_at',
|
|
94
102
|
},
|
|
95
103
|
buildFilters: async (query: any, ctx) => {
|
|
104
|
+
const advancedQuery = { ...query }
|
|
105
|
+
const advancedFilterState = consumeAdvancedFilterState(query)
|
|
96
106
|
const filters: Record<string, any> = { kind: { $eq: 'company' } }
|
|
97
107
|
if (query.id) filters.id = { $eq: query.id }
|
|
98
108
|
if (query.search) {
|
|
99
|
-
|
|
109
|
+
const matchingIds = ctx
|
|
110
|
+
? await findMatchingEntityIdsBySearchTokensAcrossSources({
|
|
111
|
+
ctx,
|
|
112
|
+
query: query.search,
|
|
113
|
+
sources: [
|
|
114
|
+
{
|
|
115
|
+
entityType: E.customers.customer_entity,
|
|
116
|
+
fields: [
|
|
117
|
+
'display_name',
|
|
118
|
+
'primary_email',
|
|
119
|
+
'primary_phone',
|
|
120
|
+
'description',
|
|
121
|
+
'status',
|
|
122
|
+
'lifecycle_stage',
|
|
123
|
+
'source',
|
|
124
|
+
'next_interaction_name',
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
entityType: E.customers.customer_company_profile,
|
|
129
|
+
fields: [
|
|
130
|
+
'display_name',
|
|
131
|
+
'primary_email',
|
|
132
|
+
'primary_phone',
|
|
133
|
+
'description',
|
|
134
|
+
'status',
|
|
135
|
+
'lifecycle_stage',
|
|
136
|
+
'source',
|
|
137
|
+
'legal_name',
|
|
138
|
+
'brand_name',
|
|
139
|
+
'domain',
|
|
140
|
+
'website_url',
|
|
141
|
+
'industry',
|
|
142
|
+
'size_bucket',
|
|
143
|
+
'annual_revenue',
|
|
144
|
+
],
|
|
145
|
+
mapToEntityIds: {
|
|
146
|
+
table: 'customer_companies',
|
|
147
|
+
targetColumn: 'entity_id',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
})
|
|
152
|
+
: null
|
|
153
|
+
if (matchingIds !== null && matchingIds.length > 0) {
|
|
154
|
+
applyEntityIdRestriction(filters, matchingIds)
|
|
155
|
+
} else {
|
|
156
|
+
const searchPattern = `%${escapeLikePattern(query.search)}%`
|
|
157
|
+
filters.$or = [
|
|
158
|
+
{ display_name: { $ilike: searchPattern } },
|
|
159
|
+
{ primary_email: { $ilike: searchPattern } },
|
|
160
|
+
{ primary_phone: { $ilike: searchPattern } },
|
|
161
|
+
{ description: { $ilike: searchPattern } },
|
|
162
|
+
{ next_interaction_name: { $ilike: searchPattern } },
|
|
163
|
+
]
|
|
164
|
+
}
|
|
100
165
|
}
|
|
101
166
|
if (query.status) {
|
|
102
167
|
filters.status = { $eq: query.status }
|
|
@@ -166,6 +231,36 @@ const crud = makeCrudRoute({
|
|
|
166
231
|
// ignore custom field filter errors; fall back to base filters
|
|
167
232
|
}
|
|
168
233
|
}
|
|
234
|
+
if (ctx && advancedFilterState) {
|
|
235
|
+
const advancedFilters = mergeAdvancedFilters(
|
|
236
|
+
{ ...filters },
|
|
237
|
+
advancedQuery as Record<string, unknown>,
|
|
238
|
+
)
|
|
239
|
+
const matchedIds = await findMatchingEntityIdsWithQueryEngine({
|
|
240
|
+
ctx,
|
|
241
|
+
entityId: E.customers.customer_entity,
|
|
242
|
+
filters: advancedFilters,
|
|
243
|
+
customFieldSources: [
|
|
244
|
+
{
|
|
245
|
+
entityId: E.customers.customer_company_profile,
|
|
246
|
+
table: 'customer_companies',
|
|
247
|
+
alias: 'company_profile',
|
|
248
|
+
recordIdColumn: 'id',
|
|
249
|
+
join: { fromField: 'id', toField: 'entity_id' },
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
joins: [
|
|
253
|
+
{
|
|
254
|
+
alias: 'tag_assignments',
|
|
255
|
+
table: 'customer_tag_assignments',
|
|
256
|
+
from: { field: 'id' },
|
|
257
|
+
to: { field: 'entity_id' },
|
|
258
|
+
type: 'left',
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
})
|
|
262
|
+
applyEntityIdRestriction(filters, matchedIds)
|
|
263
|
+
}
|
|
169
264
|
return filters
|
|
170
265
|
},
|
|
171
266
|
customFieldSources: [
|
|
@@ -244,6 +339,59 @@ const crud = makeCrudRoute({
|
|
|
244
339
|
response: () => ({ ok: true }),
|
|
245
340
|
},
|
|
246
341
|
},
|
|
342
|
+
hooks: {
|
|
343
|
+
afterList: async (payload, ctx) => {
|
|
344
|
+
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
345
|
+
const ids = items
|
|
346
|
+
.map((item: unknown) => (item && typeof item === 'object' && typeof (item as Record<string, unknown>).id === 'string'
|
|
347
|
+
? (item as Record<string, unknown>).id as string
|
|
348
|
+
: null))
|
|
349
|
+
.filter((id: string | null): id is string => typeof id === 'string' && id.length > 0)
|
|
350
|
+
if (!ids.length) return
|
|
351
|
+
|
|
352
|
+
const where: Record<string, unknown> = {
|
|
353
|
+
entity: { $in: ids },
|
|
354
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
355
|
+
}
|
|
356
|
+
if (ctx.selectedOrganizationId) {
|
|
357
|
+
where.organizationId = ctx.selectedOrganizationId
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const profiles = await findWithDecryption(
|
|
361
|
+
ctx.container.resolve('em') as any,
|
|
362
|
+
CustomerCompanyProfile,
|
|
363
|
+
where as any,
|
|
364
|
+
{ populate: ['entity'] } as any,
|
|
365
|
+
{
|
|
366
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
367
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
368
|
+
},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
const profilesByEntityId = new Map<string, CustomerCompanyProfile>()
|
|
372
|
+
for (const profile of profiles) {
|
|
373
|
+
const entityId = typeof (profile as any)?.entity?.id === 'string' ? (profile as any).entity.id : null
|
|
374
|
+
if (entityId) profilesByEntityId.set(entityId, profile)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
payload.items = items.map((item: unknown) => {
|
|
378
|
+
if (!item || typeof item !== 'object') return item
|
|
379
|
+
const record = item as Record<string, unknown>
|
|
380
|
+
const profile = typeof record.id === 'string' ? profilesByEntityId.get(record.id) : undefined
|
|
381
|
+
if (!profile) return item
|
|
382
|
+
return {
|
|
383
|
+
...record,
|
|
384
|
+
legal_name: profile.legalName ?? null,
|
|
385
|
+
brand_name: profile.brandName ?? null,
|
|
386
|
+
domain: profile.domain ?? null,
|
|
387
|
+
website_url: profile.websiteUrl ?? null,
|
|
388
|
+
industry: profile.industry ?? null,
|
|
389
|
+
size_bucket: profile.sizeBucket ?? null,
|
|
390
|
+
annual_revenue: profile.annualRevenue ?? null,
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
},
|
|
394
|
+
},
|
|
247
395
|
})
|
|
248
396
|
|
|
249
397
|
const { POST, PUT, DELETE } = crud
|
|
@@ -6,7 +6,13 @@ import { CustomerDeal, CustomerDealPersonLink, CustomerDealCompanyLink } from '.
|
|
|
6
6
|
import { dealCreateSchema, dealUpdateSchema } from '../../data/validators'
|
|
7
7
|
import { E } from '#generated/entities.ids.generated'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyEntityIdRestriction,
|
|
11
|
+
consumeAdvancedFilterState,
|
|
12
|
+
findMatchingEntityIdsWithQueryEngine,
|
|
13
|
+
findMatchingEntityIdsBySearchTokensAcrossSources,
|
|
14
|
+
parseScopedCommandInput,
|
|
15
|
+
} from '../utils'
|
|
10
16
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
11
17
|
import {
|
|
12
18
|
createCustomersCrudOpenApi,
|
|
@@ -15,6 +21,7 @@ import {
|
|
|
15
21
|
} from '../openapi'
|
|
16
22
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
17
23
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
24
|
+
import { mergeAdvancedFilters } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
18
25
|
|
|
19
26
|
const rawBodySchema = z.object({}).passthrough()
|
|
20
27
|
|
|
@@ -122,10 +129,42 @@ const crud = makeCrudRoute<unknown, unknown, DealListQuery>({
|
|
|
122
129
|
title: 'title',
|
|
123
130
|
value: 'value_amount',
|
|
124
131
|
},
|
|
125
|
-
buildFilters: async (query: any) => {
|
|
132
|
+
buildFilters: async (query: any, ctx) => {
|
|
133
|
+
const advancedQuery = { ...query }
|
|
134
|
+
const advancedFilterState = consumeAdvancedFilterState(query)
|
|
126
135
|
const filters: Record<string, any> = {}
|
|
127
136
|
if (query.search) {
|
|
128
|
-
|
|
137
|
+
const matchingIds = ctx
|
|
138
|
+
? await findMatchingEntityIdsBySearchTokensAcrossSources({
|
|
139
|
+
ctx,
|
|
140
|
+
query: query.search,
|
|
141
|
+
sources: [
|
|
142
|
+
{
|
|
143
|
+
entityType: E.customers.customer_deal,
|
|
144
|
+
fields: [
|
|
145
|
+
'title',
|
|
146
|
+
'description',
|
|
147
|
+
'status',
|
|
148
|
+
'pipeline_stage',
|
|
149
|
+
'source',
|
|
150
|
+
'value_amount',
|
|
151
|
+
'value_currency',
|
|
152
|
+
'cf:competitive_risk',
|
|
153
|
+
'cf:implementation_complexity',
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
})
|
|
158
|
+
: null
|
|
159
|
+
if (matchingIds !== null && matchingIds.length > 0) {
|
|
160
|
+
applyEntityIdRestriction(filters, matchingIds)
|
|
161
|
+
} else {
|
|
162
|
+
const searchPattern = `%${escapeLikePattern(query.search)}%`
|
|
163
|
+
filters.$or = [
|
|
164
|
+
{ title: { $ilike: searchPattern } },
|
|
165
|
+
{ description: { $ilike: searchPattern } },
|
|
166
|
+
]
|
|
167
|
+
}
|
|
129
168
|
}
|
|
130
169
|
if (query.status) {
|
|
131
170
|
filters.status = { $eq: query.status }
|
|
@@ -139,6 +178,18 @@ const crud = makeCrudRoute<unknown, unknown, DealListQuery>({
|
|
|
139
178
|
if (query.pipelineStageId) {
|
|
140
179
|
filters.pipeline_stage_id = { $eq: query.pipelineStageId }
|
|
141
180
|
}
|
|
181
|
+
if (ctx && advancedFilterState) {
|
|
182
|
+
const advancedFilters = mergeAdvancedFilters(
|
|
183
|
+
{ ...filters },
|
|
184
|
+
advancedQuery as Record<string, unknown>,
|
|
185
|
+
)
|
|
186
|
+
const matchedIds = await findMatchingEntityIdsWithQueryEngine({
|
|
187
|
+
ctx,
|
|
188
|
+
entityId: E.customers.customer_deal,
|
|
189
|
+
filters: advancedFilters,
|
|
190
|
+
})
|
|
191
|
+
applyEntityIdRestriction(filters, matchedIds)
|
|
192
|
+
}
|
|
142
193
|
return filters
|
|
143
194
|
},
|
|
144
195
|
},
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
4
4
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
-
import { CustomerEntity } from '../../data/entities'
|
|
5
|
+
import { CustomerEntity, CustomerPersonProfile } from '../../data/entities'
|
|
6
6
|
import { E } from '#generated/entities.ids.generated'
|
|
7
7
|
import { personCreateSchema, personUpdateSchema } from '../../data/validators'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyEntityIdRestriction,
|
|
11
|
+
consumeAdvancedFilterState,
|
|
12
|
+
findMatchingEntityIdsWithQueryEngine,
|
|
13
|
+
findMatchingEntityIdsBySearchTokensAcrossSources,
|
|
14
|
+
withScopedPayload,
|
|
15
|
+
} from '../utils'
|
|
10
16
|
import { buildCustomFieldFiltersFromQuery, extractAllCustomFieldEntries, splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'
|
|
11
17
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
12
18
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
19
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
20
|
+
import { mergeAdvancedFilters } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
13
21
|
import {
|
|
14
22
|
createCustomersCrudOpenApi,
|
|
15
23
|
createPagedListResponseSchema,
|
|
@@ -90,10 +98,68 @@ const crud = makeCrudRoute({
|
|
|
90
98
|
updatedAt: 'updated_at',
|
|
91
99
|
},
|
|
92
100
|
buildFilters: async (query: any, ctx) => {
|
|
101
|
+
const advancedQuery = { ...query }
|
|
102
|
+
const advancedFilterState = consumeAdvancedFilterState(query)
|
|
93
103
|
const filters: Record<string, any> = { kind: { $eq: 'person' } }
|
|
94
104
|
if (query.id) filters.id = { $eq: query.id }
|
|
95
105
|
if (query.search) {
|
|
96
|
-
|
|
106
|
+
const matchingIds = ctx
|
|
107
|
+
? await findMatchingEntityIdsBySearchTokensAcrossSources({
|
|
108
|
+
ctx,
|
|
109
|
+
query: query.search,
|
|
110
|
+
sources: [
|
|
111
|
+
{
|
|
112
|
+
entityType: E.customers.customer_entity,
|
|
113
|
+
fields: [
|
|
114
|
+
'display_name',
|
|
115
|
+
'primary_email',
|
|
116
|
+
'primary_phone',
|
|
117
|
+
'description',
|
|
118
|
+
'status',
|
|
119
|
+
'lifecycle_stage',
|
|
120
|
+
'source',
|
|
121
|
+
'next_interaction_name',
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
entityType: E.customers.customer_person_profile,
|
|
126
|
+
fields: [
|
|
127
|
+
'display_name',
|
|
128
|
+
'primary_email',
|
|
129
|
+
'primary_phone',
|
|
130
|
+
'status',
|
|
131
|
+
'lifecycle_stage',
|
|
132
|
+
'source',
|
|
133
|
+
'first_name',
|
|
134
|
+
'last_name',
|
|
135
|
+
'preferred_name',
|
|
136
|
+
'job_title',
|
|
137
|
+
'department',
|
|
138
|
+
'seniority',
|
|
139
|
+
'timezone',
|
|
140
|
+
'linked_in_url',
|
|
141
|
+
'twitter_url',
|
|
142
|
+
],
|
|
143
|
+
mapToEntityIds: {
|
|
144
|
+
table: 'customer_people',
|
|
145
|
+
targetColumn: 'entity_id',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
})
|
|
150
|
+
: null
|
|
151
|
+
if (matchingIds !== null && matchingIds.length > 0) {
|
|
152
|
+
applyEntityIdRestriction(filters, matchingIds)
|
|
153
|
+
} else {
|
|
154
|
+
const searchPattern = `%${escapeLikePattern(query.search)}%`
|
|
155
|
+
filters.$or = [
|
|
156
|
+
{ display_name: { $ilike: searchPattern } },
|
|
157
|
+
{ primary_email: { $ilike: searchPattern } },
|
|
158
|
+
{ primary_phone: { $ilike: searchPattern } },
|
|
159
|
+
{ description: { $ilike: searchPattern } },
|
|
160
|
+
{ next_interaction_name: { $ilike: searchPattern } },
|
|
161
|
+
]
|
|
162
|
+
}
|
|
97
163
|
}
|
|
98
164
|
const email = typeof query.email === 'string' ? query.email.trim().toLowerCase() : ''
|
|
99
165
|
const emailStartsWith = typeof query.emailStartsWith === 'string' ? query.emailStartsWith.trim().toLowerCase() : ''
|
|
@@ -163,6 +229,36 @@ const crud = makeCrudRoute({
|
|
|
163
229
|
// ignore custom field filter errors; fall back to base filters
|
|
164
230
|
}
|
|
165
231
|
}
|
|
232
|
+
if (ctx && advancedFilterState) {
|
|
233
|
+
const advancedFilters = mergeAdvancedFilters(
|
|
234
|
+
{ ...filters },
|
|
235
|
+
advancedQuery as Record<string, unknown>,
|
|
236
|
+
)
|
|
237
|
+
const matchedIds = await findMatchingEntityIdsWithQueryEngine({
|
|
238
|
+
ctx,
|
|
239
|
+
entityId: E.customers.customer_entity,
|
|
240
|
+
filters: advancedFilters,
|
|
241
|
+
customFieldSources: [
|
|
242
|
+
{
|
|
243
|
+
entityId: E.customers.customer_person_profile,
|
|
244
|
+
table: 'customer_people',
|
|
245
|
+
alias: 'person_profile',
|
|
246
|
+
recordIdColumn: 'id',
|
|
247
|
+
join: { fromField: 'id', toField: 'entity_id' },
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
joins: [
|
|
251
|
+
{
|
|
252
|
+
alias: 'tag_assignments',
|
|
253
|
+
table: 'customer_tag_assignments',
|
|
254
|
+
from: { field: 'id' },
|
|
255
|
+
to: { field: 'entity_id' },
|
|
256
|
+
type: 'left',
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
})
|
|
260
|
+
applyEntityIdRestriction(filters, matchedIds)
|
|
261
|
+
}
|
|
166
262
|
return filters
|
|
167
263
|
},
|
|
168
264
|
customFieldSources: [
|
|
@@ -241,6 +337,67 @@ const crud = makeCrudRoute({
|
|
|
241
337
|
response: () => ({ ok: true }),
|
|
242
338
|
},
|
|
243
339
|
},
|
|
340
|
+
hooks: {
|
|
341
|
+
afterList: async (payload, ctx) => {
|
|
342
|
+
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
343
|
+
const ids = items
|
|
344
|
+
.map((item: unknown) => (
|
|
345
|
+
item && typeof item === 'object' && typeof (item as Record<string, unknown>).id === 'string'
|
|
346
|
+
? (item as Record<string, unknown>).id as string
|
|
347
|
+
: null
|
|
348
|
+
))
|
|
349
|
+
.filter((id: string | null): id is string => typeof id === 'string' && id.length > 0)
|
|
350
|
+
if (!ids.length) return
|
|
351
|
+
|
|
352
|
+
const where: Record<string, unknown> = {
|
|
353
|
+
entity: { $in: ids },
|
|
354
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
355
|
+
}
|
|
356
|
+
if (ctx.selectedOrganizationId) {
|
|
357
|
+
where.organizationId = ctx.selectedOrganizationId
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const profiles = await findWithDecryption(
|
|
361
|
+
ctx.container.resolve('em') as any,
|
|
362
|
+
CustomerPersonProfile,
|
|
363
|
+
where as any,
|
|
364
|
+
{ populate: ['entity', 'company'] } as any,
|
|
365
|
+
{
|
|
366
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
367
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
368
|
+
},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
const profilesByEntityId = new Map<string, CustomerPersonProfile>()
|
|
372
|
+
for (const profile of profiles) {
|
|
373
|
+
const entityId = typeof (profile as any)?.entity?.id === 'string' ? (profile as any).entity.id : null
|
|
374
|
+
if (entityId) profilesByEntityId.set(entityId, profile)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
payload.items = items.map((item: unknown) => {
|
|
378
|
+
if (!item || typeof item !== 'object') return item
|
|
379
|
+
const record = item as Record<string, unknown>
|
|
380
|
+
const profile = typeof record.id === 'string' ? profilesByEntityId.get(record.id) : undefined
|
|
381
|
+
if (!profile) return item
|
|
382
|
+
return {
|
|
383
|
+
...record,
|
|
384
|
+
first_name: profile.firstName ?? null,
|
|
385
|
+
last_name: profile.lastName ?? null,
|
|
386
|
+
preferred_name: profile.preferredName ?? null,
|
|
387
|
+
job_title: profile.jobTitle ?? null,
|
|
388
|
+
department: profile.department ?? null,
|
|
389
|
+
seniority: profile.seniority ?? null,
|
|
390
|
+
timezone: profile.timezone ?? null,
|
|
391
|
+
linked_in_url: profile.linkedInUrl ?? null,
|
|
392
|
+
twitter_url: profile.twitterUrl ?? null,
|
|
393
|
+
company_entity_id:
|
|
394
|
+
profile.company && typeof profile.company === 'object'
|
|
395
|
+
? profile.company.id
|
|
396
|
+
: profile.company ?? null,
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
},
|
|
400
|
+
},
|
|
244
401
|
})
|
|
245
402
|
|
|
246
403
|
const { POST, PUT, DELETE } = crud
|