@open-mercato/core 0.4.8-develop-15259be22b → 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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'
|
|
2
|
+
import { InboxEmailPreview } from './components/messages/InboxEmailPreview'
|
|
3
|
+
|
|
4
|
+
export const messageObjectTypes: MessageObjectTypeDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
module: 'inbox_ops',
|
|
7
|
+
entityType: 'inbox_email',
|
|
8
|
+
messageTypes: ['inbox_ops.email', 'inbox_ops.reply'],
|
|
9
|
+
labelKey: 'inbox_ops.title',
|
|
10
|
+
icon: 'mail-open',
|
|
11
|
+
PreviewComponent: InboxEmailPreview,
|
|
12
|
+
actions: [
|
|
13
|
+
{
|
|
14
|
+
id: 'view',
|
|
15
|
+
labelKey: 'inbox_ops.view_in_messages',
|
|
16
|
+
variant: 'outline',
|
|
17
|
+
href: '/backend/inbox-ops',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
loadPreview: async (entityId, ctx) => {
|
|
21
|
+
try {
|
|
22
|
+
if (typeof window !== 'undefined') {
|
|
23
|
+
return { title: 'Inbox Email', subtitle: entityId }
|
|
24
|
+
}
|
|
25
|
+
const { loadInboxEmailPreview } = await import('./lib/messageObjectPreviews')
|
|
26
|
+
return loadInboxEmailPreview(entityId, ctx)
|
|
27
|
+
} catch {
|
|
28
|
+
return { title: 'Inbox Email', subtitle: entityId }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export default messageObjectTypes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MessageTypeDefinition } from '@open-mercato/shared/modules/messages/types'
|
|
2
|
+
import { InboxEmailContent } from './components/messages/InboxEmailContent'
|
|
3
|
+
|
|
4
|
+
export const messageTypes: MessageTypeDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
type: 'inbox_ops.email',
|
|
7
|
+
module: 'inbox_ops',
|
|
8
|
+
labelKey: 'inbox_ops.title',
|
|
9
|
+
icon: 'mail-open',
|
|
10
|
+
color: 'blue',
|
|
11
|
+
ui: {
|
|
12
|
+
listItemComponent: 'messages.default.listItem',
|
|
13
|
+
contentComponent: 'inbox_ops.email.content',
|
|
14
|
+
actionsComponent: 'messages.default.actions',
|
|
15
|
+
},
|
|
16
|
+
ContentComponent: InboxEmailContent,
|
|
17
|
+
allowReply: false,
|
|
18
|
+
allowForward: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'inbox_ops.reply',
|
|
22
|
+
module: 'inbox_ops',
|
|
23
|
+
labelKey: 'inbox_ops.action_type.draft_reply',
|
|
24
|
+
icon: 'reply',
|
|
25
|
+
color: 'green',
|
|
26
|
+
ui: {
|
|
27
|
+
listItemComponent: 'messages.default.listItem',
|
|
28
|
+
contentComponent: 'messages.default.content',
|
|
29
|
+
actionsComponent: 'messages.default.actions',
|
|
30
|
+
},
|
|
31
|
+
allowReply: true,
|
|
32
|
+
allowForward: true,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export default messageTypes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260303173020 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`create index "inbox_discrepancies_organization_id_tenant_id_index" on "inbox_discrepancies" ("organization_id", "tenant_id");`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async down(): Promise<void> {
|
|
10
|
+
this.addSql(`drop index "inbox_discrepancies_organization_id_tenant_id_index";`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260303173215 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "inbox_proposals" add column "category" text null;`);
|
|
7
|
+
this.addSql(`create index "inbox_proposals_organization_id_tenant_id_category_index" on "inbox_proposals" ("organization_id", "tenant_id", "category");`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override async down(): Promise<void> {
|
|
11
|
+
this.addSql(`drop index "inbox_proposals_organization_id_tenant_id_category_index";`);
|
|
12
|
+
this.addSql(`alter table "inbox_proposals" drop column "category";`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -22,7 +22,7 @@ export const searchConfig: SearchModuleConfig = {
|
|
|
22
22
|
enabled: true,
|
|
23
23
|
priority: 6,
|
|
24
24
|
fieldPolicy: {
|
|
25
|
-
searchable: ['summary'],
|
|
25
|
+
searchable: ['summary', 'category'],
|
|
26
26
|
excluded: ['metadata', 'participants'],
|
|
27
27
|
},
|
|
28
28
|
buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
|
|
@@ -35,17 +35,19 @@ export const searchConfig: SearchModuleConfig = {
|
|
|
35
35
|
fields: {
|
|
36
36
|
status: record.status,
|
|
37
37
|
confidence: record.confidence,
|
|
38
|
+
category: record.category,
|
|
38
39
|
detected_language: record.detected_language,
|
|
39
40
|
},
|
|
40
41
|
presenter: {
|
|
41
42
|
title: String(record.summary || 'Inbox Proposal').slice(0, 80),
|
|
42
|
-
subtitle: `Confidence: ${record.confidence} - Status: ${record.status}`,
|
|
43
|
+
subtitle: `Confidence: ${record.confidence} - Status: ${record.status}${record.category ? ` - Category: ${record.category}` : ''}`,
|
|
43
44
|
icon: 'inbox',
|
|
44
45
|
},
|
|
45
46
|
checksumSource: {
|
|
46
47
|
summary: record.summary,
|
|
47
48
|
status: record.status,
|
|
48
49
|
confidence: record.confidence,
|
|
50
|
+
category: record.category,
|
|
49
51
|
detectedLanguage: record.detected_language,
|
|
50
52
|
},
|
|
51
53
|
}
|
|
@@ -53,7 +55,7 @@ export const searchConfig: SearchModuleConfig = {
|
|
|
53
55
|
formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
|
|
54
56
|
return {
|
|
55
57
|
title: String(ctx.record.summary || 'Inbox Proposal').slice(0, 80),
|
|
56
|
-
subtitle: `Confidence: ${ctx.record.confidence} - Status: ${ctx.record.status}`,
|
|
58
|
+
subtitle: `Confidence: ${ctx.record.confidence} - Status: ${ctx.record.status}${ctx.record.category ? ` - Category: ${ctx.record.category}` : ''}`,
|
|
57
59
|
icon: 'inbox',
|
|
58
60
|
}
|
|
59
61
|
},
|
|
@@ -15,7 +15,12 @@ import { extractParticipantsFromThread } from '../lib/emailParser'
|
|
|
15
15
|
import { runExtractionWithConfiguredProvider } from '../lib/llmProvider'
|
|
16
16
|
import { safeParsePayloadJson } from '../lib/validation'
|
|
17
17
|
import { htmlToPlainText } from '../lib/htmlToPlainText'
|
|
18
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
18
19
|
import { emitInboxOpsEvent } from '../events'
|
|
20
|
+
import { createMessageRecordForEmail } from '../lib/messagesIntegration'
|
|
21
|
+
import { resolveCache, invalidateCountsCache } from '../lib/cache'
|
|
22
|
+
|
|
23
|
+
const SYSTEM_USER_ID = '00000000-0000-0000-0000-000000000000'
|
|
19
24
|
|
|
20
25
|
export const metadata = {
|
|
21
26
|
event: 'inbox_ops.email.received',
|
|
@@ -348,6 +353,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
|
|
|
348
353
|
id: proposalId,
|
|
349
354
|
inboxEmailId: email.id,
|
|
350
355
|
summary: extractionResult.summary,
|
|
356
|
+
category: extractionResult.category || null,
|
|
351
357
|
participants: enrichedParticipants,
|
|
352
358
|
confidence: String(extractionResult.confidence.toFixed(2)),
|
|
353
359
|
detectedLanguage: extractionResult.detectedLanguage || email.detectedLanguage,
|
|
@@ -366,6 +372,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
|
|
|
366
372
|
contactMatches,
|
|
367
373
|
extractionResult.proposedActions,
|
|
368
374
|
email.toAddress,
|
|
375
|
+
email.forwardedByAddress,
|
|
369
376
|
)
|
|
370
377
|
|
|
371
378
|
// Step 6d-2: Also generate create_contact for LLM-discovered unmatched participants
|
|
@@ -385,8 +392,15 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
|
|
|
385
392
|
email.toAddress,
|
|
386
393
|
)
|
|
387
394
|
|
|
395
|
+
// Step 6f: Deduplicate — remove company create_contact actions when a person
|
|
396
|
+
// action with the same companyName already exists (person creation auto-creates
|
|
397
|
+
// the company, so the separate company action would be redundant).
|
|
398
|
+
const dedupedProposedActions = deduplicateCompanyActions([
|
|
399
|
+
...autoContactActions, ...autoLinkActions, ...autoProductActions, ...extractionResult.proposedActions,
|
|
400
|
+
])
|
|
401
|
+
|
|
388
402
|
// Create actions — contact & product creation actions go first so they're executed before orders
|
|
389
|
-
const combinedProposedActions =
|
|
403
|
+
const combinedProposedActions = dedupedProposedActions
|
|
390
404
|
const allActions = [
|
|
391
405
|
...combinedProposedActions.map((action, index) => {
|
|
392
406
|
const parsedPayload = safeParsePayloadJson(action.payloadJson)
|
|
@@ -518,6 +532,39 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
|
|
|
518
532
|
|
|
519
533
|
await em.flush()
|
|
520
534
|
|
|
535
|
+
// Step 8b: Invalidate counts cache (new proposal affects counts)
|
|
536
|
+
try {
|
|
537
|
+
const cache = resolveCache(ctx)
|
|
538
|
+
await runWithCacheTenant(email.tenantId, () => invalidateCountsCache(cache, email.tenantId))
|
|
539
|
+
} catch (cacheErr) {
|
|
540
|
+
console.warn('[inbox_ops:extraction-worker] Cache invalidation failed (non-fatal):', cacheErr)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Step 8c: Register email as a message record (graceful degradation)
|
|
544
|
+
try {
|
|
545
|
+
await createMessageRecordForEmail(
|
|
546
|
+
{
|
|
547
|
+
id: email.id,
|
|
548
|
+
subject: email.subject,
|
|
549
|
+
cleanedText: email.cleanedText,
|
|
550
|
+
rawText: email.rawText,
|
|
551
|
+
forwardedByAddress: email.forwardedByAddress,
|
|
552
|
+
forwardedByName: email.forwardedByName,
|
|
553
|
+
status: email.status,
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
container: ctx,
|
|
557
|
+
scope: {
|
|
558
|
+
tenantId: email.tenantId,
|
|
559
|
+
organizationId: email.organizationId,
|
|
560
|
+
userId: SYSTEM_USER_ID,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
)
|
|
564
|
+
} catch (msgErr) {
|
|
565
|
+
console.error('[inbox_ops:extraction-worker] Messages integration failed (non-fatal):', msgErr)
|
|
566
|
+
}
|
|
567
|
+
|
|
521
568
|
// Step 9: Emit events
|
|
522
569
|
try {
|
|
523
570
|
await emitInboxOpsEvent('inbox_ops.email.processed', {
|
|
@@ -580,6 +627,7 @@ function buildContactActionsForUnmatchedParticipants(
|
|
|
580
627
|
contactMatches: { participant: { name: string; email: string }; match?: { contactId: string } | null }[],
|
|
581
628
|
existingActions: { actionType: string; payloadJson: string }[],
|
|
582
629
|
inboxAddress: string,
|
|
630
|
+
forwardedByAddress?: string,
|
|
583
631
|
): { actionType: 'create_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {
|
|
584
632
|
const alreadyProposed = new Set(
|
|
585
633
|
existingActions
|
|
@@ -592,14 +640,17 @@ function buildContactActionsForUnmatchedParticipants(
|
|
|
592
640
|
)
|
|
593
641
|
|
|
594
642
|
const inboxLower = (inboxAddress || '').toLowerCase()
|
|
643
|
+
const forwardedByLower = (forwardedByAddress || '').toLowerCase()
|
|
595
644
|
const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']
|
|
596
645
|
|
|
597
646
|
return contactMatches
|
|
598
647
|
.filter((m) => {
|
|
599
648
|
if (m.match?.contactId) return false
|
|
600
649
|
const emailLower = m.participant.email.toLowerCase()
|
|
650
|
+
if (!emailLower || !emailLower.includes('@')) return false
|
|
601
651
|
if (alreadyProposed.has(emailLower)) return false
|
|
602
652
|
if (emailLower === inboxLower) return false
|
|
653
|
+
if (forwardedByLower && emailLower === forwardedByLower) return false
|
|
603
654
|
return !systemPatterns.some((p) => emailLower.includes(p))
|
|
604
655
|
})
|
|
605
656
|
.map((m) => ({
|
|
@@ -844,6 +895,29 @@ function buildFullTextForExtraction(email: InboxEmail): string {
|
|
|
844
895
|
.trim()
|
|
845
896
|
}
|
|
846
897
|
|
|
898
|
+
function deduplicateCompanyActions<T extends { actionType: string; payloadJson: string }>(
|
|
899
|
+
actions: T[],
|
|
900
|
+
): T[] {
|
|
901
|
+
// Collect company names that will be auto-created by person actions via companyName field
|
|
902
|
+
const personCompanyNames = new Set<string>()
|
|
903
|
+
for (const action of actions) {
|
|
904
|
+
if (action.actionType !== 'create_contact') continue
|
|
905
|
+
const payload = safeParsePayloadJson(action.payloadJson)
|
|
906
|
+
if (payload.type === 'person' && typeof payload.companyName === 'string' && payload.companyName.trim()) {
|
|
907
|
+
personCompanyNames.add(payload.companyName.trim().toLowerCase())
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (personCompanyNames.size === 0) return actions
|
|
911
|
+
|
|
912
|
+
return actions.filter((action) => {
|
|
913
|
+
if (action.actionType !== 'create_contact') return true
|
|
914
|
+
const payload = safeParsePayloadJson(action.payloadJson)
|
|
915
|
+
if (payload.type !== 'company') return true
|
|
916
|
+
const companyName = typeof payload.name === 'string' ? payload.name.trim().toLowerCase() : ''
|
|
917
|
+
return !companyName || !personCompanyNames.has(companyName)
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
847
921
|
function findPartialNameMatch(name: string, map: Map<string, string>): string | undefined {
|
|
848
922
|
const lower = name.toLowerCase()
|
|
849
923
|
// Split on common separators (e.g. "Marco Rossi / Rossi Imports S.r.l.")
|