@open-mercato/core 0.5.1-develop.2917.31ee9898e3 → 0.5.1-develop.2935.357c9db339

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 (59) hide show
  1. package/dist/modules/customers/api/companies/[id]/people/route.js +12 -7
  2. package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
  3. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -1
  4. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +7 -2
  6. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  7. package/dist/modules/customers/commands/companies.js +93 -19
  8. package/dist/modules/customers/commands/companies.js.map +2 -2
  9. package/dist/modules/customers/commands/people.js +9 -1
  10. package/dist/modules/customers/commands/people.js.map +2 -2
  11. package/dist/modules/customers/commands/personCompanyLinks.js +2 -2
  12. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  13. package/dist/modules/customers/components/detail/CompanyCard.js +32 -3
  14. package/dist/modules/customers/components/detail/CompanyCard.js.map +2 -2
  15. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -19
  16. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  17. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +7 -4
  18. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  19. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +63 -2
  20. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  21. package/dist/modules/customers/components/detail/PersonDetailTabs.js +37 -19
  22. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  23. package/dist/modules/customers/components/detail/TasksSection.js +1 -11
  24. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  25. package/dist/modules/customers/components/formConfig.js +50 -39
  26. package/dist/modules/customers/components/formConfig.js.map +2 -2
  27. package/dist/modules/customers/events.js +3 -3
  28. package/dist/modules/customers/events.js.map +2 -2
  29. package/dist/modules/customers/lib/displayName.js +13 -1
  30. package/dist/modules/customers/lib/displayName.js.map +2 -2
  31. package/dist/modules/customers/lib/personCompanies.js +12 -7
  32. package/dist/modules/customers/lib/personCompanies.js.map +2 -2
  33. package/dist/modules/customers/lib/personCompanyLinkTable.js +5 -0
  34. package/dist/modules/customers/lib/personCompanyLinkTable.js.map +2 -2
  35. package/dist/modules/integrations/data/validators.js +1 -1
  36. package/dist/modules/integrations/data/validators.js.map +2 -2
  37. package/package.json +3 -3
  38. package/src/modules/customers/api/companies/[id]/people/route.ts +12 -7
  39. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +2 -1
  40. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +12 -2
  41. package/src/modules/customers/commands/companies.ts +107 -19
  42. package/src/modules/customers/commands/people.ts +16 -1
  43. package/src/modules/customers/commands/personCompanyLinks.ts +3 -2
  44. package/src/modules/customers/components/detail/CompanyCard.tsx +28 -4
  45. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +18 -2
  46. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -4
  47. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +66 -0
  48. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +18 -2
  49. package/src/modules/customers/components/detail/TasksSection.tsx +1 -8
  50. package/src/modules/customers/components/formConfig.tsx +59 -40
  51. package/src/modules/customers/events.ts +3 -3
  52. package/src/modules/customers/i18n/de.json +10 -0
  53. package/src/modules/customers/i18n/en.json +10 -0
  54. package/src/modules/customers/i18n/es.json +10 -0
  55. package/src/modules/customers/i18n/pl.json +10 -0
  56. package/src/modules/customers/lib/displayName.ts +19 -0
  57. package/src/modules/customers/lib/personCompanies.ts +12 -7
  58. package/src/modules/customers/lib/personCompanyLinkTable.ts +14 -0
  59. package/src/modules/integrations/data/validators.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/customers/events.ts"],
4
- "sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * Customers Module Events\n *\n * Declares all events that can be emitted by the customers module.\n */\nconst events = [\n // People\n { id: 'customers.person.created', label: 'Customer (Person) Created', entity: 'person', category: 'crud' },\n { id: 'customers.person.updated', label: 'Customer (Person) Updated', entity: 'person', category: 'crud' },\n { id: 'customers.person.deleted', label: 'Customer (Person) Deleted', entity: 'person', category: 'crud' },\n\n // Companies\n { id: 'customers.company.created', label: 'Customer (Company) Created', entity: 'company', category: 'crud' },\n { id: 'customers.company.updated', label: 'Customer (Company) Updated', entity: 'company', category: 'crud' },\n { id: 'customers.company.deleted', label: 'Customer (Company) Deleted', entity: 'company', category: 'crud' },\n\n // Deals\n { id: 'customers.deal.created', label: 'Deal Created', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.updated', label: 'Deal Updated', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.deleted', label: 'Deal Deleted', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.won', label: 'Deal Won', entity: 'deal', category: 'lifecycle' },\n { id: 'customers.deal.lost', label: 'Deal Lost', entity: 'deal', category: 'lifecycle' },\n\n // Comments\n { id: 'customers.comment.created', label: 'Comment Created', entity: 'comment', category: 'crud' },\n { id: 'customers.comment.updated', label: 'Comment Updated', entity: 'comment', category: 'crud' },\n { id: 'customers.comment.deleted', label: 'Comment Deleted', entity: 'comment', category: 'crud' },\n\n // Addresses\n { id: 'customers.address.created', label: 'Address Created', entity: 'address', category: 'crud' },\n { id: 'customers.address.updated', label: 'Address Updated', entity: 'address', category: 'crud' },\n { id: 'customers.address.deleted', label: 'Address Deleted', entity: 'address', category: 'crud' },\n\n // Activities\n { id: 'customers.activity.created', label: 'Activity Created', entity: 'activity', category: 'crud' },\n { id: 'customers.activity.updated', label: 'Activity Updated', entity: 'activity', category: 'crud' },\n { id: 'customers.activity.deleted', label: 'Activity Deleted', entity: 'activity', category: 'crud' },\n\n // Tags\n { id: 'customers.tag.created', label: 'Tag Created', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.updated', label: 'Tag Updated', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.deleted', label: 'Tag Deleted', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.assigned', label: 'Tag Assigned', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.removed', label: 'Tag Removed', entity: 'tag', category: 'crud' },\n\n // Todos\n { id: 'customers.todo.created', label: 'Todo Created', entity: 'todo', category: 'crud' },\n { id: 'customers.todo.updated', label: 'Todo Updated', entity: 'todo', category: 'crud' },\n { id: 'customers.todo.deleted', label: 'Todo Deleted', entity: 'todo', category: 'crud' },\n\n // Interactions (canonical)\n { id: 'customers.interaction.created', label: 'Interaction Created', entity: 'interaction', category: 'crud' },\n { id: 'customers.interaction.updated', label: 'Interaction Updated', entity: 'interaction', category: 'crud' },\n { id: 'customers.interaction.completed', label: 'Interaction Completed', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.canceled', label: 'Interaction Canceled', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.reverted', label: 'Interaction Reverted', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.deleted', label: 'Interaction Deleted', entity: 'interaction', category: 'crud' },\n { id: 'customers.next_interaction.updated', label: 'Next Interaction Updated', entity: 'interaction', category: 'lifecycle' },\n\n // Entity Roles\n { id: 'customers.entity_role.created', label: 'Entity Role Created', entity: 'entity_role', category: 'crud' },\n { id: 'customers.entity_role.updated', label: 'Entity Role Updated', entity: 'entity_role', category: 'crud' },\n { id: 'customers.entity_role.deleted', label: 'Entity Role Deleted', entity: 'entity_role', category: 'crud' },\n\n // Labels\n { id: 'customers.label.created', label: 'Label Created', entity: 'label', category: 'crud' },\n { id: 'customers.label.updated', label: 'Label Updated', entity: 'label', category: 'crud' },\n { id: 'customers.label.deleted', label: 'Label Deleted', entity: 'label', category: 'crud' },\n\n // Label Assignments\n { id: 'customers.label_assignment.created', label: 'Label Assigned', entity: 'label_assignment', category: 'crud' },\n { id: 'customers.label_assignment.updated', label: 'Label Assignment Updated', entity: 'label_assignment', category: 'crud' },\n { id: 'customers.label_assignment.deleted', label: 'Label Unassigned', entity: 'label_assignment', category: 'crud' },\n\n // Person-Company Links\n { id: 'customers.person_company_link.created', label: 'Person Linked To Company', entity: 'person_company_link', category: 'crud' },\n { id: 'customers.person_company_link.updated', label: 'Person-Company Link Updated', entity: 'person_company_link', category: 'crud' },\n { id: 'customers.person_company_link.deleted', label: 'Person Unlinked From Company', entity: 'person_company_link', category: 'crud' },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'customers',\n events,\n})\n\n/** Type-safe event emitter for customers module */\nexport const emitCustomersEvent = eventsConfig.emit\n\n/** Event IDs that can be emitted by the customers module */\nexport type CustomersEventId = typeof events[number]['id']\n\nexport default eventsConfig\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * Customers Module Events\n *\n * Declares all events that can be emitted by the customers module.\n */\nconst events = [\n // People\n { id: 'customers.person.created', label: 'Customer (Person) Created', entity: 'person', category: 'crud' },\n { id: 'customers.person.updated', label: 'Customer (Person) Updated', entity: 'person', category: 'crud' },\n { id: 'customers.person.deleted', label: 'Customer (Person) Deleted', entity: 'person', category: 'crud' },\n\n // Companies\n { id: 'customers.company.created', label: 'Customer (Company) Created', entity: 'company', category: 'crud' },\n { id: 'customers.company.updated', label: 'Customer (Company) Updated', entity: 'company', category: 'crud' },\n { id: 'customers.company.deleted', label: 'Customer (Company) Deleted', entity: 'company', category: 'crud' },\n\n // Deals\n { id: 'customers.deal.created', label: 'Deal Created', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.updated', label: 'Deal Updated', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.deleted', label: 'Deal Deleted', entity: 'deal', category: 'crud' },\n { id: 'customers.deal.won', label: 'Deal Won', entity: 'deal', category: 'lifecycle' },\n { id: 'customers.deal.lost', label: 'Deal Lost', entity: 'deal', category: 'lifecycle' },\n\n // Comments\n { id: 'customers.comment.created', label: 'Comment Created', entity: 'comment', category: 'crud' },\n { id: 'customers.comment.updated', label: 'Comment Updated', entity: 'comment', category: 'crud' },\n { id: 'customers.comment.deleted', label: 'Comment Deleted', entity: 'comment', category: 'crud' },\n\n // Addresses\n { id: 'customers.address.created', label: 'Address Created', entity: 'address', category: 'crud' },\n { id: 'customers.address.updated', label: 'Address Updated', entity: 'address', category: 'crud' },\n { id: 'customers.address.deleted', label: 'Address Deleted', entity: 'address', category: 'crud' },\n\n // Activities\n { id: 'customers.activity.created', label: 'Activity Created', entity: 'activity', category: 'crud' },\n { id: 'customers.activity.updated', label: 'Activity Updated', entity: 'activity', category: 'crud' },\n { id: 'customers.activity.deleted', label: 'Activity Deleted', entity: 'activity', category: 'crud' },\n\n // Tags\n { id: 'customers.tag.created', label: 'Tag Created', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.updated', label: 'Tag Updated', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.deleted', label: 'Tag Deleted', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.assigned', label: 'Tag Assigned', entity: 'tag', category: 'crud' },\n { id: 'customers.tag.removed', label: 'Tag Removed', entity: 'tag', category: 'crud' },\n\n // Todos\n { id: 'customers.todo.created', label: 'Todo Created', entity: 'todo', category: 'crud' },\n { id: 'customers.todo.updated', label: 'Todo Updated', entity: 'todo', category: 'crud' },\n { id: 'customers.todo.deleted', label: 'Todo Deleted', entity: 'todo', category: 'crud' },\n\n // Interactions (canonical)\n { id: 'customers.interaction.created', label: 'Interaction Created', entity: 'interaction', category: 'crud' },\n { id: 'customers.interaction.updated', label: 'Interaction Updated', entity: 'interaction', category: 'crud' },\n { id: 'customers.interaction.completed', label: 'Interaction Completed', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.canceled', label: 'Interaction Canceled', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.reverted', label: 'Interaction Reverted', entity: 'interaction', category: 'lifecycle' },\n { id: 'customers.interaction.deleted', label: 'Interaction Deleted', entity: 'interaction', category: 'crud' },\n { id: 'customers.next_interaction.updated', label: 'Next Interaction Updated', entity: 'interaction', category: 'lifecycle' },\n\n // Entity Roles\n { id: 'customers.entity_role.created', label: 'Entity Role Created', entity: 'entity_role', category: 'crud' },\n { id: 'customers.entity_role.updated', label: 'Entity Role Updated', entity: 'entity_role', category: 'crud' },\n { id: 'customers.entity_role.deleted', label: 'Entity Role Deleted', entity: 'entity_role', category: 'crud' },\n\n // Labels\n { id: 'customers.label.created', label: 'Label Created', entity: 'label', category: 'crud' },\n { id: 'customers.label.updated', label: 'Label Updated', entity: 'label', category: 'crud' },\n { id: 'customers.label.deleted', label: 'Label Deleted', entity: 'label', category: 'crud' },\n\n // Label Assignments\n { id: 'customers.label_assignment.created', label: 'Label Assigned', entity: 'label_assignment', category: 'crud' },\n { id: 'customers.label_assignment.updated', label: 'Label Assignment Updated', entity: 'label_assignment', category: 'crud' },\n { id: 'customers.label_assignment.deleted', label: 'Label Unassigned', entity: 'label_assignment', category: 'crud' },\n\n // Person-Company Links\n { id: 'customers.person_company_link.created', label: 'Person Linked To Company', entity: 'person_company_link', category: 'crud', clientBroadcast: true },\n { id: 'customers.person_company_link.updated', label: 'Person-Company Link Updated', entity: 'person_company_link', category: 'crud', clientBroadcast: true },\n { id: 'customers.person_company_link.deleted', label: 'Person Unlinked From Company', entity: 'person_company_link', category: 'crud', clientBroadcast: true },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'customers',\n events,\n})\n\n/** Type-safe event emitter for customers module */\nexport const emitCustomersEvent = eventsConfig.emit\n\n/** Event IDs that can be emitted by the customers module */\nexport type CustomersEventId = typeof events[number]['id']\n\nexport default eventsConfig\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,13 @@
1
+ function deriveDisplayName(firstName, lastName) {
2
+ const first = (firstName ?? "").trim();
3
+ const last = (lastName ?? "").trim();
4
+ return [first, last].filter((part) => part.length > 0).join(" ").trim();
5
+ }
6
+ function isDerivedDisplayName(current, firstName, lastName) {
7
+ const trimmed = (current ?? "").trim();
8
+ if (trimmed.length === 0) return true;
9
+ return trimmed === deriveDisplayName(firstName, lastName);
10
+ }
1
11
  function deriveDisplayNameFromEmail(email) {
2
12
  if (typeof email !== "string") return null;
3
13
  const trimmed = email.trim();
@@ -10,6 +20,8 @@ function deriveDisplayNameFromEmail(email) {
10
20
  return segments.map((part) => part.charAt(0).toLocaleUpperCase() + part.slice(1)).join(" ");
11
21
  }
12
22
  export {
13
- deriveDisplayNameFromEmail
23
+ deriveDisplayName,
24
+ deriveDisplayNameFromEmail,
25
+ isDerivedDisplayName
14
26
  };
15
27
  //# sourceMappingURL=displayName.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/displayName.ts"],
4
- "sourcesContent": ["export function deriveDisplayNameFromEmail(email: string | null | undefined): string | null {\n if (typeof email !== 'string') return null\n const trimmed = email.trim()\n if (!trimmed.length) return null\n const atIndex = trimmed.indexOf('@')\n const localPart = (atIndex >= 0 ? trimmed.slice(0, atIndex) : trimmed).trim()\n if (!localPart.length) return null\n const segments = localPart\n .split(/[._\\-+]+/)\n .map((part) => part.trim())\n .filter((part) => part.length > 0)\n if (segments.length === 0) return null\n return segments\n .map((part) => part.charAt(0).toLocaleUpperCase() + part.slice(1))\n .join(' ')\n}\n"],
5
- "mappings": "AAAO,SAAS,2BAA2B,OAAiD;AAC1F,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,QAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,QAAM,aAAa,WAAW,IAAI,QAAQ,MAAM,GAAG,OAAO,IAAI,SAAS,KAAK;AAC5E,MAAI,CAAC,UAAU,OAAQ,QAAO;AAC9B,QAAM,WAAW,UACd,MAAM,UAAU,EAChB,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACnC,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,SACJ,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,kBAAkB,IAAI,KAAK,MAAM,CAAC,CAAC,EAChE,KAAK,GAAG;AACb;",
4
+ "sourcesContent": ["export function deriveDisplayName(\n firstName: string | null | undefined,\n lastName: string | null | undefined,\n): string {\n const first = (firstName ?? '').trim()\n const last = (lastName ?? '').trim()\n return [first, last].filter((part) => part.length > 0).join(' ').trim()\n}\n\nexport function isDerivedDisplayName(\n current: string | null | undefined,\n firstName: string | null | undefined,\n lastName: string | null | undefined,\n): boolean {\n const trimmed = (current ?? '').trim()\n if (trimmed.length === 0) return true\n return trimmed === deriveDisplayName(firstName, lastName)\n}\n\nexport function deriveDisplayNameFromEmail(email: string | null | undefined): string | null {\n if (typeof email !== 'string') return null\n const trimmed = email.trim()\n if (!trimmed.length) return null\n const atIndex = trimmed.indexOf('@')\n const localPart = (atIndex >= 0 ? trimmed.slice(0, atIndex) : trimmed).trim()\n if (!localPart.length) return null\n const segments = localPart\n .split(/[._\\-+]+/)\n .map((part) => part.trim())\n .filter((part) => part.length > 0)\n if (segments.length === 0) return null\n return segments\n .map((part) => part.charAt(0).toLocaleUpperCase() + part.slice(1))\n .join(' ')\n}\n"],
5
+ "mappings": "AAAO,SAAS,kBACd,WACA,UACQ;AACR,QAAM,SAAS,aAAa,IAAI,KAAK;AACrC,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,SAAO,CAAC,OAAO,IAAI,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,KAAK,GAAG,EAAE,KAAK;AACxE;AAEO,SAAS,qBACd,SACA,WACA,UACS;AACT,QAAM,WAAW,WAAW,IAAI,KAAK;AACrC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,YAAY,kBAAkB,WAAW,QAAQ;AAC1D;AAEO,SAAS,2BAA2B,OAAiD;AAC1F,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,QAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,QAAM,aAAa,WAAW,IAAI,QAAQ,MAAM,GAAG,OAAO,IAAI,SAAS,KAAK;AAC5E,MAAI,CAAC,UAAU,OAAQ,QAAO;AAC9B,QAAM,WAAW,UACd,MAAM,UAAU,EAChB,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACnC,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,SACJ,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,kBAAkB,IAAI,KAAK,MAAM,CAAC,CAAC,EAChE,KAAK,GAAG;AACb;",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,10 @@ import {
4
4
  CustomerEntity,
5
5
  CustomerPersonCompanyLink
6
6
  } from "../data/entities.js";
7
- import { withActiveCustomerPersonCompanyLinkFilter } from "./personCompanyLinkTable.js";
7
+ import {
8
+ filterActivePersonCompanyLinks,
9
+ withActiveCustomerPersonCompanyLinkFilter
10
+ } from "./personCompanyLinkTable.js";
8
11
  async function findDeletedPersonCompanyLink(em, person, company) {
9
12
  const link = await findOneWithDecryption(
10
13
  em,
@@ -37,12 +40,14 @@ async function loadPersonCompanyLinks(em, person) {
37
40
  { person, organizationId: person.organizationId, tenantId: person.tenantId },
38
41
  "customers.personCompanies.loadPersonCompanyLinks"
39
42
  );
40
- return findWithDecryption(
41
- em,
42
- CustomerPersonCompanyLink,
43
- where,
44
- { populate: ["company"], orderBy: { isPrimary: "desc", createdAt: "asc" } },
45
- { tenantId: person.tenantId, organizationId: person.organizationId }
43
+ return filterActivePersonCompanyLinks(
44
+ await findWithDecryption(
45
+ em,
46
+ CustomerPersonCompanyLink,
47
+ where,
48
+ { populate: ["company"], orderBy: { isPrimary: "desc", createdAt: "asc" } },
49
+ { tenantId: person.tenantId, organizationId: person.organizationId }
50
+ )
46
51
  );
47
52
  }
48
53
  function summarizePersonCompanies(profile, links) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/personCompanies.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n CustomerPersonProfile,\n} from '../data/entities'\nimport { withActiveCustomerPersonCompanyLinkFilter } from './personCompanyLinkTable'\n\nexport type PersonCompanySummary = {\n linkId: string | null\n companyId: string\n displayName: string\n isPrimary: boolean\n synthetic?: boolean\n}\n\nexport async function findDeletedPersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n company: CustomerEntity,\n): Promise<CustomerPersonCompanyLink | null> {\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n person,\n company,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: { $ne: null },\n } as any,\n {},\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n return link ?? null\n}\n\nasync function requireCompany(\n em: EntityManager,\n companyId: string,\n organizationId: string,\n tenantId: string,\n): Promise<CustomerEntity> {\n const company = await findOneWithDecryption(em, CustomerEntity, { id: companyId, kind: 'company', deletedAt: null }, {}, { tenantId, organizationId })\n if (!company) {\n throw new CrudHttpError(404, { error: 'Company not found' })\n }\n if (company.organizationId !== organizationId || company.tenantId !== tenantId) {\n throw new CrudHttpError(403, { error: 'Cannot link company outside current scope' })\n }\n return company\n}\n\nexport async function loadPersonCompanyLinks(\n em: EntityManager,\n person: CustomerEntity,\n): Promise<CustomerPersonCompanyLink[]> {\n const where = await withActiveCustomerPersonCompanyLinkFilter(\n em,\n { person, organizationId: person.organizationId, tenantId: person.tenantId },\n 'customers.personCompanies.loadPersonCompanyLinks',\n )\n return findWithDecryption(\n em,\n CustomerPersonCompanyLink,\n where,\n { populate: ['company'], orderBy: { isPrimary: 'desc', createdAt: 'asc' } },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n}\n\nexport function summarizePersonCompanies(\n profile: CustomerPersonProfile | null,\n links: CustomerPersonCompanyLink[],\n): PersonCompanySummary[] {\n if (links.length > 0) {\n const items: PersonCompanySummary[] = []\n links.forEach((link) => {\n const company = typeof link.company === 'string' ? null : link.company\n if (!company) return\n items.push({\n linkId: link.id,\n companyId: company.id,\n displayName: company.displayName,\n isPrimary: Boolean(link.isPrimary),\n })\n })\n return items\n }\n\n const fallbackCompany = profile?.company && typeof profile.company !== 'string' ? profile.company : null\n if (!fallbackCompany) return []\n\n return [\n {\n linkId: fallbackCompany.id,\n companyId: fallbackCompany.id,\n displayName: fallbackCompany.displayName,\n isPrimary: true,\n synthetic: true,\n },\n ]\n}\n\nasync function clearPrimaryFlags(em: EntityManager, person: CustomerEntity): Promise<void> {\n await em.nativeUpdate(\n CustomerPersonCompanyLink,\n { person, organizationId: person.organizationId, tenantId: person.tenantId, isPrimary: true },\n { isPrimary: false },\n )\n}\n\nfunction resolveLinkedCompany(link: CustomerPersonCompanyLink): CustomerEntity | null {\n return typeof link.company === 'string' ? null : link.company\n}\n\nexport async function promoteFallbackPrimaryLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n links: CustomerPersonCompanyLink[],\n removedCompanyId?: string | null,\n): Promise<void> {\n const nextPrimary = links[0] ?? null\n if (!nextPrimary) {\n if (\n !removedCompanyId\n || (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId)\n || profile.company == null\n ) {\n profile.company = null\n }\n return\n }\n\n await clearPrimaryFlags(em, person)\n nextPrimary.isPrimary = true\n const nextCompany = resolveLinkedCompany(nextPrimary)\n if (nextCompany) {\n profile.company = nextCompany\n }\n}\n\nexport async function syncLegacyPrimaryCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n companyId: string | null | undefined,\n): Promise<void> {\n const normalizedCompanyId = typeof companyId === 'string' && companyId.trim().length > 0 ? companyId.trim() : null\n const existingLinks = await loadPersonCompanyLinks(em, person)\n\n if (!normalizedCompanyId) {\n if (existingLinks.some((link) => link.isPrimary)) {\n await clearPrimaryFlags(em, person)\n }\n profile.company = null\n return\n }\n\n const company = await requireCompany(em, normalizedCompanyId, person.organizationId, person.tenantId)\n const currentLink =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n\n if (currentLink) {\n if (!currentLink.isPrimary) {\n await clearPrimaryFlags(em, person)\n currentLink.isPrimary = true\n } else if (existingLinks.some((link) => link.id !== currentLink.id && link.isPrimary)) {\n await clearPrimaryFlags(em, person)\n currentLink.isPrimary = true\n }\n } else {\n await clearPrimaryFlags(em, person)\n const link = em.create(CustomerPersonCompanyLink, {\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n person,\n company,\n isPrimary: true,\n })\n em.persist(link)\n }\n\n profile.company = company\n}\n\nexport async function addPersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n companyId: string,\n options?: { isPrimary?: boolean },\n): Promise<CustomerPersonCompanyLink> {\n const company = await requireCompany(em, companyId, person.organizationId, person.tenantId)\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const makePrimary = Boolean(options?.isPrimary) || existingLinks.length === 0\n const existing =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n\n if (existing) {\n if (makePrimary && !existing.isPrimary) {\n await clearPrimaryFlags(em, person)\n existing.isPrimary = true\n profile.company = company\n }\n return existing\n }\n\n if (makePrimary) {\n await clearPrimaryFlags(em, person)\n }\n\n const deletedLink = await findDeletedPersonCompanyLink(em, person, company)\n const link = deletedLink ?? em.create(CustomerPersonCompanyLink, {\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n person,\n company,\n isPrimary: makePrimary,\n })\n if (deletedLink) {\n deletedLink.deletedAt = null\n deletedLink.isPrimary = makePrimary\n }\n em.persist(link)\n\n if (makePrimary) {\n profile.company = company\n } else if (!profile.company && existingLinks.length === 0) {\n profile.company = company\n link.isPrimary = true\n }\n\n return link\n}\n\nexport async function updatePersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n linkId: string,\n patch: { isPrimary?: boolean },\n): Promise<CustomerPersonCompanyLink | null> {\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const link = existingLinks.find((entry) => entry.id === linkId)\n ?? existingLinks.find((entry) => (typeof entry.company === 'string' ? entry.company : entry.company.id) === linkId)\n ?? null\n\n if (!link && profile.company && typeof profile.company !== 'string' && profile.company.id === linkId && patch.isPrimary === false) {\n profile.company = null\n return null\n }\n\n if (!link) {\n throw new CrudHttpError(404, { error: 'Company link not found' })\n }\n\n if (patch.isPrimary === true) {\n await clearPrimaryFlags(em, person)\n link.isPrimary = true\n const company = resolveLinkedCompany(link)\n if (company) {\n profile.company = company\n }\n } else if (patch.isPrimary === false) {\n const linkWasPrimary = link.isPrimary\n const removedCompanyId = typeof link.company === 'string' ? link.company : link.company.id\n link.isPrimary = false\n if (linkWasPrimary) {\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, removedCompanyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId) {\n profile.company = null\n }\n }\n\n return link\n}\n\nexport async function removePersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n linkId: string,\n): Promise<void> {\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const link = existingLinks.find((entry) => entry.id === linkId)\n ?? existingLinks.find((entry) => (typeof entry.company === 'string' ? entry.company : entry.company.id) === linkId)\n ?? null\n\n if (!link) {\n if (profile.company && typeof profile.company !== 'string' && profile.company.id === linkId) {\n profile.company = null\n return\n }\n throw new CrudHttpError(404, { error: 'Company link not found' })\n }\n\n const removedCompanyId = typeof link.company === 'string' ? link.company : link.company.id\n const removedWasPrimary = link.isPrimary\n link.isPrimary = false\n link.deletedAt = new Date()\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n\n if (removedWasPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, removedCompanyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId) {\n const primary = remainingLinks.find((entry) => entry.isPrimary) ?? null\n const primaryCompany = primary ? resolveLinkedCompany(primary) : null\n if (primaryCompany) {\n profile.company = primaryCompany\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n CustomerPersonProfile,\n} from '../data/entities'\nimport {\n filterActivePersonCompanyLinks,\n withActiveCustomerPersonCompanyLinkFilter,\n} from './personCompanyLinkTable'\n\nexport type PersonCompanySummary = {\n linkId: string | null\n companyId: string\n displayName: string\n isPrimary: boolean\n synthetic?: boolean\n}\n\nexport async function findDeletedPersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n company: CustomerEntity,\n): Promise<CustomerPersonCompanyLink | null> {\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n person,\n company,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: { $ne: null },\n } as any,\n {},\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n return link ?? null\n}\n\nasync function requireCompany(\n em: EntityManager,\n companyId: string,\n organizationId: string,\n tenantId: string,\n): Promise<CustomerEntity> {\n const company = await findOneWithDecryption(em, CustomerEntity, { id: companyId, kind: 'company', deletedAt: null }, {}, { tenantId, organizationId })\n if (!company) {\n throw new CrudHttpError(404, { error: 'Company not found' })\n }\n if (company.organizationId !== organizationId || company.tenantId !== tenantId) {\n throw new CrudHttpError(403, { error: 'Cannot link company outside current scope' })\n }\n return company\n}\n\nexport async function loadPersonCompanyLinks(\n em: EntityManager,\n person: CustomerEntity,\n): Promise<CustomerPersonCompanyLink[]> {\n const where = await withActiveCustomerPersonCompanyLinkFilter(\n em,\n { person, organizationId: person.organizationId, tenantId: person.tenantId },\n 'customers.personCompanies.loadPersonCompanyLinks',\n )\n return filterActivePersonCompanyLinks(\n await findWithDecryption(\n em,\n CustomerPersonCompanyLink,\n where,\n { populate: ['company'], orderBy: { isPrimary: 'desc', createdAt: 'asc' } },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n ),\n )\n}\n\nexport function summarizePersonCompanies(\n profile: CustomerPersonProfile | null,\n links: CustomerPersonCompanyLink[],\n): PersonCompanySummary[] {\n if (links.length > 0) {\n const items: PersonCompanySummary[] = []\n links.forEach((link) => {\n const company = typeof link.company === 'string' ? null : link.company\n if (!company) return\n items.push({\n linkId: link.id,\n companyId: company.id,\n displayName: company.displayName,\n isPrimary: Boolean(link.isPrimary),\n })\n })\n return items\n }\n\n const fallbackCompany = profile?.company && typeof profile.company !== 'string' ? profile.company : null\n if (!fallbackCompany) return []\n\n return [\n {\n linkId: fallbackCompany.id,\n companyId: fallbackCompany.id,\n displayName: fallbackCompany.displayName,\n isPrimary: true,\n synthetic: true,\n },\n ]\n}\n\nasync function clearPrimaryFlags(em: EntityManager, person: CustomerEntity): Promise<void> {\n await em.nativeUpdate(\n CustomerPersonCompanyLink,\n { person, organizationId: person.organizationId, tenantId: person.tenantId, isPrimary: true },\n { isPrimary: false },\n )\n}\n\nfunction resolveLinkedCompany(link: CustomerPersonCompanyLink): CustomerEntity | null {\n return typeof link.company === 'string' ? null : link.company\n}\n\nexport async function promoteFallbackPrimaryLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n links: CustomerPersonCompanyLink[],\n removedCompanyId?: string | null,\n): Promise<void> {\n const nextPrimary = links[0] ?? null\n if (!nextPrimary) {\n if (\n !removedCompanyId\n || (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId)\n || profile.company == null\n ) {\n profile.company = null\n }\n return\n }\n\n await clearPrimaryFlags(em, person)\n nextPrimary.isPrimary = true\n const nextCompany = resolveLinkedCompany(nextPrimary)\n if (nextCompany) {\n profile.company = nextCompany\n }\n}\n\nexport async function syncLegacyPrimaryCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n companyId: string | null | undefined,\n): Promise<void> {\n const normalizedCompanyId = typeof companyId === 'string' && companyId.trim().length > 0 ? companyId.trim() : null\n const existingLinks = await loadPersonCompanyLinks(em, person)\n\n if (!normalizedCompanyId) {\n if (existingLinks.some((link) => link.isPrimary)) {\n await clearPrimaryFlags(em, person)\n }\n profile.company = null\n return\n }\n\n const company = await requireCompany(em, normalizedCompanyId, person.organizationId, person.tenantId)\n const currentLink =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n\n if (currentLink) {\n if (!currentLink.isPrimary) {\n await clearPrimaryFlags(em, person)\n currentLink.isPrimary = true\n } else if (existingLinks.some((link) => link.id !== currentLink.id && link.isPrimary)) {\n await clearPrimaryFlags(em, person)\n currentLink.isPrimary = true\n }\n } else {\n await clearPrimaryFlags(em, person)\n const link = em.create(CustomerPersonCompanyLink, {\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n person,\n company,\n isPrimary: true,\n })\n em.persist(link)\n }\n\n profile.company = company\n}\n\nexport async function addPersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n companyId: string,\n options?: { isPrimary?: boolean },\n): Promise<CustomerPersonCompanyLink> {\n const company = await requireCompany(em, companyId, person.organizationId, person.tenantId)\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const makePrimary = Boolean(options?.isPrimary) || existingLinks.length === 0\n const existing =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n\n if (existing) {\n if (makePrimary && !existing.isPrimary) {\n await clearPrimaryFlags(em, person)\n existing.isPrimary = true\n profile.company = company\n }\n return existing\n }\n\n if (makePrimary) {\n await clearPrimaryFlags(em, person)\n }\n\n const deletedLink = await findDeletedPersonCompanyLink(em, person, company)\n const link = deletedLink ?? em.create(CustomerPersonCompanyLink, {\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n person,\n company,\n isPrimary: makePrimary,\n })\n if (deletedLink) {\n deletedLink.deletedAt = null\n deletedLink.isPrimary = makePrimary\n }\n em.persist(link)\n\n if (makePrimary) {\n profile.company = company\n } else if (!profile.company && existingLinks.length === 0) {\n profile.company = company\n link.isPrimary = true\n }\n\n return link\n}\n\nexport async function updatePersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n linkId: string,\n patch: { isPrimary?: boolean },\n): Promise<CustomerPersonCompanyLink | null> {\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const link = existingLinks.find((entry) => entry.id === linkId)\n ?? existingLinks.find((entry) => (typeof entry.company === 'string' ? entry.company : entry.company.id) === linkId)\n ?? null\n\n if (!link && profile.company && typeof profile.company !== 'string' && profile.company.id === linkId && patch.isPrimary === false) {\n profile.company = null\n return null\n }\n\n if (!link) {\n throw new CrudHttpError(404, { error: 'Company link not found' })\n }\n\n if (patch.isPrimary === true) {\n await clearPrimaryFlags(em, person)\n link.isPrimary = true\n const company = resolveLinkedCompany(link)\n if (company) {\n profile.company = company\n }\n } else if (patch.isPrimary === false) {\n const linkWasPrimary = link.isPrimary\n const removedCompanyId = typeof link.company === 'string' ? link.company : link.company.id\n link.isPrimary = false\n if (linkWasPrimary) {\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, removedCompanyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId) {\n profile.company = null\n }\n }\n\n return link\n}\n\nexport async function removePersonCompanyLink(\n em: EntityManager,\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n linkId: string,\n): Promise<void> {\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const link = existingLinks.find((entry) => entry.id === linkId)\n ?? existingLinks.find((entry) => (typeof entry.company === 'string' ? entry.company : entry.company.id) === linkId)\n ?? null\n\n if (!link) {\n if (profile.company && typeof profile.company !== 'string' && profile.company.id === linkId) {\n profile.company = null\n return\n }\n throw new CrudHttpError(404, { error: 'Company link not found' })\n }\n\n const removedCompanyId = typeof link.company === 'string' ? link.company : link.company.id\n const removedWasPrimary = link.isPrimary\n link.isPrimary = false\n link.deletedAt = new Date()\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n\n if (removedWasPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, removedCompanyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === removedCompanyId) {\n const primary = remainingLinks.find((entry) => entry.isPrimary) ?? null\n const primaryCompany = primary ? resolveLinkedCompany(primary) : null\n if (primaryCompany) {\n profile.company = primaryCompany\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["link"]
7
7
  }
@@ -32,8 +32,13 @@ async function withActiveCustomerPersonCompanyLinkFilter(em, where, source) {
32
32
  }
33
33
  return { ...where, deletedAt: null };
34
34
  }
35
+ function filterActivePersonCompanyLinks(links) {
36
+ if (!Array.isArray(links)) return [];
37
+ return links.filter((entry) => entry?.deletedAt == null);
38
+ }
35
39
  export {
36
40
  customerPersonCompanyLinksSupportDeletedAt,
41
+ filterActivePersonCompanyLinks,
37
42
  warnMissingCustomerPersonCompanyLinksDeletedAt,
38
43
  withActiveCustomerPersonCompanyLinkFilter
39
44
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/personCompanyLinkTable.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from '@mikro-orm/postgresql'\n\nconst PERSON_COMPANY_LINKS_TABLE = 'customer_person_company_links'\nconst PERSON_COMPANY_LINKS_DELETED_AT_COLUMN = 'deleted_at'\n\nlet supportsDeletedAtColumnPromise: Promise<boolean> | null = null\nlet warnedAboutMissingDeletedAtColumn = false\n\nexport async function customerPersonCompanyLinksSupportDeletedAt(em: EntityManager): Promise<boolean> {\n if (typeof (em as { getKysely?: unknown }).getKysely !== 'function') {\n return true\n }\n if (!supportsDeletedAtColumnPromise) {\n const db = em.getKysely<any>() as any\n const probe: Promise<boolean> = db\n .selectFrom('information_schema.columns')\n .select(['column_name'])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name', '=', PERSON_COMPANY_LINKS_TABLE)\n .where('column_name', '=', PERSON_COMPANY_LINKS_DELETED_AT_COLUMN)\n .executeTakeFirst()\n .then((row: unknown) => !!row)\n .catch(() => false)\n supportsDeletedAtColumnPromise = probe\n return probe\n }\n return supportsDeletedAtColumnPromise\n}\n\nexport function warnMissingCustomerPersonCompanyLinksDeletedAt(source: string): void {\n if (warnedAboutMissingDeletedAtColumn) {\n return\n }\n warnedAboutMissingDeletedAtColumn = true\n console.warn(\n `[${source}] missing ${PERSON_COMPANY_LINKS_TABLE}.${PERSON_COMPANY_LINKS_DELETED_AT_COLUMN}; ` +\n 'continuing without link soft-delete filtering. Run yarn db:migrate.',\n )\n}\n\nexport async function withActiveCustomerPersonCompanyLinkFilter<T extends Record<string, unknown>>(\n em: EntityManager,\n where: T,\n source: string,\n): Promise<T & { deletedAt?: null }> {\n const supportsDeletedAt = await customerPersonCompanyLinksSupportDeletedAt(em)\n if (!supportsDeletedAt) {\n warnMissingCustomerPersonCompanyLinksDeletedAt(source)\n return { ...where }\n }\n return { ...where, deletedAt: null }\n}\n"],
5
- "mappings": "AACA,SAAS,WAAW;AAEpB,MAAM,6BAA6B;AACnC,MAAM,yCAAyC;AAE/C,IAAI,iCAA0D;AAC9D,IAAI,oCAAoC;AAExC,eAAsB,2CAA2C,IAAqC;AACpG,MAAI,OAAQ,GAA+B,cAAc,YAAY;AACnE,WAAO;AAAA,EACT;AACA,MAAI,CAAC,gCAAgC;AACnC,UAAM,KAAK,GAAG,UAAe;AAC7B,UAAM,QAA0B,GAC7B,WAAW,4BAA4B,EACvC,OAAO,CAAC,aAAa,CAAC,EACtB,MAAM,oCAA6C,EACnD,MAAM,cAAc,KAAK,0BAA0B,EACnD,MAAM,eAAe,KAAK,sCAAsC,EAChE,iBAAiB,EACjB,KAAK,CAAC,QAAiB,CAAC,CAAC,GAAG,EAC5B,MAAM,MAAM,KAAK;AACpB,qCAAiC;AACjC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,+CAA+C,QAAsB;AACnF,MAAI,mCAAmC;AACrC;AAAA,EACF;AACA,sCAAoC;AACpC,UAAQ;AAAA,IACN,IAAI,MAAM,aAAa,0BAA0B,IAAI,sCAAsC;AAAA,EAE7F;AACF;AAEA,eAAsB,0CACpB,IACA,OACA,QACmC;AACnC,QAAM,oBAAoB,MAAM,2CAA2C,EAAE;AAC7E,MAAI,CAAC,mBAAmB;AACtB,mDAA+C,MAAM;AACrD,WAAO,EAAE,GAAG,MAAM;AAAA,EACpB;AACA,SAAO,EAAE,GAAG,OAAO,WAAW,KAAK;AACrC;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from '@mikro-orm/postgresql'\n\nconst PERSON_COMPANY_LINKS_TABLE = 'customer_person_company_links'\nconst PERSON_COMPANY_LINKS_DELETED_AT_COLUMN = 'deleted_at'\n\nlet supportsDeletedAtColumnPromise: Promise<boolean> | null = null\nlet warnedAboutMissingDeletedAtColumn = false\n\nexport async function customerPersonCompanyLinksSupportDeletedAt(em: EntityManager): Promise<boolean> {\n if (typeof (em as { getKysely?: unknown }).getKysely !== 'function') {\n return true\n }\n if (!supportsDeletedAtColumnPromise) {\n const db = em.getKysely<any>() as any\n const probe: Promise<boolean> = db\n .selectFrom('information_schema.columns')\n .select(['column_name'])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name', '=', PERSON_COMPANY_LINKS_TABLE)\n .where('column_name', '=', PERSON_COMPANY_LINKS_DELETED_AT_COLUMN)\n .executeTakeFirst()\n .then((row: unknown) => !!row)\n .catch(() => false)\n supportsDeletedAtColumnPromise = probe\n return probe\n }\n return supportsDeletedAtColumnPromise\n}\n\nexport function warnMissingCustomerPersonCompanyLinksDeletedAt(source: string): void {\n if (warnedAboutMissingDeletedAtColumn) {\n return\n }\n warnedAboutMissingDeletedAtColumn = true\n console.warn(\n `[${source}] missing ${PERSON_COMPANY_LINKS_TABLE}.${PERSON_COMPANY_LINKS_DELETED_AT_COLUMN}; ` +\n 'continuing without link soft-delete filtering. Run yarn db:migrate.',\n )\n}\n\nexport async function withActiveCustomerPersonCompanyLinkFilter<T extends Record<string, unknown>>(\n em: EntityManager,\n where: T,\n source: string,\n): Promise<T & { deletedAt?: null }> {\n const supportsDeletedAt = await customerPersonCompanyLinksSupportDeletedAt(em)\n if (!supportsDeletedAt) {\n warnMissingCustomerPersonCompanyLinksDeletedAt(source)\n return { ...where }\n }\n return { ...where, deletedAt: null }\n}\n\n/**\n * Drop soft-deleted link rows from a result set as a defense-in-depth fallback.\n * MikroORM has historically dropped `deletedAt: null` from the WHERE clause for\n * nullable date columns under certain configurations, so callers SHOULD apply this\n * after `findWithDecryption(...)` until the upstream query filter is verified to\n * fully cover all callers.\n */\nexport function filterActivePersonCompanyLinks<T extends { deletedAt?: Date | string | null | undefined }>(\n links: T[] | null | undefined,\n): T[] {\n if (!Array.isArray(links)) return []\n return links.filter((entry) => entry?.deletedAt == null)\n}\n"],
5
+ "mappings": "AACA,SAAS,WAAW;AAEpB,MAAM,6BAA6B;AACnC,MAAM,yCAAyC;AAE/C,IAAI,iCAA0D;AAC9D,IAAI,oCAAoC;AAExC,eAAsB,2CAA2C,IAAqC;AACpG,MAAI,OAAQ,GAA+B,cAAc,YAAY;AACnE,WAAO;AAAA,EACT;AACA,MAAI,CAAC,gCAAgC;AACnC,UAAM,KAAK,GAAG,UAAe;AAC7B,UAAM,QAA0B,GAC7B,WAAW,4BAA4B,EACvC,OAAO,CAAC,aAAa,CAAC,EACtB,MAAM,oCAA6C,EACnD,MAAM,cAAc,KAAK,0BAA0B,EACnD,MAAM,eAAe,KAAK,sCAAsC,EAChE,iBAAiB,EACjB,KAAK,CAAC,QAAiB,CAAC,CAAC,GAAG,EAC5B,MAAM,MAAM,KAAK;AACpB,qCAAiC;AACjC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,+CAA+C,QAAsB;AACnF,MAAI,mCAAmC;AACrC;AAAA,EACF;AACA,sCAAoC;AACpC,UAAQ;AAAA,IACN,IAAI,MAAM,aAAa,0BAA0B,IAAI,sCAAsC;AAAA,EAE7F;AACF;AAEA,eAAsB,0CACpB,IACA,OACA,QACmC;AACnC,QAAM,oBAAoB,MAAM,2CAA2C,EAAE;AAC7E,MAAI,CAAC,mBAAmB;AACtB,mDAA+C,MAAM;AACrD,WAAO,EAAE,GAAG,MAAM;AAAA,EACpB;AACA,SAAO,EAAE,GAAG,OAAO,WAAW,KAAK;AACrC;AASO,SAAS,+BACd,OACK;AACL,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,SAAO,MAAM,OAAO,CAAC,UAAU,OAAO,aAAa,IAAI;AACzD;",
6
6
  "names": []
7
7
  }
@@ -31,7 +31,7 @@ const optionalBooleanQuery = z.preprocess((value) => {
31
31
  if (value === true || value === "true" || value === "1") return true;
32
32
  if (value === false || value === "false" || value === "0") return false;
33
33
  return value;
34
- }, z.boolean().optional());
34
+ }, z.boolean().optional()).optional();
35
35
  const integrationMarketplaceHealthStatusSchema = z.enum(["healthy", "degraded", "unhealthy", "unconfigured"]);
36
36
  const listIntegrationsQuerySchema = z.object({
37
37
  q: z.string().max(200).optional(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\nexport const saveCredentialsSchema = z.object({\n credentials: z.record(\n z.string().min(1).max(128),\n z.union([z.string().max(20_000), z.number(), z.boolean(), z.null()]),\n ),\n}).refine((value) => Object.keys(value.credentials).length <= 200, {\n message: 'At most 200 credential fields are allowed',\n})\n\nexport type SaveCredentialsInput = z.infer<typeof saveCredentialsSchema>\n\nexport const updateVersionSchema = z.object({\n apiVersion: z.string().min(1),\n})\n\nexport type UpdateVersionInput = z.infer<typeof updateVersionSchema>\n\nexport const updateStateSchema = z.object({\n isEnabled: z.boolean().optional(),\n reauthRequired: z.boolean().optional(),\n}).refine((value) => value.isEnabled !== undefined || value.reauthRequired !== undefined, {\n message: 'At least one state field must be provided',\n})\n\nexport type UpdateStateInput = z.infer<typeof updateStateSchema>\n\nexport const integrationLogLevelSchema = z.enum(['info', 'warn', 'error'])\n\nexport const listIntegrationLogsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n level: integrationLogLevelSchema.optional(),\n runId: z.string().uuid().optional(),\n entityType: z.string().optional(),\n entityId: z.string().uuid().optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nexport type ListIntegrationLogsQuery = z.infer<typeof listIntegrationLogsQuerySchema>\n\nconst optionalBooleanQuery = z.preprocess((value) => {\n if (value === undefined || value === '' || value === null) return undefined\n if (value === true || value === 'true' || value === '1') return true\n if (value === false || value === 'false' || value === '0') return false\n return value\n}, z.boolean().optional())\n\nexport const integrationMarketplaceHealthStatusSchema = z.enum(['healthy', 'degraded', 'unhealthy', 'unconfigured'])\n\nexport const listIntegrationsQuerySchema = z.object({\n q: z.string().max(200).optional(),\n category: z.string().max(64).optional(),\n bundleId: z.string().max(128).optional(),\n isEnabled: optionalBooleanQuery,\n healthStatus: integrationMarketplaceHealthStatusSchema.optional(),\n sort: z.enum(['title', 'category', 'enabledAt', 'healthStatus']).optional(),\n order: z.enum(['asc', 'desc']).default('asc'),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(100),\n})\n\nexport type ListIntegrationsQuery = z.infer<typeof listIntegrationsQuerySchema>\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,aAAa,EAAE;AAAA,IACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,IACzB,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAM,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;AAAA,EACrE;AACF,CAAC,EAAE,OAAO,CAAC,UAAU,OAAO,KAAK,MAAM,WAAW,EAAE,UAAU,KAAK;AAAA,EACjE,SAAS;AACX,CAAC;AAIM,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,QAAQ,EAAE,SAAS;AACvC,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,cAAc,UAAa,MAAM,mBAAmB,QAAW;AAAA,EACxF,SAAS;AACX,CAAC;AAIM,MAAM,4BAA4B,EAAE,KAAK,CAAC,QAAQ,QAAQ,OAAO,CAAC;AAElE,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC1C,OAAO,0BAA0B,SAAS;AAAA,EAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC9D,CAAC;AAID,MAAM,uBAAuB,EAAE,WAAW,CAAC,UAAU;AACnD,MAAI,UAAU,UAAa,UAAU,MAAM,UAAU,KAAM,QAAO;AAClE,MAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,IAAK,QAAO;AAChE,MAAI,UAAU,SAAS,UAAU,WAAW,UAAU,IAAK,QAAO;AAClE,SAAO;AACT,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC;AAElB,MAAM,2CAA2C,EAAE,KAAK,CAAC,WAAW,YAAY,aAAa,cAAc,CAAC;AAE5G,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,GAAG,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvC,WAAW;AAAA,EACX,cAAc,yCAAyC,SAAS;AAAA,EAChE,MAAM,EAAE,KAAK,CAAC,SAAS,YAAY,aAAa,cAAc,CAAC,EAAE,SAAS;AAAA,EAC1E,OAAO,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,KAAK;AAAA,EAC5C,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,GAAG;AAC/D,CAAC;",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nexport const saveCredentialsSchema = z.object({\n credentials: z.record(\n z.string().min(1).max(128),\n z.union([z.string().max(20_000), z.number(), z.boolean(), z.null()]),\n ),\n}).refine((value) => Object.keys(value.credentials).length <= 200, {\n message: 'At most 200 credential fields are allowed',\n})\n\nexport type SaveCredentialsInput = z.infer<typeof saveCredentialsSchema>\n\nexport const updateVersionSchema = z.object({\n apiVersion: z.string().min(1),\n})\n\nexport type UpdateVersionInput = z.infer<typeof updateVersionSchema>\n\nexport const updateStateSchema = z.object({\n isEnabled: z.boolean().optional(),\n reauthRequired: z.boolean().optional(),\n}).refine((value) => value.isEnabled !== undefined || value.reauthRequired !== undefined, {\n message: 'At least one state field must be provided',\n})\n\nexport type UpdateStateInput = z.infer<typeof updateStateSchema>\n\nexport const integrationLogLevelSchema = z.enum(['info', 'warn', 'error'])\n\nexport const listIntegrationLogsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n level: integrationLogLevelSchema.optional(),\n runId: z.string().uuid().optional(),\n entityType: z.string().optional(),\n entityId: z.string().uuid().optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nexport type ListIntegrationLogsQuery = z.infer<typeof listIntegrationLogsQuerySchema>\n\nconst optionalBooleanQuery = z.preprocess((value) => {\n if (value === undefined || value === '' || value === null) return undefined\n if (value === true || value === 'true' || value === '1') return true\n if (value === false || value === 'false' || value === '0') return false\n return value\n}, z.boolean().optional()).optional()\n\nexport const integrationMarketplaceHealthStatusSchema = z.enum(['healthy', 'degraded', 'unhealthy', 'unconfigured'])\n\nexport const listIntegrationsQuerySchema = z.object({\n q: z.string().max(200).optional(),\n category: z.string().max(64).optional(),\n bundleId: z.string().max(128).optional(),\n isEnabled: optionalBooleanQuery,\n healthStatus: integrationMarketplaceHealthStatusSchema.optional(),\n sort: z.enum(['title', 'category', 'enabledAt', 'healthStatus']).optional(),\n order: z.enum(['asc', 'desc']).default('asc'),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(100),\n})\n\nexport type ListIntegrationsQuery = z.infer<typeof listIntegrationsQuerySchema>\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,aAAa,EAAE;AAAA,IACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,IACzB,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAM,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;AAAA,EACrE;AACF,CAAC,EAAE,OAAO,CAAC,UAAU,OAAO,KAAK,MAAM,WAAW,EAAE,UAAU,KAAK;AAAA,EACjE,SAAS;AACX,CAAC;AAIM,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,QAAQ,EAAE,SAAS;AACvC,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,cAAc,UAAa,MAAM,mBAAmB,QAAW;AAAA,EACxF,SAAS;AACX,CAAC;AAIM,MAAM,4BAA4B,EAAE,KAAK,CAAC,QAAQ,QAAQ,OAAO,CAAC;AAElE,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC1C,OAAO,0BAA0B,SAAS;AAAA,EAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC9D,CAAC;AAID,MAAM,uBAAuB,EAAE,WAAW,CAAC,UAAU;AACnD,MAAI,UAAU,UAAa,UAAU,MAAM,UAAU,KAAM,QAAO;AAClE,MAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,IAAK,QAAO;AAChE,MAAI,UAAU,SAAS,UAAU,WAAW,UAAU,IAAK,QAAO;AAClE,SAAO;AACT,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,SAAS;AAE7B,MAAM,2CAA2C,EAAE,KAAK,CAAC,WAAW,YAAY,aAAa,cAAc,CAAC;AAE5G,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,GAAG,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvC,WAAW;AAAA,EACX,cAAc,yCAAyC,SAAS;AAAA,EAChE,MAAM,EAAE,KAAK,CAAC,SAAS,YAAY,aAAa,cAAc,CAAC,EAAE,SAAS;AAAA,EAC1E,OAAO,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,KAAK;AAAA,EAC5C,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,GAAG;AAC/D,CAAC;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.5.1-develop.2917.31ee9898e3",
3
+ "version": "0.5.1-develop.2935.357c9db339",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -237,10 +237,10 @@
237
237
  "ts-pattern": "^5.0.0"
238
238
  },
239
239
  "peerDependencies": {
240
- "@open-mercato/shared": "0.5.1-develop.2917.31ee9898e3"
240
+ "@open-mercato/shared": "0.5.1-develop.2935.357c9db339"
241
241
  },
242
242
  "devDependencies": {
243
- "@open-mercato/shared": "0.5.1-develop.2917.31ee9898e3",
243
+ "@open-mercato/shared": "0.5.1-develop.2935.357c9db339",
244
244
  "@testing-library/dom": "^10.4.1",
245
245
  "@testing-library/jest-dom": "^6.9.1",
246
246
  "@testing-library/react": "^16.3.1",
@@ -13,7 +13,10 @@ import {
13
13
  CustomerPersonCompanyLink,
14
14
  CustomerPersonProfile,
15
15
  } from '../../../../data/entities'
16
- import { withActiveCustomerPersonCompanyLinkFilter } from '../../../../lib/personCompanyLinkTable'
16
+ import {
17
+ filterActivePersonCompanyLinks,
18
+ withActiveCustomerPersonCompanyLinkFilter,
19
+ } from '../../../../lib/personCompanyLinkTable'
17
20
 
18
21
  const paramsSchema = z.object({
19
22
  id: z.string().uuid(),
@@ -129,12 +132,14 @@ export async function GET(req: Request, ctx: { params?: { id?: string } }) {
129
132
  },
130
133
  'customers.companies.people.GET',
131
134
  )
132
- const links = await findWithDecryption(
133
- em,
134
- CustomerPersonCompanyLink,
135
- linkWhere,
136
- { populate: ['person'] },
137
- entityScope,
135
+ const links = filterActivePersonCompanyLinks(
136
+ await findWithDecryption(
137
+ em,
138
+ CustomerPersonCompanyLink,
139
+ linkWhere,
140
+ { populate: ['person'] },
141
+ entityScope,
142
+ ),
138
143
  )
139
144
 
140
145
  const personIds = links
@@ -255,7 +255,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
255
255
 
256
256
  // Section action (for tabs that expose add/create buttons)
257
257
  const handleSectionActionChange = React.useCallback((action: SectionAction | null) => {
258
- setSectionAction(action)
258
+ setSectionAction((prev) => (action !== null ? action : prev))
259
259
  }, [])
260
260
 
261
261
  const handleSectionAction = React.useCallback(() => {
@@ -420,6 +420,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
420
420
  peopleCount={data.counts?.people ?? 0}
421
421
  dealsCount={dealCount}
422
422
  activitiesCount={data.counts?.activities ?? 0}
423
+ sectionAction={sectionAction}
423
424
  >
424
425
  {activeTab === 'people' && (
425
426
  <CompanyPeopleSection
@@ -59,7 +59,6 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
59
59
 
60
60
  const formSchema = React.useMemo(() => createPersonEditSchema(), [])
61
61
  const fields = React.useMemo(() => createPersonEditFields(t), [t])
62
- const groups = React.useMemo(() => createPersonPersonalDataGroups(t), [t])
63
62
 
64
63
  const [data, setData] = React.useState<PersonOverview | null>(null)
65
64
  const [isLoading, setIsLoading] = React.useState(true)
@@ -101,6 +100,16 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
101
100
  ? data.person.displayName
102
101
  : t('customers.people.list.deleteFallbackName', 'this person')
103
102
 
103
+ const personDisplayNameForGroups =
104
+ typeof data?.person?.displayName === 'string' && data.person.displayName.trim().length
105
+ ? data.person.displayName.trim()
106
+ : null
107
+
108
+ const groups = React.useMemo(
109
+ () => createPersonPersonalDataGroups(t, { entityName: personDisplayNameForGroups }),
110
+ [t, personDisplayNameForGroups],
111
+ )
112
+
104
113
  const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [
105
114
  { id: 'personalData', icon: User, label: t('customers.people.form.groups.personalData', 'Personal data') },
106
115
  { id: 'companyRole', icon: Building2, label: t('customers.people.form.groups.companyRole', 'Company & role') },
@@ -249,7 +258,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
249
258
 
250
259
  // Section action (for tabs that expose add/create buttons)
251
260
  const handleSectionActionChange = React.useCallback((action: SectionAction | null) => {
252
- setSectionAction(action)
261
+ setSectionAction((prev) => (action !== null ? action : prev))
253
262
  }, [])
254
263
 
255
264
  React.useEffect(() => {
@@ -424,6 +433,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
424
433
  dealsCount={dealCount}
425
434
  companiesCount={companyCount}
426
435
  tasksCount={todoCount}
436
+ sectionAction={sectionAction}
427
437
  >
428
438
  <div className="min-w-0">
429
439
  {(() => {
@@ -22,6 +22,7 @@ import {
22
22
  CustomerInteraction,
23
23
  CustomerTodoLink,
24
24
  CustomerEntity,
25
+ CustomerPersonCompanyLink,
25
26
  CustomerPersonProfile,
26
27
  CustomerTagAssignment,
27
28
  } from '../data/entities'
@@ -49,7 +50,7 @@ import {
49
50
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
50
51
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
51
52
  import { E } from '#generated/entities.ids.generated'
52
- import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
53
+ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
53
54
  import { CUSTOMER_ENTITY_ID } from '../lib/customFieldRouting'
54
55
  import { CustomFieldValue } from '@open-mercato/core/modules/entities/data/entities'
55
56
 
@@ -72,6 +73,41 @@ const companyCrudEvents: CrudEventsConfig<CustomerEntity> = {
72
73
  }),
73
74
  }
74
75
 
76
+ type CompanyDeleteBlockerCounts = {
77
+ personLinks: number
78
+ dealLinks: number
79
+ directPeople: number
80
+ }
81
+
82
+ function buildCompanyHasDependentsError(
83
+ translate: (key: string, fallback?: string, params?: Record<string, string | number>) => string,
84
+ counts: CompanyDeleteBlockerCounts,
85
+ ): CrudHttpError {
86
+ const blockers: string[] = []
87
+ if (counts.personLinks > 0) {
88
+ blockers.push(
89
+ translate('customers.companies.delete.blockers.persons', 'linked persons ({{count}})', { count: counts.personLinks }),
90
+ )
91
+ }
92
+ if (counts.dealLinks > 0) {
93
+ blockers.push(
94
+ translate('customers.companies.delete.blockers.deals', 'linked deals ({{count}})', { count: counts.dealLinks }),
95
+ )
96
+ }
97
+ if (counts.directPeople > 0) {
98
+ blockers.push(
99
+ translate('customers.companies.delete.blockers.directPeople', 'persons whose primary company is this one ({{count}})', { count: counts.directPeople }),
100
+ )
101
+ }
102
+ const summary = blockers.join(', ')
103
+ const message = translate(
104
+ 'customers.companies.delete.blocked',
105
+ 'Cannot delete company: {{blockers}}. Please unlink or reassign first.',
106
+ { blockers: summary },
107
+ )
108
+ return new CrudHttpError(422, { error: message, code: 'COMPANY_HAS_DEPENDENTS' })
109
+ }
110
+
75
111
  function companyEntityIndexEntry(entity: CustomerEntity): QueryIndexEventEntry {
76
112
  return {
77
113
  entityType: E.customers.customer_entity,
@@ -781,28 +817,80 @@ const deleteCompanyCommand: CommandHandler<{ body?: Record<string, unknown>; que
781
817
  },
782
818
  async execute(input, ctx) {
783
819
  const id = requireId(input, 'Company id required')
784
- const em = (ctx.container.resolve('em') as EntityManager).fork()
785
- const snapshot = await loadCompanySnapshot(em, id)
786
- const entity = await em.findOne(CustomerEntity, { id, deletedAt: null })
820
+ const baseEm = (ctx.container.resolve('em') as EntityManager).fork()
821
+ const snapshot = await loadCompanySnapshot(baseEm, id)
822
+ const entity = await baseEm.findOne(CustomerEntity, { id, deletedAt: null })
787
823
  const record = assertFound(entity, 'Company not found')
788
824
  ensureTenantScope(ctx, record.tenantId)
789
825
  ensureOrganizationScope(ctx, record.organizationId)
790
- const profile = await em.findOne(CustomerCompanyProfile, { entity: record })
791
- await em.nativeUpdate(CustomerPersonProfile, { company: record }, { company: null })
792
- await em.nativeDelete(CustomerDealCompanyLink, { company: record })
793
- await em.nativeDelete(CustomerActivity, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
794
- await em.nativeDelete(CustomerInteraction, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
795
- await em.nativeDelete(CustomerTodoLink, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
796
- await em.nativeDelete(CustomerCompanyProfile, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
797
- await em.nativeDelete(CustomerAddress, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
798
- await em.nativeDelete(CustomerComment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
799
- await em.nativeDelete(CustomerTagAssignment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
800
- if (profile) {
801
- await em.nativeDelete(CustomFieldValue, { entityId: COMPANY_ENTITY_ID, recordId: profile.id })
826
+
827
+ const dependentScope = {
828
+ organizationId: record.organizationId,
829
+ tenantId: record.tenantId,
802
830
  }
803
- await em.nativeDelete(CustomFieldValue, { entityId: CUSTOMER_ENTITY_ID, recordId: record.id })
804
- em.remove(record)
805
- await em.flush()
831
+ const personLinks = await baseEm.count(CustomerPersonCompanyLink, {
832
+ company: record,
833
+ deletedAt: null,
834
+ ...dependentScope,
835
+ })
836
+ const dealLinks = await baseEm.count(CustomerDealCompanyLink, {
837
+ company: record,
838
+ })
839
+ const directPeople = await baseEm.count(CustomerPersonProfile, {
840
+ company: record,
841
+ ...dependentScope,
842
+ })
843
+ if (personLinks > 0 || dealLinks > 0 || directPeople > 0) {
844
+ const { translate } = await resolveTranslations()
845
+ throw buildCompanyHasDependentsError(translate, { personLinks, dealLinks, directPeople })
846
+ }
847
+
848
+ const profile = await baseEm.findOne(CustomerCompanyProfile, { entity: record })
849
+
850
+ await baseEm.transactional(async (em) => {
851
+ const recheckPersonLinks = await em.count(CustomerPersonCompanyLink, {
852
+ company: record,
853
+ deletedAt: null,
854
+ ...dependentScope,
855
+ })
856
+ const recheckDealLinks = await em.count(CustomerDealCompanyLink, {
857
+ company: record,
858
+ })
859
+ const recheckDirectPeople = await em.count(CustomerPersonProfile, {
860
+ company: record,
861
+ ...dependentScope,
862
+ })
863
+ if (recheckPersonLinks > 0 || recheckDealLinks > 0 || recheckDirectPeople > 0) {
864
+ const { translate } = await resolveTranslations()
865
+ throw buildCompanyHasDependentsError(translate, {
866
+ personLinks: recheckPersonLinks,
867
+ dealLinks: recheckDealLinks,
868
+ directPeople: recheckDirectPeople,
869
+ })
870
+ }
871
+
872
+ await em.nativeUpdate(CustomerPersonProfile, { company: record }, { company: null })
873
+ await em.nativeDelete(CustomerDealCompanyLink, { company: record })
874
+ await em.nativeDelete(CustomerActivity, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
875
+ await em.nativeDelete(CustomerInteraction, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
876
+ await em.nativeDelete(CustomerTodoLink, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
877
+ await em.nativeDelete(CustomerCompanyProfile, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
878
+ await em.nativeDelete(CustomerAddress, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
879
+ await em.nativeDelete(CustomerComment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
880
+ await em.nativeDelete(CustomerTagAssignment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId })
881
+ if (profile) {
882
+ await em.nativeDelete(CustomFieldValue, { entityId: COMPANY_ENTITY_ID, recordId: profile.id })
883
+ }
884
+ await em.nativeDelete(CustomFieldValue, { entityId: CUSTOMER_ENTITY_ID, recordId: record.id })
885
+ const txEntity = await findOneWithDecryption(
886
+ em,
887
+ CustomerEntity,
888
+ { id: record.id },
889
+ undefined,
890
+ { tenantId: record.tenantId, organizationId: record.organizationId },
891
+ )
892
+ if (txEntity) em.remove(txEntity)
893
+ })
806
894
 
807
895
  const indexDeletes: QueryIndexEventEntry[] = []
808
896
  const memberUpserts: QueryIndexEventEntry[] = []
@@ -54,6 +54,7 @@ import {
54
54
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
55
55
  import { E } from '#generated/entities.ids.generated'
56
56
  import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
57
+ import { deriveDisplayName, isDerivedDisplayName } from '../lib/displayName'
57
58
  import {
58
59
  loadPersonCompanyLinks,
59
60
  summarizePersonCompanies,
@@ -523,7 +524,10 @@ const createPersonCommand: CommandHandler<PersonCreateInput, { entityId: string;
523
524
  const timezone = normalizeOptionalString(parsed.timezone)
524
525
  const linkedInUrl = normalizeOptionalString(parsed.linkedInUrl)
525
526
  const twitterUrl = normalizeOptionalString(parsed.twitterUrl)
526
- const displayName = parsed.displayName?.trim() ?? ''
527
+ const displayNameInput = parsed.displayName?.trim() ?? ''
528
+ const displayName = displayNameInput.length > 0
529
+ ? displayNameInput
530
+ : deriveDisplayName(firstName, lastName)
527
531
  const nextInteractionName = parsed.nextInteraction?.name ? parsed.nextInteraction.name.trim() : null
528
532
  const nextInteractionRefId = normalizeOptionalString(parsed.nextInteraction?.refId)
529
533
  const nextInteractionIcon = normalizeOptionalString(parsed.nextInteraction?.icon)
@@ -715,6 +719,17 @@ const updatePersonCommand: CommandHandler<PersonUpdateInput, { entityId: string
715
719
  }
716
720
  }
717
721
 
722
+ if (
723
+ parsed.displayName === undefined
724
+ && (parsed.firstName !== undefined || parsed.lastName !== undefined)
725
+ && isDerivedDisplayName(record.displayName, profile.firstName, profile.lastName)
726
+ ) {
727
+ const nextFirst = parsed.firstName !== undefined ? parsed.firstName : profile.firstName
728
+ const nextLast = parsed.lastName !== undefined ? parsed.lastName : profile.lastName
729
+ const derived = deriveDisplayName(nextFirst, nextLast)
730
+ if (derived.length > 0) parsed.displayName = derived
731
+ }
732
+
718
733
  await withAtomicFlush(em, [
719
734
  () => {
720
735
  if (parsed.description !== undefined) record.description = normalizeOptionalString(parsed.description)