@open-mercato/core 0.4.11-develop.1309.4b37381a7a → 0.4.11-develop.1347.c693e6dfee

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.
Files changed (60) hide show
  1. package/dist/modules/customers/api/companies/[id]/route.js +3 -2
  2. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  3. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +59 -91
  4. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
  5. package/dist/modules/customers/api/interactions/tasks/route.js +115 -0
  6. package/dist/modules/customers/api/interactions/tasks/route.js.map +7 -0
  7. package/dist/modules/customers/api/people/[id]/route.js +3 -2
  8. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  9. package/dist/modules/customers/api/todos/route.js +14 -134
  10. package/dist/modules/customers/api/todos/route.js.map +2 -2
  11. package/dist/modules/customers/backend/customer-tasks/page.js +10 -0
  12. package/dist/modules/customers/backend/customer-tasks/page.js.map +7 -0
  13. package/dist/modules/customers/backend/customer-tasks/page.meta.js +25 -0
  14. package/dist/modules/customers/backend/customer-tasks/page.meta.js.map +7 -0
  15. package/dist/modules/customers/commands/interactions.js +40 -4
  16. package/dist/modules/customers/commands/interactions.js.map +2 -2
  17. package/dist/modules/customers/components/CustomerTodosTable.js +77 -47
  18. package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
  19. package/dist/modules/customers/components/detail/TasksSection.js +4 -4
  20. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  21. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +2 -1
  22. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  23. package/dist/modules/customers/components/detail/utils.js +3 -0
  24. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  25. package/dist/modules/customers/data/entities.js +2 -2
  26. package/dist/modules/customers/data/entities.js.map +2 -2
  27. package/dist/modules/customers/data/validators.js +2 -2
  28. package/dist/modules/customers/data/validators.js.map +2 -2
  29. package/dist/modules/customers/lib/interactionCompatibility.js +12 -2
  30. package/dist/modules/customers/lib/interactionCompatibility.js.map +2 -2
  31. package/dist/modules/customers/lib/todoCompatibility.js +167 -4
  32. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  33. package/dist/modules/customers/migrations/Migration20260401172819.js +45 -0
  34. package/dist/modules/customers/migrations/Migration20260401172819.js.map +7 -0
  35. package/dist/modules/customers/search.js +3 -2
  36. package/dist/modules/customers/search.js.map +2 -2
  37. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +10 -2
  38. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
  39. package/package.json +3 -3
  40. package/src/modules/customers/api/companies/[id]/route.ts +6 -5
  41. package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +69 -126
  42. package/src/modules/customers/api/interactions/tasks/route.ts +122 -0
  43. package/src/modules/customers/api/people/[id]/route.ts +3 -2
  44. package/src/modules/customers/api/todos/route.ts +13 -181
  45. package/src/modules/customers/backend/customer-tasks/page.meta.ts +23 -0
  46. package/src/modules/customers/backend/customer-tasks/page.tsx +12 -0
  47. package/src/modules/customers/commands/interactions.ts +50 -2
  48. package/src/modules/customers/components/CustomerTodosTable.tsx +91 -66
  49. package/src/modules/customers/components/detail/TasksSection.tsx +8 -8
  50. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +2 -1
  51. package/src/modules/customers/components/detail/types.ts +6 -0
  52. package/src/modules/customers/components/detail/utils.ts +3 -0
  53. package/src/modules/customers/data/entities.ts +2 -2
  54. package/src/modules/customers/data/validators.ts +2 -2
  55. package/src/modules/customers/lib/interactionCompatibility.ts +16 -0
  56. package/src/modules/customers/lib/todoCompatibility.ts +229 -10
  57. package/src/modules/customers/migrations/.snapshot-open-mercato.json +1 -1
  58. package/src/modules/customers/migrations/Migration20260401172819.ts +45 -0
  59. package/src/modules/customers/search.ts +3 -2
  60. package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +24 -23
@@ -1,5 +1,17 @@
1
1
  import { parseBooleanFromUnknown } from "@open-mercato/shared/lib/boolean";
2
- import { CUSTOMER_INTERACTION_TASK_SOURCE } from "./interactionCompatibility.js";
2
+ import {
3
+ CustomerInteraction,
4
+ CustomerTodoLink
5
+ } from "../data/entities.js";
6
+ import {
7
+ CUSTOMER_INTERACTION_TASK_SOURCE,
8
+ EXAMPLE_TODO_SOURCE,
9
+ resolveExampleIntegrationHref
10
+ } from "./interactionCompatibility.js";
11
+ import { hydrateCanonicalInteractions, loadCustomerSummaries } from "./interactionReadModel.js";
12
+ function resolveLegacyTodoSource(source) {
13
+ return typeof source === "string" && source.trim().length > 0 ? source : EXAMPLE_TODO_SOURCE;
14
+ }
3
15
  function extractTodoTitle(record) {
4
16
  const candidates = ["title", "subject", "name", "summary", "text", "description"];
5
17
  for (const key of candidates) {
@@ -40,6 +52,52 @@ function readCustomField(record, key) {
40
52
  }
41
53
  return void 0;
42
54
  }
55
+ function normalizeTodoSearch(value) {
56
+ if (typeof value !== "string") return null;
57
+ const trimmed = value.trim().toLowerCase();
58
+ return trimmed.length > 0 ? trimmed : null;
59
+ }
60
+ function sortTodoRows(rows) {
61
+ return [...rows].sort((left, right) => {
62
+ const leftTime = new Date(left.createdAt).getTime();
63
+ const rightTime = new Date(right.createdAt).getTime();
64
+ if (leftTime === rightTime) {
65
+ return right.id.localeCompare(left.id);
66
+ }
67
+ return rightTime - leftTime;
68
+ });
69
+ }
70
+ function filterTodoRows(rows, search) {
71
+ if (!search) return rows;
72
+ return rows.filter((row) => {
73
+ const haystack = [
74
+ row.customer.displayName,
75
+ row.todoTitle,
76
+ row.todoDescription
77
+ ].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase();
78
+ return haystack.includes(search);
79
+ });
80
+ }
81
+ function paginateTodoRows(rows, page, pageSize, exportAll) {
82
+ const total = rows.length;
83
+ if (exportAll) {
84
+ return {
85
+ items: rows,
86
+ total,
87
+ page: 1,
88
+ pageSize: total,
89
+ totalPages: 1
90
+ };
91
+ }
92
+ const start = (page - 1) * pageSize;
93
+ return {
94
+ items: rows.slice(start, start + pageSize),
95
+ total,
96
+ page,
97
+ pageSize,
98
+ totalPages: Math.max(1, Math.ceil(total / pageSize))
99
+ };
100
+ }
43
101
  async function resolveLegacyTodoDetails(queryEngine, links, tenantId, organizationIds) {
44
102
  const details = /* @__PURE__ */ new Map();
45
103
  if (!links.length || !tenantId) return details;
@@ -48,7 +106,7 @@ async function resolveLegacyTodoDetails(queryEngine, links, tenantId, organizati
48
106
  );
49
107
  const idsBySource = /* @__PURE__ */ new Map();
50
108
  for (const link of links) {
51
- const source = typeof link.todoSource === "string" && link.todoSource.trim().length > 0 ? link.todoSource : "example:todo";
109
+ const source = resolveLegacyTodoSource(link.todoSource);
52
110
  const id = typeof link.todoId === "string" && link.todoId.trim().length > 0 ? link.todoId : String(link.todoId ?? "");
53
111
  if (!id) continue;
54
112
  if (!idsBySource.has(source)) idsBySource.set(source, /* @__PURE__ */ new Set());
@@ -188,6 +246,102 @@ async function resolveLegacyTodoDetails(queryEngine, links, tenantId, organizati
188
246
  }
189
247
  return details;
190
248
  }
249
+ async function listLegacyTodoRows(em, queryEngine, tenantId, organizationIds, entityId) {
250
+ const where = { tenantId };
251
+ if (organizationIds && organizationIds.length > 0) {
252
+ where.organizationId = { $in: organizationIds };
253
+ }
254
+ if (entityId) {
255
+ where.entity = entityId;
256
+ }
257
+ const links = await em.find(CustomerTodoLink, where, {
258
+ populate: ["entity"],
259
+ orderBy: { createdAt: "desc" }
260
+ });
261
+ const details = await resolveLegacyTodoDetails(
262
+ queryEngine,
263
+ links,
264
+ tenantId,
265
+ organizationIds ?? []
266
+ );
267
+ return links.map((link) => {
268
+ const source = resolveLegacyTodoSource(link.todoSource);
269
+ return mapLegacyTodoLinkToRow(
270
+ link,
271
+ details.get(`${source}:${link.todoId}`) ?? null
272
+ );
273
+ });
274
+ }
275
+ async function listCanonicalTodoRows(em, container, auth, selectedOrganizationId, organizationIds, options) {
276
+ const where = {
277
+ tenantId: auth.tenantId,
278
+ interactionType: "task"
279
+ };
280
+ if (!options?.includeDeleted) {
281
+ where.deletedAt = null;
282
+ }
283
+ if (organizationIds && organizationIds.length > 0) {
284
+ where.organizationId = { $in: organizationIds };
285
+ }
286
+ if (options?.entityId) {
287
+ where.entity = options.entityId;
288
+ }
289
+ if (options?.source) {
290
+ where.source = Array.isArray(options.source) ? { $in: options.source } : options.source;
291
+ }
292
+ const interactions = await em.find(CustomerInteraction, where, {
293
+ orderBy: { createdAt: "desc" }
294
+ });
295
+ const activeInteractions = interactions.filter((interaction) => !interaction.deletedAt);
296
+ const groups = /* @__PURE__ */ new Map();
297
+ for (const interaction of activeInteractions) {
298
+ const organizationId = typeof interaction.organizationId === "string" && interaction.organizationId.trim().length > 0 ? interaction.organizationId : selectedOrganizationId ?? "";
299
+ const bucket = groups.get(organizationId);
300
+ if (bucket) {
301
+ bucket.push(interaction);
302
+ } else {
303
+ groups.set(organizationId, [interaction]);
304
+ }
305
+ }
306
+ const rowByInteractionId = /* @__PURE__ */ new Map();
307
+ for (const [groupOrganizationId, groupedInteractions] of groups.entries()) {
308
+ const scopedOrganizationId = groupOrganizationId.length > 0 ? groupOrganizationId : null;
309
+ const hydrated = await hydrateCanonicalInteractions({
310
+ em,
311
+ container,
312
+ auth: {
313
+ ...auth,
314
+ orgId: auth.orgId ?? null
315
+ },
316
+ selectedOrganizationId: scopedOrganizationId,
317
+ interactions: groupedInteractions
318
+ });
319
+ const customerIds = Array.from(
320
+ new Set(
321
+ hydrated.map((interaction) => interaction.entityId ?? null).filter((value) => !!value)
322
+ )
323
+ );
324
+ const customerSummaries = await loadCustomerSummaries(
325
+ em,
326
+ customerIds,
327
+ auth.tenantId,
328
+ scopedOrganizationId
329
+ );
330
+ for (const interaction of hydrated) {
331
+ rowByInteractionId.set(
332
+ interaction.id,
333
+ mapInteractionRecordToTodoRow(
334
+ interaction,
335
+ interaction.entityId ? customerSummaries.get(interaction.entityId) ?? null : null
336
+ )
337
+ );
338
+ }
339
+ }
340
+ return {
341
+ items: activeInteractions.map((interaction) => rowByInteractionId.get(interaction.id) ?? null).filter((row) => !!row),
342
+ bridgeIds: new Set(interactions.map((interaction) => interaction.id))
343
+ };
344
+ }
191
345
  function mapLegacyTodoLinkToRow(link, detail, customerOverride) {
192
346
  const entity = customerOverride ?? {
193
347
  id: typeof link.entity === "string" ? null : link.entity.id,
@@ -197,7 +351,7 @@ function mapLegacyTodoLinkToRow(link, detail, customerOverride) {
197
351
  return {
198
352
  id: link.id,
199
353
  todoId: link.todoId,
200
- todoSource: typeof link.todoSource === "string" && link.todoSource.trim().length > 0 ? link.todoSource : "example:todo",
354
+ todoSource: resolveLegacyTodoSource(link.todoSource),
201
355
  todoTitle: detail?.title ?? null,
202
356
  todoIsDone: detail?.isDone ?? null,
203
357
  todoPriority: detail?.priority ?? null,
@@ -209,6 +363,7 @@ function mapLegacyTodoLinkToRow(link, detail, customerOverride) {
209
363
  organizationId: link.organizationId,
210
364
  tenantId: link.tenantId,
211
365
  createdAt: link.createdAt.toISOString(),
366
+ _integrations: void 0,
212
367
  customer: entity
213
368
  };
214
369
  }
@@ -238,6 +393,8 @@ function mapInteractionRecordToTodoRow(interaction, customer, options) {
238
393
  organizationId: interaction.organizationId ?? "",
239
394
  tenantId: interaction.tenantId ?? "",
240
395
  createdAt: interaction.createdAt,
396
+ externalHref: resolveExampleIntegrationHref(interaction),
397
+ _integrations: interaction._integrations ?? void 0,
241
398
  customer: customer ?? {
242
399
  id: interaction.entityId ?? null,
243
400
  displayName: null,
@@ -246,8 +403,14 @@ function mapInteractionRecordToTodoRow(interaction, customer, options) {
246
403
  };
247
404
  }
248
405
  export {
406
+ filterTodoRows,
407
+ listCanonicalTodoRows,
408
+ listLegacyTodoRows,
249
409
  mapInteractionRecordToTodoRow,
250
410
  mapLegacyTodoLinkToRow,
251
- resolveLegacyTodoDetails
411
+ normalizeTodoSearch,
412
+ paginateTodoRows,
413
+ resolveLegacyTodoDetails,
414
+ sortTodoRows
252
415
  };
253
416
  //# sourceMappingURL=todoCompatibility.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/todoCompatibility.ts"],
4
- "sourcesContent": ["import type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport type { CustomerTodoLink } from '../data/entities'\nimport type { InteractionRecord } from './interactionCompatibility'\nimport { CUSTOMER_INTERACTION_TASK_SOURCE } from './interactionCompatibility'\n\nexport type CustomerTodoRow = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n todoIsDone: boolean | null\n todoPriority?: number | null\n todoSeverity?: string | null\n todoDescription?: string | null\n todoDueAt?: string | null\n todoCustomValues?: Record<string, unknown> | null\n todoOrganizationId: string | null\n organizationId: string\n tenantId: string\n createdAt: string\n customer: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nexport type LegacyTodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n customValues: Record<string, unknown> | null\n}\n\ntype CustomerSummary = {\n id: string | null\n displayName: string | null\n kind: string | null\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n const candidates = ['title', 'subject', 'name', 'summary', 'text', 'description']\n for (const key of candidates) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\nexport async function resolveLegacyTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n): Promise<Map<string, LegacyTodoDetail>> {\n const details = new Map<string, LegacyTodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrganizationIds = organizationIds.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source =\n typeof link.todoSource === 'string' && link.todoSource.trim().length > 0\n ? link.todoSource\n : 'example:todo'\n const id =\n typeof link.todoId === 'string' && link.todoId.trim().length > 0\n ? link.todoId\n : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrganizationIds.length > 0 ? scopedOrganizationIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId =\n typeof record.id === 'string' && record.id.trim().length > 0\n ? record.id\n : String(record.id ?? '')\n if (!rawId) continue\n\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n record.severity,\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const description = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n const organizationId = (() => {\n const candidates = [\n record.organization_id,\n record.organizationId,\n record['cf:organization_id'],\n record['cf_organization_id'],\n readCustomField(record, 'organization_id'),\n readCustomField(record, 'organizationId'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: string, value: unknown) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [key, value] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(key, value)\n }\n }\n\n details.set(`${source}:${rawId}`, {\n title: extractTodoTitle(record),\n isDone,\n priority,\n severity,\n description,\n dueAt,\n organizationId,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n })\n }\n } catch (err) {\n console.warn(`[customers.todoCompatibility] Failed to resolve details for source=\"${source}\"`, err)\n continue\n }\n }\n\n return details\n}\n\nexport function mapLegacyTodoLinkToRow(\n link: CustomerTodoLink,\n detail: LegacyTodoDetail | null,\n customerOverride?: CustomerSummary | null,\n): CustomerTodoRow {\n const entity = customerOverride ?? {\n id: typeof link.entity === 'string' ? null : link.entity.id,\n displayName: typeof link.entity === 'string' ? null : link.entity.displayName ?? null,\n kind: typeof link.entity === 'string' ? null : link.entity.kind ?? null,\n }\n\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource:\n typeof link.todoSource === 'string' && link.todoSource.trim().length > 0\n ? link.todoSource\n : 'example:todo',\n todoTitle: detail?.title ?? null,\n todoIsDone: detail?.isDone ?? null,\n todoPriority: detail?.priority ?? null,\n todoSeverity: detail?.severity ?? null,\n todoDescription: detail?.description ?? null,\n todoDueAt: detail?.dueAt ?? null,\n todoCustomValues: detail?.customValues ?? null,\n todoOrganizationId: detail?.organizationId ?? link.organizationId ?? null,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n createdAt: link.createdAt.toISOString(),\n customer: entity,\n }\n}\n\nexport function mapInteractionRecordToTodoRow(\n interaction: InteractionRecord,\n customer: CustomerSummary | null,\n options?: { rowId?: string | null; todoSource?: string | null },\n): CustomerTodoRow {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n if (interaction.priority !== undefined && customValues.priority === undefined) {\n customValues.priority = interaction.priority ?? null\n }\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id:\n typeof options?.rowId === 'string' && options.rowId.trim().length > 0\n ? options.rowId\n : interaction.id,\n todoId: interaction.id,\n todoSource:\n typeof options?.todoSource === 'string' && options.todoSource.trim().length > 0\n ? options.todoSource\n : CUSTOMER_INTERACTION_TASK_SOURCE,\n todoTitle: interaction.title ?? null,\n todoIsDone: interaction.status === 'done',\n todoPriority: interaction.priority ?? null,\n todoSeverity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length > 0\n ? customValues.severity.trim()\n : null,\n todoDescription: interaction.body ?? null,\n todoDueAt: interaction.scheduledAt ?? null,\n todoCustomValues: Object.keys(customValues).length > 0 ? customValues : null,\n todoOrganizationId: interaction.organizationId ?? null,\n organizationId: interaction.organizationId ?? '',\n tenantId: interaction.tenantId ?? '',\n createdAt: interaction.createdAt,\n customer: customer ?? {\n id: interaction.entityId ?? null,\n displayName: null,\n kind: null,\n },\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport {\n CustomerInteraction,\n CustomerTodoLink,\n} from '../data/entities'\nimport type { InteractionRecord } from './interactionCompatibility'\nimport {\n CUSTOMER_INTERACTION_TASK_SOURCE,\n EXAMPLE_TODO_SOURCE,\n resolveExampleIntegrationHref,\n} from './interactionCompatibility'\nimport { hydrateCanonicalInteractions, loadCustomerSummaries } from './interactionReadModel'\n\nexport type CustomerTodoRow = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n todoIsDone: boolean | null\n todoPriority?: number | null\n todoSeverity?: string | null\n todoDescription?: string | null\n todoDueAt?: string | null\n todoCustomValues?: Record<string, unknown> | null\n todoOrganizationId: string | null\n organizationId: string\n tenantId: string\n createdAt: string\n externalHref?: string | null\n _integrations?: Record<string, unknown>\n customer: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nexport type LegacyTodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n customValues: Record<string, unknown> | null\n}\n\ntype CustomerSummary = {\n id: string | null\n displayName: string | null\n kind: string | null\n}\n\ntype CustomersAuthLike = {\n tenantId: string | null\n orgId?: string | null\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}\n\ntype CustomersContainerLike = {\n resolve: (name: string) => unknown\n}\n\nexport type CanonicalTodoListResult = {\n items: CustomerTodoRow[]\n bridgeIds: Set<string>\n}\n\nfunction resolveLegacyTodoSource(source: string | null | undefined): string {\n return typeof source === 'string' && source.trim().length > 0\n ? source\n : EXAMPLE_TODO_SOURCE\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n const candidates = ['title', 'subject', 'name', 'summary', 'text', 'description']\n for (const key of candidates) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = new Date(trimmed)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\nexport function normalizeTodoSearch(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim().toLowerCase()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport function sortTodoRows(rows: CustomerTodoRow[]): CustomerTodoRow[] {\n return [...rows].sort((left, right) => {\n const leftTime = new Date(left.createdAt).getTime()\n const rightTime = new Date(right.createdAt).getTime()\n if (leftTime === rightTime) {\n return right.id.localeCompare(left.id)\n }\n return rightTime - leftTime\n })\n}\n\nexport function filterTodoRows(rows: CustomerTodoRow[], search: string | null): CustomerTodoRow[] {\n if (!search) return rows\n return rows.filter((row) => {\n const haystack = [\n row.customer.displayName,\n row.todoTitle,\n row.todoDescription,\n ]\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(search)\n })\n}\n\nexport function paginateTodoRows(\n rows: CustomerTodoRow[],\n page: number,\n pageSize: number,\n exportAll: boolean,\n): { items: CustomerTodoRow[]; total: number; page: number; pageSize: number; totalPages: number } {\n const total = rows.length\n if (exportAll) {\n return {\n items: rows,\n total,\n page: 1,\n pageSize: total,\n totalPages: 1,\n }\n }\n const start = (page - 1) * pageSize\n return {\n items: rows.slice(start, start + pageSize),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n }\n}\n\nexport async function resolveLegacyTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n): Promise<Map<string, LegacyTodoDetail>> {\n const details = new Map<string, LegacyTodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrganizationIds = organizationIds.filter(\n (value): value is string => typeof value === 'string' && value.trim().length > 0,\n )\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source = resolveLegacyTodoSource(link.todoSource)\n const id =\n typeof link.todoId === 'string' && link.todoId.trim().length > 0\n ? link.todoId\n : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrganizationIds.length > 0 ? scopedOrganizationIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId =\n typeof record.id === 'string' && record.id.trim().length > 0\n ? record.id\n : String(record.id ?? '')\n if (!rawId) continue\n\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n record.severity,\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const description = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n\n const organizationId = (() => {\n const candidates = [\n record.organization_id,\n record.organizationId,\n record['cf:organization_id'],\n record['cf_organization_id'],\n readCustomField(record, 'organization_id'),\n readCustomField(record, 'organizationId'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate.trim()\n }\n }\n return null\n })()\n\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: string, value: unknown) => {\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [key, value] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(key, value)\n }\n }\n\n details.set(`${source}:${rawId}`, {\n title: extractTodoTitle(record),\n isDone,\n priority,\n severity,\n description,\n dueAt,\n organizationId,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n })\n }\n } catch (err) {\n console.warn(`[customers.todoCompatibility] Failed to resolve details for source=\"${source}\"`, err)\n continue\n }\n }\n\n return details\n}\n\nexport async function listLegacyTodoRows(\n em: EntityManager,\n queryEngine: QueryEngine,\n tenantId: string,\n organizationIds: string[] | null,\n entityId: string | undefined,\n): Promise<CustomerTodoRow[]> {\n const where: Record<string, unknown> = { tenantId }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (entityId) {\n where.entity = entityId\n }\n\n const links = await em.find(CustomerTodoLink, where, {\n populate: ['entity'],\n orderBy: { createdAt: 'desc' },\n })\n const details = await resolveLegacyTodoDetails(\n queryEngine,\n links,\n tenantId,\n organizationIds ?? [],\n )\n\n return links.map((link) => {\n const source = resolveLegacyTodoSource(link.todoSource)\n return mapLegacyTodoLinkToRow(\n link,\n details.get(`${source}:${link.todoId}`) ?? null,\n )\n })\n}\n\nexport async function listCanonicalTodoRows(\n em: EntityManager,\n container: CustomersContainerLike,\n auth: CustomersAuthLike,\n selectedOrganizationId: string | null,\n organizationIds: string[] | null,\n options?: {\n entityId?: string\n includeDeleted?: boolean\n source?: string | string[] | null\n },\n): Promise<CanonicalTodoListResult> {\n const where: Record<string, unknown> = {\n tenantId: auth.tenantId,\n interactionType: 'task',\n }\n if (!options?.includeDeleted) {\n where.deletedAt = null\n }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (options?.entityId) {\n where.entity = options.entityId\n }\n if (options?.source) {\n where.source = Array.isArray(options.source) ? { $in: options.source } : options.source\n }\n\n const interactions = await em.find(CustomerInteraction, where, {\n orderBy: { createdAt: 'desc' },\n })\n const activeInteractions = interactions.filter((interaction) => !interaction.deletedAt)\n const groups = new Map<string, CustomerInteraction[]>()\n\n for (const interaction of activeInteractions) {\n const organizationId =\n typeof interaction.organizationId === 'string' && interaction.organizationId.trim().length > 0\n ? interaction.organizationId\n : selectedOrganizationId ?? ''\n const bucket = groups.get(organizationId)\n if (bucket) {\n bucket.push(interaction)\n } else {\n groups.set(organizationId, [interaction])\n }\n }\n\n const rowByInteractionId = new Map<string, CustomerTodoRow>()\n\n for (const [groupOrganizationId, groupedInteractions] of groups.entries()) {\n const scopedOrganizationId = groupOrganizationId.length > 0 ? groupOrganizationId : null\n const hydrated = await hydrateCanonicalInteractions({\n em,\n container,\n auth: {\n ...auth,\n orgId: auth.orgId ?? null,\n },\n selectedOrganizationId: scopedOrganizationId,\n interactions: groupedInteractions,\n })\n const customerIds = Array.from(\n new Set(\n hydrated\n .map((interaction) => interaction.entityId ?? null)\n .filter((value): value is string => !!value),\n ),\n )\n const customerSummaries = await loadCustomerSummaries(\n em,\n customerIds,\n auth.tenantId,\n scopedOrganizationId,\n )\n\n for (const interaction of hydrated) {\n rowByInteractionId.set(\n interaction.id,\n mapInteractionRecordToTodoRow(\n interaction,\n interaction.entityId ? customerSummaries.get(interaction.entityId) ?? null : null,\n ),\n )\n }\n }\n\n return {\n items: activeInteractions\n .map((interaction) => rowByInteractionId.get(interaction.id) ?? null)\n .filter((row): row is CustomerTodoRow => !!row),\n bridgeIds: new Set(interactions.map((interaction) => interaction.id)),\n }\n}\n\nexport function mapLegacyTodoLinkToRow(\n link: CustomerTodoLink,\n detail: LegacyTodoDetail | null,\n customerOverride?: CustomerSummary | null,\n): CustomerTodoRow {\n const entity = customerOverride ?? {\n id: typeof link.entity === 'string' ? null : link.entity.id,\n displayName: typeof link.entity === 'string' ? null : link.entity.displayName ?? null,\n kind: typeof link.entity === 'string' ? null : link.entity.kind ?? null,\n }\n\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource: resolveLegacyTodoSource(link.todoSource),\n todoTitle: detail?.title ?? null,\n todoIsDone: detail?.isDone ?? null,\n todoPriority: detail?.priority ?? null,\n todoSeverity: detail?.severity ?? null,\n todoDescription: detail?.description ?? null,\n todoDueAt: detail?.dueAt ?? null,\n todoCustomValues: detail?.customValues ?? null,\n todoOrganizationId: detail?.organizationId ?? link.organizationId ?? null,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n createdAt: link.createdAt.toISOString(),\n _integrations: undefined,\n customer: entity,\n }\n}\n\nexport function mapInteractionRecordToTodoRow(\n interaction: InteractionRecord,\n customer: CustomerSummary | null,\n options?: { rowId?: string | null; todoSource?: string | null },\n): CustomerTodoRow {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n if (interaction.priority !== undefined && customValues.priority === undefined) {\n customValues.priority = interaction.priority ?? null\n }\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id:\n typeof options?.rowId === 'string' && options.rowId.trim().length > 0\n ? options.rowId\n : interaction.id,\n todoId: interaction.id,\n todoSource:\n typeof options?.todoSource === 'string' && options.todoSource.trim().length > 0\n ? options.todoSource\n : CUSTOMER_INTERACTION_TASK_SOURCE,\n todoTitle: interaction.title ?? null,\n todoIsDone: interaction.status === 'done',\n todoPriority: interaction.priority ?? null,\n todoSeverity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length > 0\n ? customValues.severity.trim()\n : null,\n todoDescription: interaction.body ?? null,\n todoDueAt: interaction.scheduledAt ?? null,\n todoCustomValues: Object.keys(customValues).length > 0 ? customValues : null,\n todoOrganizationId: interaction.organizationId ?? null,\n organizationId: interaction.organizationId ?? '',\n tenantId: interaction.tenantId ?? '',\n createdAt: interaction.createdAt,\n externalHref: resolveExampleIntegrationHref(interaction),\n _integrations: interaction._integrations ?? undefined,\n customer: customer ?? {\n id: interaction.entityId ?? null,\n displayName: null,\n kind: null,\n },\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,45 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260401172819 extends Migration {
3
+ async up() {
4
+ this.addSql(`alter table "customer_todo_links" alter column "todo_source" set default 'customers:interaction';`);
5
+ this.addSql(`
6
+ update "role_acls" as ra
7
+ set
8
+ "features_json" = case
9
+ when ra."features_json" is null or jsonb_typeof(ra."features_json") <> 'array'
10
+ then '["customers.interactions.view"]'::jsonb
11
+ else ra."features_json" || '"customers.interactions.view"'::jsonb
12
+ end,
13
+ "updated_at" = now()
14
+ where ra."deleted_at" is null
15
+ and ra."features_json" is not null
16
+ and jsonb_typeof(ra."features_json") = 'array'
17
+ and ra."features_json" ? 'example.todos.view'
18
+ and ra."features_json" ? 'customers.activities.view'
19
+ and not (ra."features_json" ? 'customers.interactions.view');
20
+ `);
21
+ this.addSql(`
22
+ update "user_acls" as ua
23
+ set
24
+ "features_json" = case
25
+ when ua."features_json" is null or jsonb_typeof(ua."features_json") <> 'array'
26
+ then '["customers.interactions.view"]'::jsonb
27
+ else ua."features_json" || '"customers.interactions.view"'::jsonb
28
+ end,
29
+ "updated_at" = now()
30
+ where ua."deleted_at" is null
31
+ and ua."features_json" is not null
32
+ and jsonb_typeof(ua."features_json") = 'array'
33
+ and ua."features_json" ? 'example.todos.view'
34
+ and ua."features_json" ? 'customers.activities.view'
35
+ and not (ua."features_json" ? 'customers.interactions.view');
36
+ `);
37
+ }
38
+ async down() {
39
+ this.addSql(`alter table "customer_todo_links" alter column "todo_source" set default 'example:todo';`);
40
+ }
41
+ }
42
+ export {
43
+ Migration20260401172819
44
+ };
45
+ //# sourceMappingURL=Migration20260401172819.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/migrations/Migration20260401172819.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260401172819 extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`alter table \"customer_todo_links\" alter column \"todo_source\" set default 'customers:interaction';`);\n this.addSql(`\n update \"role_acls\" as ra\n set\n \"features_json\" = case\n when ra.\"features_json\" is null or jsonb_typeof(ra.\"features_json\") <> 'array'\n then '[\"customers.interactions.view\"]'::jsonb\n else ra.\"features_json\" || '\"customers.interactions.view\"'::jsonb\n end,\n \"updated_at\" = now()\n where ra.\"deleted_at\" is null\n and ra.\"features_json\" is not null\n and jsonb_typeof(ra.\"features_json\") = 'array'\n and ra.\"features_json\" ? 'example.todos.view'\n and ra.\"features_json\" ? 'customers.activities.view'\n and not (ra.\"features_json\" ? 'customers.interactions.view');\n `);\n this.addSql(`\n update \"user_acls\" as ua\n set\n \"features_json\" = case\n when ua.\"features_json\" is null or jsonb_typeof(ua.\"features_json\") <> 'array'\n then '[\"customers.interactions.view\"]'::jsonb\n else ua.\"features_json\" || '\"customers.interactions.view\"'::jsonb\n end,\n \"updated_at\" = now()\n where ua.\"deleted_at\" is null\n and ua.\"features_json\" is not null\n and jsonb_typeof(ua.\"features_json\") = 'array'\n and ua.\"features_json\" ? 'example.todos.view'\n and ua.\"features_json\" ? 'customers.activities.view'\n and not (ua.\"features_json\" ? 'customers.interactions.view');\n `);\n }\n\n override async down(): Promise<void> {\n this.addSql(`alter table \"customer_todo_links\" alter column \"todo_source\" set default 'example:todo';`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,gCAAgC,UAAU;AAAA,EAErD,MAAe,KAAoB;AACjC,SAAK,OAAO,mGAAmG;AAC/G,SAAK,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAeX;AACD,SAAK,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAeX;AAAA,EACH;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,0FAA0F;AAAA,EACxG;AAEF;",
6
+ "names": []
7
+ }
@@ -1,3 +1,4 @@
1
+ import { CUSTOMER_INTERACTION_TASK_SOURCE, EXAMPLE_TODO_SOURCE } from "./lib/interactionCompatibility.js";
1
2
  function assertTenantContext(ctx) {
2
3
  if (typeof ctx.tenantId !== "string" || ctx.tenantId.length === 0) {
3
4
  throw new Error("[search.customers] Missing tenantId in search build context");
@@ -261,9 +262,9 @@ async function getLinkedTodo(ctx) {
261
262
  if (todoCache.has(ctx.record)) {
262
263
  return todoCache.get(ctx.record);
263
264
  }
264
- const sourceRaw = typeof ctx.record.todo_source === "string" ? ctx.record.todo_source : "example:todo";
265
+ const sourceRaw = typeof ctx.record.todo_source === "string" ? ctx.record.todo_source : EXAMPLE_TODO_SOURCE;
265
266
  const [moduleId, entityName] = sourceRaw.split(":");
266
- const entityId = moduleId && entityName ? `${moduleId}:${entityName}` : "example:todo";
267
+ const entityId = moduleId && entityName ? `${moduleId}:${entityName}` : CUSTOMER_INTERACTION_TASK_SOURCE;
267
268
  const todo = await loadRecord(ctx, entityId, ctx.record.todo_id ?? ctx.record.todoId);
268
269
  todoCache.set(ctx.record, todo ?? null);
269
270
  return todo ?? null;