@open-mercato/core 0.10.1-develop.7975.1.ada24d305c → 0.10.1-develop.7976.1.c0ee1c7c28

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.
@@ -92,6 +92,9 @@ async function loadPersonCompanyLinkSnapshot(em, id, scope) {
92
92
  }
93
93
  );
94
94
  if (!link) return null;
95
+ return snapshotPersonCompanyLink(link);
96
+ }
97
+ function snapshotPersonCompanyLink(link) {
95
98
  const personId = typeof link.person === "string" ? link.person : link.person.id;
96
99
  const companyId = typeof link.company === "string" ? link.company : link.company.id;
97
100
  return {
@@ -150,8 +153,77 @@ async function clearPrimaryFlagsForPerson(em, person) {
150
153
  { isPrimary: false }
151
154
  );
152
155
  }
156
+ async function restoreCreatePersonCompanyLink(ctx, after, before) {
157
+ const em = ctx.container.resolve("em").fork();
158
+ const scope = { tenantId: after.tenantId, organizationId: after.organizationId };
159
+ const link = await findOneWithDecryption(
160
+ em,
161
+ CustomerPersonCompanyLink,
162
+ { id: after.id, ...scope },
163
+ void 0,
164
+ scope
165
+ );
166
+ if (!link) return;
167
+ const person = await requirePersonEntity(em, after.personEntityId, after.tenantId, after.organizationId);
168
+ const profile = await requirePersonProfile(em, person);
169
+ const profileCompanyId = typeof profile.company === "string" ? profile.company : profile.company?.id ?? null;
170
+ const restorePrimaryAssignment = after.isPrimary && link.isPrimary && profileCompanyId === after.companyEntityId;
171
+ const company = restorePrimaryAssignment && before.profileCompanyId ? await requireCompanyEntity(em, before.profileCompanyId, after.tenantId, after.organizationId) : null;
172
+ const primaryLinkIds = before.primaryLinkIds.filter((id) => id !== link.id);
173
+ await withAtomicFlush(em, [
174
+ async () => {
175
+ if (restorePrimaryAssignment) {
176
+ await em.nativeUpdate(
177
+ CustomerPersonCompanyLink,
178
+ { person, ...scope, id: { $ne: link.id }, deletedAt: null, isPrimary: true },
179
+ { isPrimary: false }
180
+ );
181
+ if (primaryLinkIds.length) {
182
+ await em.nativeUpdate(
183
+ CustomerPersonCompanyLink,
184
+ { person, ...scope, id: { $in: primaryLinkIds }, deletedAt: null },
185
+ { isPrimary: true }
186
+ );
187
+ }
188
+ profile.company = company;
189
+ }
190
+ link.isPrimary = before.link?.isPrimary ?? false;
191
+ link.deletedAt = before.link ? before.link.deletedAt ? new Date(before.link.deletedAt) : null : /* @__PURE__ */ new Date();
192
+ }
193
+ ], { transaction: true });
194
+ const dataEngine = ctx.container.resolve("dataEngine");
195
+ await emitCrudUndoSideEffects({
196
+ dataEngine,
197
+ action: link.deletedAt ? "deleted" : "updated",
198
+ entity: link,
199
+ identifiers: getLinkIdentifiers(link),
200
+ syncOrigin: ctx.syncOrigin,
201
+ events: personCompanyLinkCrudEvents,
202
+ indexer: { entityType: "customers:customer_person_company_link" }
203
+ });
204
+ }
153
205
  const createPersonCompanyLinkCommand = {
154
206
  id: "customers.personCompanyLinks.create",
207
+ async prepare(rawInput, ctx) {
208
+ const parsed = personCompanyLinkCreateSchema.parse(rawInput);
209
+ ensureTenantScope(ctx, parsed.tenantId);
210
+ ensureOrganizationScope(ctx, parsed.organizationId);
211
+ const em = ctx.container.resolve("em").fork();
212
+ const person = await requirePersonEntity(em, parsed.personEntityId, parsed.tenantId, parsed.organizationId);
213
+ const company = await requireCompanyEntity(em, parsed.companyEntityId, parsed.tenantId, parsed.organizationId);
214
+ const profile = await requirePersonProfile(em, person);
215
+ const links = await loadPersonCompanyLinks(em, person);
216
+ const link = links.find(
217
+ (entry) => (typeof entry.company === "string" ? entry.company : entry.company.id) === company.id
218
+ ) ?? await findDeletedPersonCompanyLink(em, person, company);
219
+ return {
220
+ before: {
221
+ link: link ? await loadPersonCompanyLinkSnapshot(em, link.id, parsed) : null,
222
+ primaryLinkIds: links.filter((entry) => entry.isPrimary).map((entry) => entry.id),
223
+ profileCompanyId: typeof profile.company === "string" ? profile.company : profile.company?.id ?? null
224
+ }
225
+ };
226
+ },
155
227
  async execute(rawInput, ctx) {
156
228
  const parsed = personCompanyLinkCreateSchema.parse(rawInput);
157
229
  ensureTenantScope(ctx, parsed.tenantId);
@@ -163,8 +235,14 @@ const createPersonCompanyLinkCommand = {
163
235
  const existingLinks = await loadPersonCompanyLinks(em, person);
164
236
  const makePrimary = Boolean(parsed.isPrimary) || existingLinks.length === 0;
165
237
  const existingLive = existingLinks.find((link2) => (typeof link2.company === "string" ? link2.company : link2.company.id) === company.id) ?? null;
238
+ const createBefore = {
239
+ link: existingLive ? snapshotPersonCompanyLink(existingLive) : null,
240
+ primaryLinkIds: existingLinks.filter((link2) => link2.isPrimary).map((link2) => link2.id),
241
+ profileCompanyId: typeof profile.company === "string" ? profile.company : profile.company?.id ?? null
242
+ };
166
243
  if (existingLive) {
167
- if (makePrimary && !existingLive.isPrimary) {
244
+ const changed = makePrimary && !existingLive.isPrimary;
245
+ if (changed) {
168
246
  await withAtomicFlush(em, [
169
247
  () => clearPrimaryFlagsForPerson(em, person),
170
248
  () => {
@@ -173,13 +251,14 @@ const createPersonCompanyLinkCommand = {
173
251
  }
174
252
  ], { transaction: true });
175
253
  }
176
- return { linkId: existingLive.id, created: false, undeleted: false };
254
+ return { linkId: existingLive.id, created: false, undeleted: false, changed, createBefore };
177
255
  }
178
256
  let link;
179
257
  let undeleted = false;
180
258
  await withAtomicFlush(em, [
181
259
  async () => {
182
260
  const deletedLink = await findDeletedPersonCompanyLink(em, person, company);
261
+ if (deletedLink) createBefore.link = snapshotPersonCompanyLink(deletedLink);
183
262
  if (makePrimary) {
184
263
  await clearPrimaryFlagsForPerson(em, person);
185
264
  }
@@ -219,18 +298,22 @@ const createPersonCompanyLinkCommand = {
219
298
  events: personCompanyLinkCrudEvents,
220
299
  indexer: { entityType: "customers:customer_person_company_link" }
221
300
  });
222
- return { linkId: link.id, created: !undeleted, undeleted };
301
+ return { linkId: link.id, created: !undeleted, undeleted, changed: true, createBefore };
223
302
  },
224
303
  captureAfter: async (_input, result, ctx) => {
225
- const em = ctx.container.resolve("em");
304
+ const em = ctx.container.resolve("em").fork();
226
305
  return loadPersonCompanyLinkSnapshot(em, result.linkId, {
227
306
  tenantId: ctx.auth?.tenantId ?? null,
228
307
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
229
308
  });
230
309
  },
231
310
  buildLog: async ({ result, snapshots }) => {
232
- const { translate } = await resolveTranslations();
311
+ const before = result.createBefore;
233
312
  const after = snapshots.after;
313
+ if (!result.changed) {
314
+ return { skipLog: true };
315
+ }
316
+ const { translate } = await resolveTranslations();
234
317
  return {
235
318
  actionLabel: translate("customers.audit.personCompanyLinks.create", "Link company to person"),
236
319
  resourceKind: "customers.personCompanyLink",
@@ -239,10 +322,12 @@ const createPersonCompanyLinkCommand = {
239
322
  parentResourceId: after?.personEntityId ?? null,
240
323
  tenantId: after?.tenantId ?? null,
241
324
  organizationId: after?.organizationId ?? null,
325
+ snapshotBefore: before?.link ?? null,
242
326
  snapshotAfter: after ?? null,
243
327
  payload: {
244
328
  undo: {
245
329
  after: after ?? null,
330
+ ...before ? { createBefore: before } : {},
246
331
  ...result.undeleted ? { before: null } : {}
247
332
  }
248
333
  }
@@ -252,6 +337,10 @@ const createPersonCompanyLinkCommand = {
252
337
  const payload = extractUndoPayload(logEntry);
253
338
  const after = payload?.after;
254
339
  if (!after) return;
340
+ if (payload.createBefore) {
341
+ await restoreCreatePersonCompanyLink(ctx, after, payload.createBefore);
342
+ return;
343
+ }
255
344
  const em = ctx.container.resolve("em").fork();
256
345
  const link = await findOneWithDecryption(
257
346
  em,
@@ -322,6 +411,12 @@ const createPersonCompanyLinkCommand = {
322
411
  void 0,
323
412
  { tenantId: after.tenantId, organizationId: after.organizationId }
324
413
  );
414
+ const existingLinks = await loadPersonCompanyLinks(em, person);
415
+ const createBefore = {
416
+ link: link ? snapshotPersonCompanyLink(link) : null,
417
+ primaryLinkIds: existingLinks.filter((entry) => entry.isPrimary).map((entry) => entry.id),
418
+ profileCompanyId: typeof profile.company === "string" ? profile.company : profile.company?.id ?? null
419
+ };
325
420
  await withAtomicFlush(em, [
326
421
  async () => {
327
422
  if (after.isPrimary) {
@@ -359,7 +454,7 @@ const createPersonCompanyLinkCommand = {
359
454
  events: personCompanyLinkCrudEvents,
360
455
  indexer: { entityType: "customers:customer_person_company_link" }
361
456
  });
362
- return { linkId: link.id, created: true, undeleted: false };
457
+ return { linkId: link.id, created: true, undeleted: false, changed: true, createBefore };
363
458
  }
364
459
  };
365
460
  const updatePersonCompanyLinkCommand = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/commands/personCompanyLinks.ts"],
4
- "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n CustomerPersonProfile,\n} from '../data/entities'\nimport {\n personCompanyLinkCreateSchema,\n personCompanyLinkDeleteSchema,\n personCompanyLinkUpdateSchema,\n type PersonCompanyLinkCreateInput,\n type PersonCompanyLinkDeleteInput,\n type PersonCompanyLinkUpdateInput,\n} from '../data/validators'\nimport {\n findDeletedPersonCompanyLink,\n loadPersonCompanyLinks,\n promoteFallbackPrimaryLink,\n removePersonCompanyLink,\n} from '../lib/personCompanies'\nimport {\n emitQueryIndexUpsertEvents,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n type QueryIndexEventEntry,\n} from './shared'\nimport type { CustomersEventId } from '../events'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EventBus } from '@open-mercato/events'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers').child({ component: 'personCompanyLinks' })\n\n// Typed against the module's declared event ids so removing or renaming the declaration in\n// `events.ts` fails the build here rather than emitting an undeclared event at runtime.\nconst PROFILE_ONLY_DETACHED_EVENT: CustomersEventId = 'customers.person.company_assignment.detached'\n\ntype PersonCompanyLinkSnapshot = {\n id: string\n personEntityId: string\n companyEntityId: string\n isPrimary: boolean\n tenantId: string\n organizationId: string\n deletedAt: string | null\n}\n\ntype PersonCompanyLinkUndoPayload = {\n before?: PersonCompanyLinkSnapshot | null\n after?: PersonCompanyLinkSnapshot | null\n}\n\n/**\n * Snapshot of a legacy profile-only company assignment \u2014 `customer_person_profiles.company_id`\n * set with no backing link row (#5114). It carries no link id because none exists; `kind`\n * discriminates it from `PersonCompanyLinkSnapshot` in the delete command's undo payload.\n */\ntype PersonCompanyProfileAssignmentSnapshot = {\n kind: 'profile-only'\n personEntityId: string\n companyEntityId: string\n tenantId: string\n organizationId: string\n}\n\ntype PersonCompanyLinkDeleteSnapshot = PersonCompanyLinkSnapshot | PersonCompanyProfileAssignmentSnapshot\n\ntype PersonCompanyLinkDeleteUndoPayload = {\n before?: PersonCompanyLinkDeleteSnapshot | null\n}\n\ntype PersonCompanyLinkDeleteResult = {\n linkId: string | null\n personEntityId: string | null\n companyEntityId: string | null\n}\n\nfunction isProfileAssignmentSnapshot(\n snapshot: PersonCompanyLinkDeleteSnapshot | null | undefined,\n): snapshot is PersonCompanyProfileAssignmentSnapshot {\n return Boolean(snapshot) && (snapshot as PersonCompanyProfileAssignmentSnapshot).kind === 'profile-only'\n}\n\nfunction resolveProfileOnlyTarget(\n parsed: PersonCompanyLinkDeleteInput,\n): { personEntityId: string; companyEntityId: string } | null {\n if (parsed.linkId) return null\n if (!parsed.personEntityId || !parsed.companyEntityId) return null\n return { personEntityId: parsed.personEntityId, companyEntityId: parsed.companyEntityId }\n}\n\nfunction personQueryIndexEntries(\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n): QueryIndexEventEntry[] {\n return [\n {\n entityType: E.customers.customer_entity,\n recordId: person.id,\n tenantId: person.tenantId,\n organizationId: person.organizationId,\n },\n {\n entityType: E.customers.customer_person_profile,\n recordId: profile.id,\n tenantId: profile.tenantId,\n organizationId: profile.organizationId,\n },\n ]\n}\n\nconst personCompanyLinkCrudEvents: CrudEventsConfig = {\n module: 'customers',\n entity: 'person_company_link',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.entity && typeof ctx.entity === 'object' && 'person' in (ctx.entity as Record<string, unknown>)\n ? {\n personEntityId:\n typeof (ctx.entity as CustomerPersonCompanyLink).person === 'string'\n ? (ctx.entity as any).person\n : (ctx.entity as CustomerPersonCompanyLink).person?.id ?? null,\n companyEntityId:\n typeof (ctx.entity as CustomerPersonCompanyLink).company === 'string'\n ? (ctx.entity as any).company\n : (ctx.entity as CustomerPersonCompanyLink).company?.id ?? null,\n }\n : {}),\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }),\n}\n\nfunction getLinkIdentifiers(link: CustomerPersonCompanyLink) {\n return {\n id: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }\n}\n\nasync function loadPersonCompanyLinkSnapshot(\n em: EntityManager,\n id: string,\n scope?: { tenantId?: string | null; organizationId?: string | null },\n): Promise<PersonCompanyLinkSnapshot | null> {\n const filter: Record<string, unknown> = { id }\n if (scope?.tenantId) filter.tenantId = scope.tenantId\n if (scope?.organizationId) filter.organizationId = scope.organizationId\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n filter,\n undefined,\n {\n tenantId: scope?.tenantId ?? null,\n organizationId: scope?.organizationId ?? null,\n },\n )\n if (!link) return null\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n return {\n id: link.id,\n personEntityId: personId,\n companyEntityId: companyId,\n isPrimary: Boolean(link.isPrimary),\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n deletedAt: link.deletedAt ? link.deletedAt.toISOString() : null,\n }\n}\n\nasync function requirePersonEntity(\n em: EntityManager,\n entityId: string,\n tenantId: string,\n organizationId: string,\n): Promise<CustomerEntity> {\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: 'person', tenantId, organizationId, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n if (!person) {\n throw notFound('Person not found')\n }\n return person\n}\n\nasync function requireCompanyEntity(\n em: EntityManager,\n entityId: string,\n tenantId: string,\n organizationId: string,\n): Promise<CustomerEntity> {\n const company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: 'company', tenantId, organizationId, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n if (!company) {\n throw notFound('Company not found')\n }\n return company\n}\n\nasync function requirePersonProfile(\n em: EntityManager,\n person: CustomerEntity,\n): Promise<CustomerPersonProfile> {\n const profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) {\n throw notFound('Person profile not found')\n }\n return profile\n}\n\nasync function clearPrimaryFlagsForPerson(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\nconst createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInput, { linkId: string; created: boolean; undeleted: boolean }> = {\n id: 'customers.personCompanyLinks.create',\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const person = await requirePersonEntity(em, parsed.personEntityId, parsed.tenantId, parsed.organizationId)\n const company = await requireCompanyEntity(em, parsed.companyEntityId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const makePrimary = Boolean(parsed.isPrimary) || existingLinks.length === 0\n const existingLive =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n\n if (existingLive) {\n if (makePrimary && !existingLive.isPrimary) {\n await withAtomicFlush(em, [\n () => clearPrimaryFlagsForPerson(em, person),\n () => {\n existingLive.isPrimary = true\n profile.company = company\n },\n ], { transaction: true })\n }\n return { linkId: existingLive.id, created: false, undeleted: false }\n }\n\n let link!: CustomerPersonCompanyLink\n let undeleted = false\n await withAtomicFlush(em, [\n async () => {\n const deletedLink = await findDeletedPersonCompanyLink(em, person, company)\n if (makePrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n }\n if (deletedLink) {\n deletedLink.deletedAt = null\n deletedLink.isPrimary = makePrimary\n em.persist(deletedLink)\n link = deletedLink\n undeleted = true\n } else {\n link = em.create(CustomerPersonCompanyLink, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n person,\n company,\n isPrimary: makePrimary,\n })\n em.persist(link)\n }\n },\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 ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: undeleted ? 'updated' : 'created',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id, created: !undeleted, undeleted }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = ctx.container.resolve('em') as EntityManager\n return loadPersonCompanyLinkSnapshot(em, result.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as PersonCompanyLinkSnapshot | undefined\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.create', 'Link company to person'),\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: after?.personEntityId ?? null,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: {\n after: after ?? null,\n ...(result.undeleted ? { before: null } : {}),\n } satisfies PersonCompanyLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: after.id },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let remainingLinks: CustomerPersonCompanyLink[] = []\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: after.personEntityId, kind: 'person', tenantId: after.tenantId, organizationId: after.organizationId, deletedAt: null },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) return\n remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)\n },\n async () => {\n link.isPrimary = false\n link.deletedAt = new Date()\n if (!person || !profile) return\n if (after.isPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, after.companyEntityId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === after.companyEntityId) {\n profile.company = null\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<PersonCompanyLinkSnapshot>(logEntry)\n if (!after) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for person-company link create' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const person = await requirePersonEntity(em, after.personEntityId, after.tenantId, after.organizationId)\n const company = await requireCompanyEntity(em, after.companyEntityId, after.tenantId, after.organizationId)\n const profile = await requirePersonProfile(em, person)\n\n let link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: after.id },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n\n await withAtomicFlush(em, [\n async () => {\n if (after.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n }\n if (!link) {\n link = em.create(CustomerPersonCompanyLink, {\n id: after.id,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n person,\n company,\n isPrimary: after.isPrimary,\n })\n em.persist(link)\n } else {\n link.deletedAt = null\n link.isPrimary = after.isPrimary\n em.persist(link)\n }\n },\n () => {\n if (after.isPrimary) {\n profile.company = company\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: link!,\n identifiers: getLinkIdentifiers(link!),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link!.id, created: true, undeleted: false }\n },\n}\n\nconst updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInput, { linkId: string }> = {\n id: 'customers.personCompanyLinks.update',\n async prepare(rawInput, ctx) {\n const parsed = personCompanyLinkUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadPersonCompanyLinkSnapshot(em, parsed.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkUpdateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n id: parsed.linkId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: parsed.tenantId, organizationId: parsed.organizationId },\n )\n if (!link) {\n throw notFound('Company link not found')\n }\n\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n const person = await requirePersonEntity(em, personId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const linkedCompany = await requireCompanyEntity(em, companyId, parsed.tenantId, parsed.organizationId)\n\n const linkWasPrimary = link.isPrimary\n await withAtomicFlush(em, [\n async () => {\n if (parsed.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n profile.company = linkedCompany\n } else if (!parsed.isPrimary) {\n link.isPrimary = false\n if (!linkWasPrimary && profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {\n profile.company = null\n }\n }\n },\n async () => {\n if (!parsed.isPrimary && linkWasPrimary) {\n const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = ctx.container.resolve('em') as EntityManager\n return loadPersonCompanyLinkSnapshot(em, result.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as PersonCompanyLinkSnapshot | undefined\n const after = snapshots.after as PersonCompanyLinkSnapshot | undefined\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.update', 'Update company link'),\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: after?.personEntityId ?? before?.personEntityId ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: {\n before: before ?? null,\n after: after ?? null,\n } satisfies PersonCompanyLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: before.id },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile || !before.isPrimary) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n async () => {\n if (!person || !profile) return\n if (before.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n if (company) profile.company = company\n } else {\n link.isPrimary = false\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n}\n\n/**\n * Detach a legacy profile-only company assignment: `customer_person_profiles.company_id`\n * points at the company but no `customer_person_company_links` row was ever created, so\n * there is no link row for the id-based branch to soft-delete (#5114). Running it as part\n * of the same command keeps the audit entry, the undo token and the CRUD cache\n * invalidation identical to a link-backed detach.\n */\nasync function detachProfileOnlyCompany(\n em: EntityManager,\n ctx: CommandRuntimeContext,\n parsed: PersonCompanyLinkDeleteInput,\n target: { personEntityId: string; companyEntityId: string },\n): Promise<PersonCompanyLinkDeleteResult> {\n const person = await requirePersonEntity(em, target.personEntityId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const assignedCompany = profile.company\n const assignedCompanyId = typeof assignedCompany === 'string' ? assignedCompany : assignedCompany?.id ?? null\n if (assignedCompanyId !== target.companyEntityId) {\n throw notFound('Company link not found')\n }\n\n await withAtomicFlush(em, [\n () => removePersonCompanyLink(em, person, profile, target.companyEntityId),\n ], { transaction: true })\n\n // There is no link entity to hand to `emitCrudSideEffects`; what changed is the\n // person's own company assignment, so reindex the person and its profile instead.\n await emitQueryIndexUpsertEvents(ctx, personQueryIndexEntries(person, profile))\n await emitProfileOnlyDetachedEvent(ctx, person, target.companyEntityId)\n\n return { linkId: null, personEntityId: person.id, companyEntityId: target.companyEntityId }\n}\n\n/**\n * Live-refresh signal for the company People tab and the person Companies tab, which the\n * link-backed branch gets from `customers.person_company_link.deleted` via `emitCrudSideEffects`.\n * The profile-only branch has no link row to emit that for, so it publishes the sibling\n * `clientBroadcast` event instead (#5114). `tenantId` must travel in the payload: the event bus\n * only forwards a broadcast event cross-process when the payload carries a tenant scope. The\n * write is already committed, so a failed refresh signal must never fail the detach.\n */\nasync function emitProfileOnlyDetachedEvent(\n ctx: CommandRuntimeContext,\n person: CustomerEntity,\n companyEntityId: string,\n): Promise<void> {\n try {\n const bus = ctx.container.resolve<EventBus>('eventBus')\n await bus.emitEvent(\n PROFILE_ONLY_DETACHED_EVENT,\n {\n linkId: null,\n personEntityId: person.id,\n companyEntityId,\n tenantId: person.tenantId,\n organizationId: person.organizationId,\n },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n } catch (err) {\n logger.warn('Profile-only company detach broadcast failed', {\n command: 'customers.personCompanyLinks.delete',\n personEntityId: person.id,\n companyEntityId,\n err,\n })\n }\n}\n\n/**\n * Undo counterpart of `detachProfileOnlyCompany`: re-point the person's legacy\n * `profile.company` at the company it was detached from. The lookups run in the first\n * flush phase and the assignment in the second so the encryption subscriber's\n * re-baseline on find cannot drop the pending change (#2507).\n */\nasync function restoreProfileOnlyCompany(\n ctx: CommandRuntimeContext,\n before: PersonCompanyProfileAssignmentSnapshot,\n): Promise<void> {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!profile) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n () => {\n if (profile && company) profile.company = company\n },\n ], { transaction: true })\n\n if (person && profile) {\n await emitQueryIndexUpsertEvents(ctx, personQueryIndexEntries(person, profile))\n }\n}\n\nconst deletePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkDeleteInput, PersonCompanyLinkDeleteResult> = {\n id: 'customers.personCompanyLinks.delete',\n async prepare(rawInput, ctx) {\n const parsed = personCompanyLinkDeleteSchema.parse(rawInput)\n const profileOnlyTarget = resolveProfileOnlyTarget(parsed)\n if (profileOnlyTarget) {\n return {\n before: {\n kind: 'profile-only',\n personEntityId: profileOnlyTarget.personEntityId,\n companyEntityId: profileOnlyTarget.companyEntityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } satisfies PersonCompanyProfileAssignmentSnapshot,\n }\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadPersonCompanyLinkSnapshot(em, parsed.linkId as string, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkDeleteSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const profileOnlyTarget = resolveProfileOnlyTarget(parsed)\n if (profileOnlyTarget) {\n return await detachProfileOnlyCompany(em, ctx, parsed, profileOnlyTarget)\n }\n\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n id: parsed.linkId as string,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: parsed.tenantId, organizationId: parsed.organizationId },\n )\n if (!link) {\n throw notFound('Company link not found')\n }\n\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n const person = await requirePersonEntity(em, personId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const linkWasPrimary = link.isPrimary\n\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n\n await withAtomicFlush(em, [\n () => {\n link.isPrimary = false\n link.deletedAt = new Date()\n },\n async () => {\n if (linkWasPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {\n const primary = remainingLinks.find((entry) => entry.isPrimary) ?? null\n const primaryCompany = primary && typeof primary.company !== 'string' ? primary.company : null\n if (primaryCompany) {\n profile.company = primaryCompany\n }\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id, personEntityId: personId, companyEntityId: companyId }\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as PersonCompanyLinkDeleteSnapshot | undefined\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.delete', 'Remove company link'),\n // Kept as `customers.personCompanyLink` for the profile-only shape too: the company\n // detail cache tags this resource kind, so both detach shapes must invalidate it.\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: before?.personEntityId ?? null,\n tenantId: before?.tenantId ?? null,\n organizationId: before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies PersonCompanyLinkDeleteUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkDeleteUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n\n if (isProfileAssignmentSnapshot(before)) {\n await restoreProfileOnlyCompany(ctx, before)\n return\n }\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: before.id },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person || !before.isPrimary) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n async () => {\n link.deletedAt = null\n link.isPrimary = before.isPrimary\n if (person && before.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n if (profile && company) profile.company = company\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n}\n\nregisterCommand(createPersonCompanyLinkCommand)\nregisterCommand(updatePersonCompanyLinkCommand)\nregisterCommand(deletePersonCompanyLinkCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n CustomerPersonProfile,\n} from '../data/entities'\nimport {\n personCompanyLinkCreateSchema,\n personCompanyLinkDeleteSchema,\n personCompanyLinkUpdateSchema,\n type PersonCompanyLinkCreateInput,\n type PersonCompanyLinkDeleteInput,\n type PersonCompanyLinkUpdateInput,\n} from '../data/validators'\nimport {\n findDeletedPersonCompanyLink,\n loadPersonCompanyLinks,\n promoteFallbackPrimaryLink,\n removePersonCompanyLink,\n} from '../lib/personCompanies'\nimport {\n emitQueryIndexUpsertEvents,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n type QueryIndexEventEntry,\n} from './shared'\nimport type { CustomersEventId } from '../events'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EventBus } from '@open-mercato/events'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers').child({ component: 'personCompanyLinks' })\n\n// Typed against the module's declared event ids so removing or renaming the declaration in\n// `events.ts` fails the build here rather than emitting an undeclared event at runtime.\nconst PROFILE_ONLY_DETACHED_EVENT: CustomersEventId = 'customers.person.company_assignment.detached'\n\ntype PersonCompanyLinkSnapshot = {\n id: string\n personEntityId: string\n companyEntityId: string\n isPrimary: boolean\n tenantId: string\n organizationId: string\n deletedAt: string | null\n}\n\ntype PersonCompanyLinkUndoPayload = {\n before?: PersonCompanyLinkSnapshot | null\n after?: PersonCompanyLinkSnapshot | null\n createBefore?: PersonCompanyLinkCreateBefore\n}\n\ntype PersonCompanyLinkCreateBefore = {\n link: PersonCompanyLinkSnapshot | null\n primaryLinkIds: string[]\n profileCompanyId: string | null\n}\n\ntype PersonCompanyLinkCreateResult = {\n linkId: string\n created: boolean\n undeleted: boolean\n changed: boolean\n createBefore: PersonCompanyLinkCreateBefore\n}\n\n/**\n * Snapshot of a legacy profile-only company assignment \u2014 `customer_person_profiles.company_id`\n * set with no backing link row (#5114). It carries no link id because none exists; `kind`\n * discriminates it from `PersonCompanyLinkSnapshot` in the delete command's undo payload.\n */\ntype PersonCompanyProfileAssignmentSnapshot = {\n kind: 'profile-only'\n personEntityId: string\n companyEntityId: string\n tenantId: string\n organizationId: string\n}\n\ntype PersonCompanyLinkDeleteSnapshot = PersonCompanyLinkSnapshot | PersonCompanyProfileAssignmentSnapshot\n\ntype PersonCompanyLinkDeleteUndoPayload = {\n before?: PersonCompanyLinkDeleteSnapshot | null\n}\n\ntype PersonCompanyLinkDeleteResult = {\n linkId: string | null\n personEntityId: string | null\n companyEntityId: string | null\n}\n\nfunction isProfileAssignmentSnapshot(\n snapshot: PersonCompanyLinkDeleteSnapshot | null | undefined,\n): snapshot is PersonCompanyProfileAssignmentSnapshot {\n return Boolean(snapshot) && (snapshot as PersonCompanyProfileAssignmentSnapshot).kind === 'profile-only'\n}\n\nfunction resolveProfileOnlyTarget(\n parsed: PersonCompanyLinkDeleteInput,\n): { personEntityId: string; companyEntityId: string } | null {\n if (parsed.linkId) return null\n if (!parsed.personEntityId || !parsed.companyEntityId) return null\n return { personEntityId: parsed.personEntityId, companyEntityId: parsed.companyEntityId }\n}\n\nfunction personQueryIndexEntries(\n person: CustomerEntity,\n profile: CustomerPersonProfile,\n): QueryIndexEventEntry[] {\n return [\n {\n entityType: E.customers.customer_entity,\n recordId: person.id,\n tenantId: person.tenantId,\n organizationId: person.organizationId,\n },\n {\n entityType: E.customers.customer_person_profile,\n recordId: profile.id,\n tenantId: profile.tenantId,\n organizationId: profile.organizationId,\n },\n ]\n}\n\nconst personCompanyLinkCrudEvents: CrudEventsConfig = {\n module: 'customers',\n entity: 'person_company_link',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.entity && typeof ctx.entity === 'object' && 'person' in (ctx.entity as Record<string, unknown>)\n ? {\n personEntityId:\n typeof (ctx.entity as CustomerPersonCompanyLink).person === 'string'\n ? (ctx.entity as any).person\n : (ctx.entity as CustomerPersonCompanyLink).person?.id ?? null,\n companyEntityId:\n typeof (ctx.entity as CustomerPersonCompanyLink).company === 'string'\n ? (ctx.entity as any).company\n : (ctx.entity as CustomerPersonCompanyLink).company?.id ?? null,\n }\n : {}),\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }),\n}\n\nfunction getLinkIdentifiers(link: CustomerPersonCompanyLink) {\n return {\n id: link.id,\n organizationId: link.organizationId,\n tenantId: link.tenantId,\n }\n}\n\nasync function loadPersonCompanyLinkSnapshot(\n em: EntityManager,\n id: string,\n scope?: { tenantId?: string | null; organizationId?: string | null },\n): Promise<PersonCompanyLinkSnapshot | null> {\n const filter: Record<string, unknown> = { id }\n if (scope?.tenantId) filter.tenantId = scope.tenantId\n if (scope?.organizationId) filter.organizationId = scope.organizationId\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n filter,\n undefined,\n {\n tenantId: scope?.tenantId ?? null,\n organizationId: scope?.organizationId ?? null,\n },\n )\n if (!link) return null\n return snapshotPersonCompanyLink(link)\n}\n\nfunction snapshotPersonCompanyLink(link: CustomerPersonCompanyLink): PersonCompanyLinkSnapshot {\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n return {\n id: link.id,\n personEntityId: personId,\n companyEntityId: companyId,\n isPrimary: Boolean(link.isPrimary),\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n deletedAt: link.deletedAt ? link.deletedAt.toISOString() : null,\n }\n}\n\nasync function requirePersonEntity(\n em: EntityManager,\n entityId: string,\n tenantId: string,\n organizationId: string,\n): Promise<CustomerEntity> {\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: 'person', tenantId, organizationId, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n if (!person) {\n throw notFound('Person not found')\n }\n return person\n}\n\nasync function requireCompanyEntity(\n em: EntityManager,\n entityId: string,\n tenantId: string,\n organizationId: string,\n): Promise<CustomerEntity> {\n const company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: 'company', tenantId, organizationId, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n if (!company) {\n throw notFound('Company not found')\n }\n return company\n}\n\nasync function requirePersonProfile(\n em: EntityManager,\n person: CustomerEntity,\n): Promise<CustomerPersonProfile> {\n const profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) {\n throw notFound('Person profile not found')\n }\n return profile\n}\n\nasync function clearPrimaryFlagsForPerson(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\nasync function restoreCreatePersonCompanyLink(\n ctx: CommandRuntimeContext,\n after: PersonCompanyLinkSnapshot,\n before: PersonCompanyLinkCreateBefore,\n): Promise<void> {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = { tenantId: after.tenantId, organizationId: after.organizationId }\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: after.id, ...scope },\n undefined,\n scope,\n )\n if (!link) return\n const person = await requirePersonEntity(em, after.personEntityId, after.tenantId, after.organizationId)\n const profile = await requirePersonProfile(em, person)\n const profileCompanyId = typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null\n const restorePrimaryAssignment = after.isPrimary && link.isPrimary && profileCompanyId === after.companyEntityId\n const company = restorePrimaryAssignment && before.profileCompanyId\n ? await requireCompanyEntity(em, before.profileCompanyId, after.tenantId, after.organizationId)\n : null\n const primaryLinkIds = before.primaryLinkIds.filter((id) => id !== link.id)\n\n await withAtomicFlush(em, [\n async () => {\n if (restorePrimaryAssignment) {\n await em.nativeUpdate(\n CustomerPersonCompanyLink,\n { person, ...scope, id: { $ne: link.id }, deletedAt: null, isPrimary: true },\n { isPrimary: false },\n )\n if (primaryLinkIds.length) {\n await em.nativeUpdate(\n CustomerPersonCompanyLink,\n { person, ...scope, id: { $in: primaryLinkIds }, deletedAt: null },\n { isPrimary: true },\n )\n }\n profile.company = company\n }\n link.isPrimary = before.link?.isPrimary ?? false\n link.deletedAt = before.link\n ? before.link.deletedAt ? new Date(before.link.deletedAt) : null\n : new Date()\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: link.deletedAt ? 'deleted' : 'updated',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n}\n\nconst createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInput, PersonCompanyLinkCreateResult> = {\n id: 'customers.personCompanyLinks.create',\n async prepare(rawInput, ctx) {\n const parsed = personCompanyLinkCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const person = await requirePersonEntity(em, parsed.personEntityId, parsed.tenantId, parsed.organizationId)\n const company = await requireCompanyEntity(em, parsed.companyEntityId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const links = await loadPersonCompanyLinks(em, person)\n const link = links.find((entry) =>\n (typeof entry.company === 'string' ? entry.company : entry.company.id) === company.id,\n ) ?? await findDeletedPersonCompanyLink(em, person, company)\n return {\n before: {\n link: link ? await loadPersonCompanyLinkSnapshot(em, link.id, parsed) : null,\n primaryLinkIds: links.filter((entry) => entry.isPrimary).map((entry) => entry.id),\n profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,\n } satisfies PersonCompanyLinkCreateBefore,\n }\n },\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const person = await requirePersonEntity(em, parsed.personEntityId, parsed.tenantId, parsed.organizationId)\n const company = await requireCompanyEntity(em, parsed.companyEntityId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const makePrimary = Boolean(parsed.isPrimary) || existingLinks.length === 0\n const existingLive =\n existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null\n // This state must come from execute's decision, not prepare's earlier read: another\n // request may have created the link between prepare and execute.\n const createBefore: PersonCompanyLinkCreateBefore = {\n link: existingLive ? snapshotPersonCompanyLink(existingLive) : null,\n primaryLinkIds: existingLinks.filter((link) => link.isPrimary).map((link) => link.id),\n profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,\n }\n\n if (existingLive) {\n const changed = makePrimary && !existingLive.isPrimary\n if (changed) {\n await withAtomicFlush(em, [\n () => clearPrimaryFlagsForPerson(em, person),\n () => {\n existingLive.isPrimary = true\n profile.company = company\n },\n ], { transaction: true })\n }\n return { linkId: existingLive.id, created: false, undeleted: false, changed, createBefore }\n }\n\n let link!: CustomerPersonCompanyLink\n let undeleted = false\n await withAtomicFlush(em, [\n async () => {\n const deletedLink = await findDeletedPersonCompanyLink(em, person, company)\n if (deletedLink) createBefore.link = snapshotPersonCompanyLink(deletedLink)\n if (makePrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n }\n if (deletedLink) {\n deletedLink.deletedAt = null\n deletedLink.isPrimary = makePrimary\n em.persist(deletedLink)\n link = deletedLink\n undeleted = true\n } else {\n link = em.create(CustomerPersonCompanyLink, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n person,\n company,\n isPrimary: makePrimary,\n })\n em.persist(link)\n }\n },\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 ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: undeleted ? 'updated' : 'created',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id, created: !undeleted, undeleted, changed: true, createBefore }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadPersonCompanyLinkSnapshot(em, result.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n },\n buildLog: async ({ result, snapshots }) => {\n const before = result.createBefore\n const after = snapshots.after as PersonCompanyLinkSnapshot | undefined\n if (!result.changed) {\n return { skipLog: true }\n }\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.create', 'Link company to person'),\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: after?.personEntityId ?? null,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotBefore: before?.link ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: {\n after: after ?? null,\n ...(before ? { createBefore: before } : {}),\n ...(result.undeleted ? { before: null } : {}),\n } satisfies PersonCompanyLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n\n if (payload.createBefore) {\n await restoreCreatePersonCompanyLink(ctx, after, payload.createBefore)\n return\n }\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: after.id },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let remainingLinks: CustomerPersonCompanyLink[] = []\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: after.personEntityId, kind: 'person', tenantId: after.tenantId, organizationId: after.organizationId, deletedAt: null },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) return\n remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)\n },\n async () => {\n link.isPrimary = false\n link.deletedAt = new Date()\n if (!person || !profile) return\n if (after.isPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, after.companyEntityId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === after.companyEntityId) {\n profile.company = null\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<PersonCompanyLinkSnapshot>(logEntry)\n if (!after) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for person-company link create' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const person = await requirePersonEntity(em, after.personEntityId, after.tenantId, after.organizationId)\n const company = await requireCompanyEntity(em, after.companyEntityId, after.tenantId, after.organizationId)\n const profile = await requirePersonProfile(em, person)\n\n let link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: after.id },\n undefined,\n { tenantId: after.tenantId, organizationId: after.organizationId },\n )\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const createBefore: PersonCompanyLinkCreateBefore = {\n link: link ? snapshotPersonCompanyLink(link) : null,\n primaryLinkIds: existingLinks.filter((entry) => entry.isPrimary).map((entry) => entry.id),\n profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,\n }\n\n await withAtomicFlush(em, [\n async () => {\n if (after.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n }\n if (!link) {\n link = em.create(CustomerPersonCompanyLink, {\n id: after.id,\n organizationId: after.organizationId,\n tenantId: after.tenantId,\n person,\n company,\n isPrimary: after.isPrimary,\n })\n em.persist(link)\n } else {\n link.deletedAt = null\n link.isPrimary = after.isPrimary\n em.persist(link)\n }\n },\n () => {\n if (after.isPrimary) {\n profile.company = company\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: link!,\n identifiers: getLinkIdentifiers(link!),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link!.id, created: true, undeleted: false, changed: true, createBefore }\n },\n}\n\nconst updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInput, { linkId: string }> = {\n id: 'customers.personCompanyLinks.update',\n async prepare(rawInput, ctx) {\n const parsed = personCompanyLinkUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadPersonCompanyLinkSnapshot(em, parsed.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkUpdateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n id: parsed.linkId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: parsed.tenantId, organizationId: parsed.organizationId },\n )\n if (!link) {\n throw notFound('Company link not found')\n }\n\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n const person = await requirePersonEntity(em, personId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const linkedCompany = await requireCompanyEntity(em, companyId, parsed.tenantId, parsed.organizationId)\n\n const linkWasPrimary = link.isPrimary\n await withAtomicFlush(em, [\n async () => {\n if (parsed.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n profile.company = linkedCompany\n } else if (!parsed.isPrimary) {\n link.isPrimary = false\n if (!linkWasPrimary && profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {\n profile.company = null\n }\n }\n },\n async () => {\n if (!parsed.isPrimary && linkWasPrimary) {\n const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = ctx.container.resolve('em') as EntityManager\n return loadPersonCompanyLinkSnapshot(em, result.linkId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as PersonCompanyLinkSnapshot | undefined\n const after = snapshots.after as PersonCompanyLinkSnapshot | undefined\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.update', 'Update company link'),\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: after?.personEntityId ?? before?.personEntityId ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: {\n before: before ?? null,\n after: after ?? null,\n } satisfies PersonCompanyLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: before.id },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile || !before.isPrimary) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n async () => {\n if (!person || !profile) return\n if (before.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n if (company) profile.company = company\n } else {\n link.isPrimary = false\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n}\n\n/**\n * Detach a legacy profile-only company assignment: `customer_person_profiles.company_id`\n * points at the company but no `customer_person_company_links` row was ever created, so\n * there is no link row for the id-based branch to soft-delete (#5114). Running it as part\n * of the same command keeps the audit entry, the undo token and the CRUD cache\n * invalidation identical to a link-backed detach.\n */\nasync function detachProfileOnlyCompany(\n em: EntityManager,\n ctx: CommandRuntimeContext,\n parsed: PersonCompanyLinkDeleteInput,\n target: { personEntityId: string; companyEntityId: string },\n): Promise<PersonCompanyLinkDeleteResult> {\n const person = await requirePersonEntity(em, target.personEntityId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const assignedCompany = profile.company\n const assignedCompanyId = typeof assignedCompany === 'string' ? assignedCompany : assignedCompany?.id ?? null\n if (assignedCompanyId !== target.companyEntityId) {\n throw notFound('Company link not found')\n }\n\n await withAtomicFlush(em, [\n () => removePersonCompanyLink(em, person, profile, target.companyEntityId),\n ], { transaction: true })\n\n // There is no link entity to hand to `emitCrudSideEffects`; what changed is the\n // person's own company assignment, so reindex the person and its profile instead.\n await emitQueryIndexUpsertEvents(ctx, personQueryIndexEntries(person, profile))\n await emitProfileOnlyDetachedEvent(ctx, person, target.companyEntityId)\n\n return { linkId: null, personEntityId: person.id, companyEntityId: target.companyEntityId }\n}\n\n/**\n * Live-refresh signal for the company People tab and the person Companies tab, which the\n * link-backed branch gets from `customers.person_company_link.deleted` via `emitCrudSideEffects`.\n * The profile-only branch has no link row to emit that for, so it publishes the sibling\n * `clientBroadcast` event instead (#5114). `tenantId` must travel in the payload: the event bus\n * only forwards a broadcast event cross-process when the payload carries a tenant scope. The\n * write is already committed, so a failed refresh signal must never fail the detach.\n */\nasync function emitProfileOnlyDetachedEvent(\n ctx: CommandRuntimeContext,\n person: CustomerEntity,\n companyEntityId: string,\n): Promise<void> {\n try {\n const bus = ctx.container.resolve<EventBus>('eventBus')\n await bus.emitEvent(\n PROFILE_ONLY_DETACHED_EVENT,\n {\n linkId: null,\n personEntityId: person.id,\n companyEntityId,\n tenantId: person.tenantId,\n organizationId: person.organizationId,\n },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n } catch (err) {\n logger.warn('Profile-only company detach broadcast failed', {\n command: 'customers.personCompanyLinks.delete',\n personEntityId: person.id,\n companyEntityId,\n err,\n })\n }\n}\n\n/**\n * Undo counterpart of `detachProfileOnlyCompany`: re-point the person's legacy\n * `profile.company` at the company it was detached from. The lookups run in the first\n * flush phase and the assignment in the second so the encryption subscriber's\n * re-baseline on find cannot drop the pending change (#2507).\n */\nasync function restoreProfileOnlyCompany(\n ctx: CommandRuntimeContext,\n before: PersonCompanyProfileAssignmentSnapshot,\n): Promise<void> {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!profile) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n () => {\n if (profile && company) profile.company = company\n },\n ], { transaction: true })\n\n if (person && profile) {\n await emitQueryIndexUpsertEvents(ctx, personQueryIndexEntries(person, profile))\n }\n}\n\nconst deletePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkDeleteInput, PersonCompanyLinkDeleteResult> = {\n id: 'customers.personCompanyLinks.delete',\n async prepare(rawInput, ctx) {\n const parsed = personCompanyLinkDeleteSchema.parse(rawInput)\n const profileOnlyTarget = resolveProfileOnlyTarget(parsed)\n if (profileOnlyTarget) {\n return {\n before: {\n kind: 'profile-only',\n personEntityId: profileOnlyTarget.personEntityId,\n companyEntityId: profileOnlyTarget.companyEntityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } satisfies PersonCompanyProfileAssignmentSnapshot,\n }\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadPersonCompanyLinkSnapshot(em, parsed.linkId as string, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = personCompanyLinkDeleteSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const profileOnlyTarget = resolveProfileOnlyTarget(parsed)\n if (profileOnlyTarget) {\n return await detachProfileOnlyCompany(em, ctx, parsed, profileOnlyTarget)\n }\n\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n {\n id: parsed.linkId as string,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: parsed.tenantId, organizationId: parsed.organizationId },\n )\n if (!link) {\n throw notFound('Company link not found')\n }\n\n const personId = typeof link.person === 'string' ? link.person : link.person.id\n const companyId = typeof link.company === 'string' ? link.company : link.company.id\n const person = await requirePersonEntity(em, personId, parsed.tenantId, parsed.organizationId)\n const profile = await requirePersonProfile(em, person)\n const linkWasPrimary = link.isPrimary\n\n const existingLinks = await loadPersonCompanyLinks(em, person)\n const remainingLinks = existingLinks.filter((entry) => entry.id !== link.id)\n\n await withAtomicFlush(em, [\n () => {\n link.isPrimary = false\n link.deletedAt = new Date()\n },\n async () => {\n if (linkWasPrimary) {\n await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)\n } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {\n const primary = remainingLinks.find((entry) => entry.isPrimary) ?? null\n const primaryCompany = primary && typeof primary.company !== 'string' ? primary.company : null\n if (primaryCompany) {\n profile.company = primaryCompany\n }\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n\n return { linkId: link.id, personEntityId: personId, companyEntityId: companyId }\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as PersonCompanyLinkDeleteSnapshot | undefined\n return {\n actionLabel: translate('customers.audit.personCompanyLinks.delete', 'Remove company link'),\n // Kept as `customers.personCompanyLink` for the profile-only shape too: the company\n // detail cache tags this resource kind, so both detach shapes must invalidate it.\n resourceKind: 'customers.personCompanyLink',\n resourceId: result.linkId,\n parentResourceKind: 'customers.person',\n parentResourceId: before?.personEntityId ?? null,\n tenantId: before?.tenantId ?? null,\n organizationId: before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies PersonCompanyLinkDeleteUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<PersonCompanyLinkDeleteUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n\n if (isProfileAssignmentSnapshot(before)) {\n await restoreProfileOnlyCompany(ctx, before)\n return\n }\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n { id: before.id },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!link) return\n\n let person: CustomerEntity | null = null\n let profile: CustomerPersonProfile | null = null\n let company: CustomerEntity | null = null\n\n await withAtomicFlush(em, [\n async () => {\n person = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n if (!person || !before.isPrimary) return\n profile = await findOneWithDecryption(\n em,\n CustomerPersonProfile,\n { entity: person },\n { populate: ['company'] },\n { tenantId: person.tenantId, organizationId: person.organizationId },\n )\n if (!profile) return\n company = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n },\n async () => {\n link.deletedAt = null\n link.isPrimary = before.isPrimary\n if (person && before.isPrimary) {\n await clearPrimaryFlagsForPerson(em, person)\n link.isPrimary = true\n if (profile && company) profile.company = company\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: link,\n identifiers: getLinkIdentifiers(link),\n syncOrigin: ctx.syncOrigin,\n events: personCompanyLinkCrudEvents,\n indexer: { entityType: 'customers:customer_person_company_link' },\n })\n },\n}\n\nregisterCommand(createPersonCompanyLinkCommand)\nregisterCommand(updatePersonCompanyLinkCommand)\nregisterCommand(deletePersonCompanyLinkCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": ["link"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7975.1.ada24d305c",
3
+ "version": "0.10.1-develop.7976.1.c0ee1c7c28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.6.6"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7975.1.ada24d305c",
270
- "@open-mercato/shared": "0.10.1-develop.7975.1.ada24d305c",
271
- "@open-mercato/ui": "0.10.1-develop.7975.1.ada24d305c",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7976.1.c0ee1c7c28",
270
+ "@open-mercato/shared": "0.10.1-develop.7976.1.c0ee1c7c28",
271
+ "@open-mercato/ui": "0.10.1-develop.7976.1.c0ee1c7c28",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7975.1.ada24d305c",
277
- "@open-mercato/shared": "0.10.1-develop.7975.1.ada24d305c",
278
- "@open-mercato/ui": "0.10.1-develop.7975.1.ada24d305c",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7976.1.c0ee1c7c28",
277
+ "@open-mercato/shared": "0.10.1-develop.7976.1.c0ee1c7c28",
278
+ "@open-mercato/ui": "0.10.1-develop.7976.1.c0ee1c7c28",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -59,6 +59,21 @@ type PersonCompanyLinkSnapshot = {
59
59
  type PersonCompanyLinkUndoPayload = {
60
60
  before?: PersonCompanyLinkSnapshot | null
61
61
  after?: PersonCompanyLinkSnapshot | null
62
+ createBefore?: PersonCompanyLinkCreateBefore
63
+ }
64
+
65
+ type PersonCompanyLinkCreateBefore = {
66
+ link: PersonCompanyLinkSnapshot | null
67
+ primaryLinkIds: string[]
68
+ profileCompanyId: string | null
69
+ }
70
+
71
+ type PersonCompanyLinkCreateResult = {
72
+ linkId: string
73
+ created: boolean
74
+ undeleted: boolean
75
+ changed: boolean
76
+ createBefore: PersonCompanyLinkCreateBefore
62
77
  }
63
78
 
64
79
  /**
@@ -171,6 +186,10 @@ async function loadPersonCompanyLinkSnapshot(
171
186
  },
172
187
  )
173
188
  if (!link) return null
189
+ return snapshotPersonCompanyLink(link)
190
+ }
191
+
192
+ function snapshotPersonCompanyLink(link: CustomerPersonCompanyLink): PersonCompanyLinkSnapshot {
174
193
  const personId = typeof link.person === 'string' ? link.person : link.person.id
175
194
  const companyId = typeof link.company === 'string' ? link.company : link.company.id
176
195
  return {
@@ -247,8 +266,88 @@ async function clearPrimaryFlagsForPerson(em: EntityManager, person: CustomerEnt
247
266
  )
248
267
  }
249
268
 
250
- const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInput, { linkId: string; created: boolean; undeleted: boolean }> = {
269
+ async function restoreCreatePersonCompanyLink(
270
+ ctx: CommandRuntimeContext,
271
+ after: PersonCompanyLinkSnapshot,
272
+ before: PersonCompanyLinkCreateBefore,
273
+ ): Promise<void> {
274
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
275
+ const scope = { tenantId: after.tenantId, organizationId: after.organizationId }
276
+ const link = await findOneWithDecryption(
277
+ em,
278
+ CustomerPersonCompanyLink,
279
+ { id: after.id, ...scope },
280
+ undefined,
281
+ scope,
282
+ )
283
+ if (!link) return
284
+ const person = await requirePersonEntity(em, after.personEntityId, after.tenantId, after.organizationId)
285
+ const profile = await requirePersonProfile(em, person)
286
+ const profileCompanyId = typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null
287
+ const restorePrimaryAssignment = after.isPrimary && link.isPrimary && profileCompanyId === after.companyEntityId
288
+ const company = restorePrimaryAssignment && before.profileCompanyId
289
+ ? await requireCompanyEntity(em, before.profileCompanyId, after.tenantId, after.organizationId)
290
+ : null
291
+ const primaryLinkIds = before.primaryLinkIds.filter((id) => id !== link.id)
292
+
293
+ await withAtomicFlush(em, [
294
+ async () => {
295
+ if (restorePrimaryAssignment) {
296
+ await em.nativeUpdate(
297
+ CustomerPersonCompanyLink,
298
+ { person, ...scope, id: { $ne: link.id }, deletedAt: null, isPrimary: true },
299
+ { isPrimary: false },
300
+ )
301
+ if (primaryLinkIds.length) {
302
+ await em.nativeUpdate(
303
+ CustomerPersonCompanyLink,
304
+ { person, ...scope, id: { $in: primaryLinkIds }, deletedAt: null },
305
+ { isPrimary: true },
306
+ )
307
+ }
308
+ profile.company = company
309
+ }
310
+ link.isPrimary = before.link?.isPrimary ?? false
311
+ link.deletedAt = before.link
312
+ ? before.link.deletedAt ? new Date(before.link.deletedAt) : null
313
+ : new Date()
314
+ },
315
+ ], { transaction: true })
316
+
317
+ const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
318
+ await emitCrudUndoSideEffects({
319
+ dataEngine,
320
+ action: link.deletedAt ? 'deleted' : 'updated',
321
+ entity: link,
322
+ identifiers: getLinkIdentifiers(link),
323
+ syncOrigin: ctx.syncOrigin,
324
+ events: personCompanyLinkCrudEvents,
325
+ indexer: { entityType: 'customers:customer_person_company_link' },
326
+ })
327
+ }
328
+
329
+ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInput, PersonCompanyLinkCreateResult> = {
251
330
  id: 'customers.personCompanyLinks.create',
331
+ async prepare(rawInput, ctx) {
332
+ const parsed = personCompanyLinkCreateSchema.parse(rawInput)
333
+ ensureTenantScope(ctx, parsed.tenantId)
334
+ ensureOrganizationScope(ctx, parsed.organizationId)
335
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
336
+ const person = await requirePersonEntity(em, parsed.personEntityId, parsed.tenantId, parsed.organizationId)
337
+ const company = await requireCompanyEntity(em, parsed.companyEntityId, parsed.tenantId, parsed.organizationId)
338
+ const profile = await requirePersonProfile(em, person)
339
+ const links = await loadPersonCompanyLinks(em, person)
340
+ const link = links.find((entry) =>
341
+ (typeof entry.company === 'string' ? entry.company : entry.company.id) === company.id,
342
+ ) ?? await findDeletedPersonCompanyLink(em, person, company)
343
+ return {
344
+ before: {
345
+ link: link ? await loadPersonCompanyLinkSnapshot(em, link.id, parsed) : null,
346
+ primaryLinkIds: links.filter((entry) => entry.isPrimary).map((entry) => entry.id),
347
+ profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,
348
+ } satisfies PersonCompanyLinkCreateBefore,
349
+ }
350
+ },
252
351
  async execute(rawInput, ctx) {
253
352
  const parsed = personCompanyLinkCreateSchema.parse(rawInput)
254
353
  ensureTenantScope(ctx, parsed.tenantId)
@@ -263,9 +362,17 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
263
362
  const makePrimary = Boolean(parsed.isPrimary) || existingLinks.length === 0
264
363
  const existingLive =
265
364
  existingLinks.find((link) => (typeof link.company === 'string' ? link.company : link.company.id) === company.id) ?? null
365
+ // This state must come from execute's decision, not prepare's earlier read: another
366
+ // request may have created the link between prepare and execute.
367
+ const createBefore: PersonCompanyLinkCreateBefore = {
368
+ link: existingLive ? snapshotPersonCompanyLink(existingLive) : null,
369
+ primaryLinkIds: existingLinks.filter((link) => link.isPrimary).map((link) => link.id),
370
+ profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,
371
+ }
266
372
 
267
373
  if (existingLive) {
268
- if (makePrimary && !existingLive.isPrimary) {
374
+ const changed = makePrimary && !existingLive.isPrimary
375
+ if (changed) {
269
376
  await withAtomicFlush(em, [
270
377
  () => clearPrimaryFlagsForPerson(em, person),
271
378
  () => {
@@ -274,7 +381,7 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
274
381
  },
275
382
  ], { transaction: true })
276
383
  }
277
- return { linkId: existingLive.id, created: false, undeleted: false }
384
+ return { linkId: existingLive.id, created: false, undeleted: false, changed, createBefore }
278
385
  }
279
386
 
280
387
  let link!: CustomerPersonCompanyLink
@@ -282,6 +389,7 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
282
389
  await withAtomicFlush(em, [
283
390
  async () => {
284
391
  const deletedLink = await findDeletedPersonCompanyLink(em, person, company)
392
+ if (deletedLink) createBefore.link = snapshotPersonCompanyLink(deletedLink)
285
393
  if (makePrimary) {
286
394
  await clearPrimaryFlagsForPerson(em, person)
287
395
  }
@@ -323,18 +431,22 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
323
431
  indexer: { entityType: 'customers:customer_person_company_link' },
324
432
  })
325
433
 
326
- return { linkId: link.id, created: !undeleted, undeleted }
434
+ return { linkId: link.id, created: !undeleted, undeleted, changed: true, createBefore }
327
435
  },
328
436
  captureAfter: async (_input, result, ctx) => {
329
- const em = ctx.container.resolve('em') as EntityManager
437
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
330
438
  return loadPersonCompanyLinkSnapshot(em, result.linkId, {
331
439
  tenantId: ctx.auth?.tenantId ?? null,
332
440
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
333
441
  })
334
442
  },
335
443
  buildLog: async ({ result, snapshots }) => {
336
- const { translate } = await resolveTranslations()
444
+ const before = result.createBefore
337
445
  const after = snapshots.after as PersonCompanyLinkSnapshot | undefined
446
+ if (!result.changed) {
447
+ return { skipLog: true }
448
+ }
449
+ const { translate } = await resolveTranslations()
338
450
  return {
339
451
  actionLabel: translate('customers.audit.personCompanyLinks.create', 'Link company to person'),
340
452
  resourceKind: 'customers.personCompanyLink',
@@ -343,10 +455,12 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
343
455
  parentResourceId: after?.personEntityId ?? null,
344
456
  tenantId: after?.tenantId ?? null,
345
457
  organizationId: after?.organizationId ?? null,
458
+ snapshotBefore: before?.link ?? null,
346
459
  snapshotAfter: after ?? null,
347
460
  payload: {
348
461
  undo: {
349
462
  after: after ?? null,
463
+ ...(before ? { createBefore: before } : {}),
350
464
  ...(result.undeleted ? { before: null } : {}),
351
465
  } satisfies PersonCompanyLinkUndoPayload,
352
466
  },
@@ -357,6 +471,11 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
357
471
  const after = payload?.after
358
472
  if (!after) return
359
473
 
474
+ if (payload.createBefore) {
475
+ await restoreCreatePersonCompanyLink(ctx, after, payload.createBefore)
476
+ return
477
+ }
478
+
360
479
  const em = (ctx.container.resolve('em') as EntityManager).fork()
361
480
  const link = await findOneWithDecryption(
362
481
  em,
@@ -431,6 +550,12 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
431
550
  undefined,
432
551
  { tenantId: after.tenantId, organizationId: after.organizationId },
433
552
  )
553
+ const existingLinks = await loadPersonCompanyLinks(em, person)
554
+ const createBefore: PersonCompanyLinkCreateBefore = {
555
+ link: link ? snapshotPersonCompanyLink(link) : null,
556
+ primaryLinkIds: existingLinks.filter((entry) => entry.isPrimary).map((entry) => entry.id),
557
+ profileCompanyId: typeof profile.company === 'string' ? profile.company : profile.company?.id ?? null,
558
+ }
434
559
 
435
560
  await withAtomicFlush(em, [
436
561
  async () => {
@@ -471,7 +596,7 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
471
596
  indexer: { entityType: 'customers:customer_person_company_link' },
472
597
  })
473
598
 
474
- return { linkId: link!.id, created: true, undeleted: false }
599
+ return { linkId: link!.id, created: true, undeleted: false, changed: true, createBefore }
475
600
  },
476
601
  }
477
602