@open-mercato/core 0.6.7-develop.6552.1.5208a19369 → 0.6.7-develop.6565.1.1026d85fff

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/lib/reconcileOrganization.js +9 -7
  3. package/dist/modules/attachments/lib/reconcileOrganization.js.map +2 -2
  4. package/dist/modules/customers/api/companies/[id]/people/route.js +2 -2
  5. package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
  6. package/dist/modules/customers/api/deals/[id]/companies/route.js +2 -2
  7. package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
  8. package/dist/modules/customers/api/deals/[id]/people/route.js +2 -2
  9. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  10. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +4 -4
  11. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  12. package/dist/modules/customers/api/entity-roles-factory.js +3 -3
  13. package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
  14. package/dist/modules/customers/api/labels/assign/route.js +2 -2
  15. package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
  16. package/dist/modules/customers/api/labels/unassign/route.js +2 -2
  17. package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
  18. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +3 -7
  19. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
  20. package/dist/modules/customers/api/people/[id]/companies/context.js +3 -3
  21. package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
  22. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +2 -2
  23. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
  24. package/dist/modules/customers/api/todos/route.js +2 -2
  25. package/dist/modules/customers/api/todos/route.js.map +2 -2
  26. package/dist/modules/customers/commands/addresses.js +3 -3
  27. package/dist/modules/customers/commands/addresses.js.map +2 -2
  28. package/dist/modules/customers/commands/comments.js +3 -3
  29. package/dist/modules/customers/commands/comments.js.map +2 -2
  30. package/dist/modules/customers/commands/companies.js +2 -2
  31. package/dist/modules/customers/commands/companies.js.map +2 -2
  32. package/dist/modules/customers/commands/deals.js +3 -3
  33. package/dist/modules/customers/commands/deals.js.map +2 -2
  34. package/dist/modules/customers/commands/dictionaries.js +3 -3
  35. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  36. package/dist/modules/customers/commands/entity-roles.js +3 -3
  37. package/dist/modules/customers/commands/entity-roles.js.map +2 -2
  38. package/dist/modules/customers/commands/interactions.js +6 -6
  39. package/dist/modules/customers/commands/interactions.js.map +2 -2
  40. package/dist/modules/customers/commands/labels.js +7 -7
  41. package/dist/modules/customers/commands/labels.js.map +2 -2
  42. package/dist/modules/customers/commands/people.js +3 -3
  43. package/dist/modules/customers/commands/people.js.map +2 -2
  44. package/dist/modules/customers/commands/personCompanyLinks.js +6 -6
  45. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  46. package/dist/modules/customers/commands/pipeline-stages.js +3 -3
  47. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  48. package/dist/modules/customers/commands/pipelines.js +3 -3
  49. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  50. package/dist/modules/customers/commands/shared.js +3 -3
  51. package/dist/modules/customers/commands/shared.js.map +2 -2
  52. package/dist/modules/customers/commands/tags.js +7 -7
  53. package/dist/modules/customers/commands/tags.js.map +2 -2
  54. package/dist/modules/customers/commands/todos.js +3 -3
  55. package/dist/modules/customers/commands/todos.js.map +2 -2
  56. package/dist/modules/customers/lib/personCompanies.js +4 -4
  57. package/dist/modules/customers/lib/personCompanies.js.map +2 -2
  58. package/dist/modules/perspectives/data/validators.js +4 -0
  59. package/dist/modules/perspectives/data/validators.js.map +2 -2
  60. package/dist/modules/sales/api/notes/route.js +2 -4
  61. package/dist/modules/sales/api/notes/route.js.map +2 -2
  62. package/dist/modules/sales/api/quotes/accept/route.js +2 -2
  63. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  64. package/dist/modules/sales/api/quotes/public/[token]/route.js +3 -7
  65. package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
  66. package/dist/modules/sales/api/quotes/send/route.js +2 -2
  67. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  68. package/dist/modules/sales/api/returns/[id]/route.js +2 -2
  69. package/dist/modules/sales/api/returns/[id]/route.js.map +2 -2
  70. package/dist/modules/sales/commands/configuration.js +11 -11
  71. package/dist/modules/sales/commands/configuration.js.map +2 -2
  72. package/dist/modules/sales/commands/documentAddresses.js +3 -3
  73. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  74. package/dist/modules/sales/commands/documents.js +31 -39
  75. package/dist/modules/sales/commands/documents.js.map +2 -2
  76. package/dist/modules/sales/commands/notes.js +7 -7
  77. package/dist/modules/sales/commands/notes.js.map +2 -2
  78. package/dist/modules/sales/commands/payments.js +2 -2
  79. package/dist/modules/sales/commands/payments.js.map +2 -2
  80. package/dist/modules/sales/commands/returns.js +8 -8
  81. package/dist/modules/sales/commands/returns.js.map +2 -2
  82. package/dist/modules/sales/commands/shared.js +2 -2
  83. package/dist/modules/sales/commands/shared.js.map +2 -2
  84. package/dist/modules/sales/commands/shipments.js +5 -5
  85. package/dist/modules/sales/commands/shipments.js.map +2 -2
  86. package/dist/modules/sales/commands/statuses.js +2 -2
  87. package/dist/modules/sales/commands/statuses.js.map +2 -2
  88. package/dist/modules/sales/commands/tags.js +3 -3
  89. package/dist/modules/sales/commands/tags.js.map +2 -2
  90. package/dist/modules/sales/lib/calculations.js +6 -0
  91. package/dist/modules/sales/lib/calculations.js.map +2 -2
  92. package/package.json +7 -7
  93. package/src/modules/attachments/lib/reconcileOrganization.ts +16 -7
  94. package/src/modules/customer_accounts/i18n/de.json +12 -0
  95. package/src/modules/customer_accounts/i18n/en.json +12 -0
  96. package/src/modules/customer_accounts/i18n/es.json +34 -22
  97. package/src/modules/customer_accounts/i18n/pl.json +32 -20
  98. package/src/modules/customers/api/companies/[id]/people/route.ts +2 -2
  99. package/src/modules/customers/api/deals/[id]/companies/route.ts +2 -2
  100. package/src/modules/customers/api/deals/[id]/people/route.ts +2 -2
  101. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +4 -4
  102. package/src/modules/customers/api/entity-roles-factory.ts +3 -3
  103. package/src/modules/customers/api/labels/assign/route.ts +2 -2
  104. package/src/modules/customers/api/labels/unassign/route.ts +2 -2
  105. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +3 -7
  106. package/src/modules/customers/api/people/[id]/companies/context.ts +3 -3
  107. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +2 -2
  108. package/src/modules/customers/api/todos/route.ts +2 -2
  109. package/src/modules/customers/commands/addresses.ts +3 -3
  110. package/src/modules/customers/commands/comments.ts +3 -3
  111. package/src/modules/customers/commands/companies.ts +2 -2
  112. package/src/modules/customers/commands/deals.ts +3 -3
  113. package/src/modules/customers/commands/dictionaries.ts +3 -3
  114. package/src/modules/customers/commands/entity-roles.ts +3 -3
  115. package/src/modules/customers/commands/interactions.ts +6 -6
  116. package/src/modules/customers/commands/labels.ts +7 -7
  117. package/src/modules/customers/commands/people.ts +3 -3
  118. package/src/modules/customers/commands/personCompanyLinks.ts +6 -6
  119. package/src/modules/customers/commands/pipeline-stages.ts +3 -3
  120. package/src/modules/customers/commands/pipelines.ts +3 -3
  121. package/src/modules/customers/commands/shared.ts +3 -3
  122. package/src/modules/customers/commands/tags.ts +7 -7
  123. package/src/modules/customers/commands/todos.ts +3 -3
  124. package/src/modules/customers/lib/personCompanies.ts +4 -4
  125. package/src/modules/perspectives/data/validators.ts +4 -0
  126. package/src/modules/sales/api/notes/route.ts +2 -4
  127. package/src/modules/sales/api/quotes/accept/route.ts +2 -2
  128. package/src/modules/sales/api/quotes/public/[token]/route.ts +3 -7
  129. package/src/modules/sales/api/quotes/send/route.ts +2 -2
  130. package/src/modules/sales/api/returns/[id]/route.ts +2 -2
  131. package/src/modules/sales/commands/configuration.ts +11 -11
  132. package/src/modules/sales/commands/documentAddresses.ts +3 -3
  133. package/src/modules/sales/commands/documents.ts +23 -31
  134. package/src/modules/sales/commands/notes.ts +7 -7
  135. package/src/modules/sales/commands/payments.ts +2 -2
  136. package/src/modules/sales/commands/returns.ts +8 -8
  137. package/src/modules/sales/commands/shared.ts +2 -2
  138. package/src/modules/sales/commands/shipments.ts +5 -5
  139. package/src/modules/sales/commands/statuses.ts +2 -2
  140. package/src/modules/sales/commands/tags.ts +3 -3
  141. package/src/modules/sales/lib/calculations.ts +15 -0
@@ -1,5 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
2
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import {
@@ -35,7 +35,7 @@ export async function loadPersonContext(req: Request, personId: string) {
35
35
  )
36
36
 
37
37
  if (!person) {
38
- throw new CrudHttpError(404, { error: translate('customers.errors.person_not_found', 'Person not found') })
38
+ throw notFound(translate('customers.errors.person_not_found', 'Person not found'))
39
39
  }
40
40
 
41
41
  if (!isOrganizationReadAccessAllowed({ scope, auth: authenticatedAuth, organizationId: person.organizationId })) {
@@ -53,7 +53,7 @@ export async function loadPersonContext(req: Request, personId: string) {
53
53
  },
54
54
  )
55
55
  if (!profile) {
56
- throw new CrudHttpError(404, { error: translate('customers.errors.person_profile_not_found', 'Person profile not found') })
56
+ throw notFound(translate('customers.errors.person_profile_not_found', 'Person profile not found'))
57
57
  }
58
58
 
59
59
  return {
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
- import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
+ import { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
7
7
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
@@ -174,7 +174,7 @@ export async function GET(req: Request, ctx: { params?: { id?: string } }) {
174
174
  const decryptionScope = { tenantId: auth.tenantId, organizationId: auth.orgId ?? null }
175
175
  const person = await findOneWithDecryption(em, CustomerEntity, { id, kind: 'person', tenantId: auth.tenantId, deletedAt: null }, {}, decryptionScope)
176
176
  if (!person) {
177
- throw new CrudHttpError(404, { error: translate('customers.errors.person_not_found', 'Person not found') })
177
+ throw notFound(translate('customers.errors.person_not_found', 'Person not found'))
178
178
  }
179
179
 
180
180
  if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: person.organizationId })) {
@@ -7,7 +7,7 @@ import { z } from 'zod'
7
7
  import type { EntityManager } from '@mikro-orm/postgresql'
8
8
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
9
9
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
10
- import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
+ import { isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
11
11
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
12
12
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
13
13
  import {
@@ -218,7 +218,7 @@ async function resolveCanonicalTodoTargetId(
218
218
 
219
219
  const legacyLink = await findLegacyTodoLink(em, target, tenantId)
220
220
  if (!legacyLink) {
221
- if (!target.todoId) throw new CrudHttpError(404, { error: 'Todo not found' })
221
+ if (!target.todoId) throw notFound('Todo not found')
222
222
  return target.todoId
223
223
  }
224
224
 
@@ -14,7 +14,7 @@ import {
14
14
  resolveParentResourceKind,
15
15
  } from './shared'
16
16
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
17
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
17
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
18
18
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
19
19
  import { E } from '#generated/entities.ids.generated'
20
20
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
@@ -288,7 +288,7 @@ const updateAddressCommand: CommandHandler<AddressUpdateInput, { addressId: stri
288
288
  const parsed = addressUpdateSchema.parse(rawInput)
289
289
  const em = (ctx.container.resolve('em') as EntityManager).fork()
290
290
  const address = await em.findOne(CustomerAddress, { id: parsed.id })
291
- if (!address) throw new CrudHttpError(404, { error: 'Address not found' })
291
+ if (!address) throw notFound('Address not found')
292
292
  ensureTenantScope(ctx, address.tenantId)
293
293
  ensureOrganizationScope(ctx, address.organizationId)
294
294
 
@@ -477,7 +477,7 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
477
477
  const id = requireId(input, 'Address id required')
478
478
  const em = (ctx.container.resolve('em') as EntityManager).fork()
479
479
  const address = await em.findOne(CustomerAddress, { id })
480
- if (!address) throw new CrudHttpError(404, { error: 'Address not found' })
480
+ if (!address) throw notFound('Address not found')
481
481
  ensureTenantScope(ctx, address.tenantId)
482
482
  ensureOrganizationScope(ctx, address.organizationId)
483
483
  em.remove(address)
@@ -15,7 +15,7 @@ import {
15
15
  resolveParentResourceKind,
16
16
  } from './shared'
17
17
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
18
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
18
+ import { notFound } from '@open-mercato/shared/lib/crud/errors'
19
19
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
20
20
  import { E } from '#generated/entities.ids.generated'
21
21
  import { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'
@@ -196,7 +196,7 @@ const updateCommentCommand: CommandHandler<CommentUpdateInput, { commentId: stri
196
196
  const parsed = commentUpdateSchema.parse(rawInput)
197
197
  const em = (ctx.container.resolve('em') as EntityManager).fork()
198
198
  const comment = await em.findOne(CustomerComment, { id: parsed.id })
199
- if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })
199
+ if (!comment) throw notFound('Comment not found')
200
200
  ensureTenantScope(ctx, comment.tenantId)
201
201
  ensureOrganizationScope(ctx, comment.organizationId)
202
202
 
@@ -332,7 +332,7 @@ const deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; que
332
332
  const id = requireId(input, 'Comment id required')
333
333
  const em = (ctx.container.resolve('em') as EntityManager).fork()
334
334
  const comment = await em.findOne(CustomerComment, { id })
335
- if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })
335
+ if (!comment) throw notFound('Comment not found')
336
336
  ensureTenantScope(ctx, comment.tenantId)
337
337
  ensureOrganizationScope(ctx, comment.organizationId)
338
338
  em.remove(comment)
@@ -11,7 +11,7 @@ import {
11
11
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
12
12
  import type { EntityManager } from '@mikro-orm/postgresql'
13
13
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
14
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
14
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
15
15
  import {
16
16
  CustomerAddress,
17
17
  CustomerComment,
@@ -738,7 +738,7 @@ const updateCompanyCommand: CommandHandler<CompanyUpdateInput, { entityId: strin
738
738
  ensureTenantScope(ctx, record.tenantId)
739
739
  ensureOrganizationScope(ctx, record.organizationId)
740
740
  const profile = await em.findOne(CustomerCompanyProfile, { entity: record })
741
- if (!profile) throw new CrudHttpError(404, { error: 'Company profile not found' })
741
+ if (!profile) throw notFound('Company profile not found')
742
742
 
743
743
  await withAtomicFlush(em, [
744
744
  () => {
@@ -40,7 +40,7 @@ import {
40
40
  buildCustomFieldResetMap,
41
41
  type CustomFieldChangeSet,
42
42
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
43
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
43
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
44
44
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
45
45
  import { E } from '#generated/entities.ids.generated'
46
46
  import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -625,7 +625,7 @@ const updateDealCommand: CommandHandler<DealUpdateInput, { dealId: string }> = {
625
625
  const em = (ctx.container.resolve('em') as EntityManager).fork()
626
626
  const deal = await findOneWithDecryption(em, CustomerDeal, { id: parsed.id, deletedAt: null })
627
627
  const record = deal ?? null
628
- if (!record) throw new CrudHttpError(404, { error: 'Deal not found' })
628
+ if (!record) throw notFound('Deal not found')
629
629
  ensureTenantScope(ctx, record.tenantId)
630
630
  ensureOrganizationScope(ctx, record.organizationId)
631
631
 
@@ -936,7 +936,7 @@ const deleteDealCommand: CommandHandler<{ body?: Record<string, unknown>; query?
936
936
  const em = (ctx.container.resolve('em') as EntityManager).fork()
937
937
  const deal = await findOneWithDecryption(em, CustomerDeal, { id, deletedAt: null })
938
938
  const record = deal ?? null
939
- if (!record) throw new CrudHttpError(404, { error: 'Deal not found' })
939
+ if (!record) throw notFound('Deal not found')
940
940
  ensureTenantScope(ctx, record.tenantId)
941
941
  ensureOrganizationScope(ctx, record.organizationId)
942
942
  await deleteDealStageTransitions(em, record)
@@ -1,7 +1,7 @@
1
1
  import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
3
3
  import { buildChanges } from '@open-mercato/shared/lib/commands/helpers'
4
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
5
5
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
6
  import type { EntityManager } from '@mikro-orm/postgresql'
7
7
  import { invalidateDictionaryCache } from '../api/dictionaries/cache'
@@ -425,7 +425,7 @@ const updateDictionaryEntryCommand: CommandHandler<CustomerDictionaryEntryUpdate
425
425
  },
426
426
  )
427
427
  if (!entry || entry.organizationId !== parsed.organizationId || entry.tenantId !== parsed.tenantId || entry.kind !== parsed.kind) {
428
- throw new CrudHttpError(404, { error: 'Dictionary entry not found' })
428
+ throw notFound('Dictionary entry not found')
429
429
  }
430
430
 
431
431
  let changed = false
@@ -611,7 +611,7 @@ const deleteDictionaryEntryCommand: CommandHandler<CustomerDictionaryEntryDelete
611
611
  },
612
612
  )
613
613
  if (!entry || entry.organizationId !== parsed.organizationId || entry.tenantId !== parsed.tenantId || entry.kind !== parsed.kind) {
614
- throw new CrudHttpError(404, { error: 'Dictionary entry not found' })
614
+ throw notFound('Dictionary entry not found')
615
615
  }
616
616
  if (entry.kind === 'person_company_role') {
617
617
  const usage = await loadRoleTypeUsage(em, {
@@ -2,7 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandHandler } from '@open-mercato/shared/lib/commands'
3
3
  import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
4
4
  import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
5
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
8
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
@@ -345,7 +345,7 @@ const updateEntityRoleCommand: CommandHandler<EntityRoleUpdateInput, { roleId: s
345
345
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
346
346
  )
347
347
  if (!role) {
348
- throw new CrudHttpError(404, { error: 'Role not found' })
348
+ throw notFound('Role not found')
349
349
  }
350
350
 
351
351
  role.userId = parsed.userId
@@ -448,7 +448,7 @@ const deleteEntityRoleCommand: CommandHandler<EntityRoleDeleteInput, { roleId: s
448
448
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
449
449
  )
450
450
  if (!role) {
451
- throw new CrudHttpError(404, { error: 'Role not found' })
451
+ throw notFound('Role not found')
452
452
  }
453
453
 
454
454
  role.deletedAt = new Date()
@@ -38,7 +38,7 @@ import {
38
38
  loadCustomFieldSnapshot,
39
39
  buildCustomFieldResetMap,
40
40
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
41
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
41
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
42
42
  import { enforceRecordGoneIsConflict, enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
43
43
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
44
44
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
@@ -642,7 +642,7 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
642
642
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
643
643
  if (!interaction) {
644
644
  enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
645
- throw new CrudHttpError(404, { error: 'Interaction not found' })
645
+ throw notFound('Interaction not found')
646
646
  }
647
647
  ensureTenantScope(ctx, interaction.tenantId)
648
648
  ensureOrganizationScope(ctx, interaction.organizationId)
@@ -684,7 +684,7 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
684
684
  userFeatures: undefined,
685
685
  })
686
686
  ) {
687
- throw new CrudHttpError(404, { error: 'Email not found' })
687
+ throw notFound('Email not found')
688
688
  }
689
689
  }
690
690
 
@@ -915,7 +915,7 @@ const completeInteractionCommand: CommandHandler<InteractionCompleteInput, { int
915
915
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
916
916
  if (!interaction) {
917
917
  enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
918
- throw new CrudHttpError(404, { error: 'Interaction not found' })
918
+ throw notFound('Interaction not found')
919
919
  }
920
920
  ensureTenantScope(ctx, interaction.tenantId)
921
921
  ensureOrganizationScope(ctx, interaction.organizationId)
@@ -1055,7 +1055,7 @@ const cancelInteractionCommand: CommandHandler<InteractionCancelInput, { interac
1055
1055
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
1056
1056
  if (!interaction) {
1057
1057
  enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
1058
- throw new CrudHttpError(404, { error: 'Interaction not found' })
1058
+ throw notFound('Interaction not found')
1059
1059
  }
1060
1060
  ensureTenantScope(ctx, interaction.tenantId)
1061
1061
  ensureOrganizationScope(ctx, interaction.organizationId)
@@ -1193,7 +1193,7 @@ const deleteInteractionCommand: CommandHandler<{ body?: Record<string, unknown>;
1193
1193
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id, deletedAt: null })
1194
1194
  if (!interaction) {
1195
1195
  enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: id, request: ctx.request ?? null })
1196
- throw new CrudHttpError(404, { error: 'Interaction not found' })
1196
+ throw notFound('Interaction not found')
1197
1197
  }
1198
1198
  ensureTenantScope(ctx, interaction.tenantId)
1199
1199
  ensureOrganizationScope(ctx, interaction.organizationId)
@@ -2,7 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandHandler } from '@open-mercato/shared/lib/commands'
3
3
  import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
4
4
  import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
5
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
8
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
@@ -287,7 +287,7 @@ const assignLabelCommand: CommandHandler<LabelAssignCommandInput, { assignmentId
287
287
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
288
288
  )
289
289
  if (!label) {
290
- throw new CrudHttpError(404, { error: 'Label not found' })
290
+ throw notFound('Label not found')
291
291
  }
292
292
 
293
293
  const entity = await findOneWithDecryption(
@@ -298,7 +298,7 @@ const assignLabelCommand: CommandHandler<LabelAssignCommandInput, { assignmentId
298
298
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
299
299
  )
300
300
  if (!entity) {
301
- throw new CrudHttpError(404, { error: 'Entity not found' })
301
+ throw notFound('Entity not found')
302
302
  }
303
303
 
304
304
  const existing = await findOneWithDecryption(
@@ -441,7 +441,7 @@ const assignLabelCommand: CommandHandler<LabelAssignCommandInput, { assignmentId
441
441
  { tenantId: after.tenantId, organizationId: after.organizationId },
442
442
  )
443
443
  if (!label) {
444
- throw new CrudHttpError(404, { error: 'Label not found' })
444
+ throw notFound('Label not found')
445
445
  }
446
446
  const entity = await findOneWithDecryption(
447
447
  em,
@@ -451,7 +451,7 @@ const assignLabelCommand: CommandHandler<LabelAssignCommandInput, { assignmentId
451
451
  { tenantId: after.tenantId, organizationId: after.organizationId },
452
452
  )
453
453
  if (!entity) {
454
- throw new CrudHttpError(404, { error: 'Entity not found' })
454
+ throw notFound('Entity not found')
455
455
  }
456
456
 
457
457
  let assignment = await findOneWithDecryption(
@@ -570,7 +570,7 @@ const unassignLabelCommand: CommandHandler<LabelUnassignCommandInput, { assignme
570
570
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
571
571
  )
572
572
  if (!label) {
573
- throw new CrudHttpError(404, { error: 'Label not found' })
573
+ throw notFound('Label not found')
574
574
  }
575
575
 
576
576
  const entity = await findOneWithDecryption(
@@ -581,7 +581,7 @@ const unassignLabelCommand: CommandHandler<LabelUnassignCommandInput, { assignme
581
581
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
582
582
  )
583
583
  if (!entity) {
584
- throw new CrudHttpError(404, { error: 'Entity not found' })
584
+ throw notFound('Entity not found')
585
585
  }
586
586
 
587
587
  const existing = await findOneWithDecryption(
@@ -46,7 +46,7 @@ import {
46
46
  } from './shared'
47
47
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
48
48
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
49
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
49
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
50
50
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
51
51
  import {
52
52
  loadCustomFieldSnapshot,
@@ -851,7 +851,7 @@ const createPersonCommand: CommandHandler<PersonCreateInput, { entityId: string;
851
851
  undefined,
852
852
  { tenantId: after.entity.tenantId, organizationId: after.entity.organizationId },
853
853
  )
854
- if (!existingProfile) throw new CrudHttpError(404, { error: 'Person profile not found' })
854
+ if (!existingProfile) throw notFound('Person profile not found')
855
855
  profile = existingProfile
856
856
  const survivingEntity = entity
857
857
  await withAtomicFlush(em, [
@@ -927,7 +927,7 @@ const updatePersonCommand: CommandHandler<PersonUpdateInput, { entityId: string
927
927
  ensureTenantScope(ctx, record.tenantId)
928
928
  ensureOrganizationScope(ctx, record.organizationId)
929
929
  const profile = await em.findOne(CustomerPersonProfile, { entity: record })
930
- if (!profile) throw new CrudHttpError(404, { error: 'Person profile not found' })
930
+ if (!profile) throw notFound('Person profile not found')
931
931
 
932
932
  if (parsed.displayName !== undefined) {
933
933
  const nextDisplayName = parsed.displayName.trim()
@@ -2,7 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandHandler } from '@open-mercato/shared/lib/commands'
3
3
  import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
4
4
  import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
5
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
8
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
@@ -126,7 +126,7 @@ async function requirePersonEntity(
126
126
  { tenantId, organizationId },
127
127
  )
128
128
  if (!person) {
129
- throw new CrudHttpError(404, { error: 'Person not found' })
129
+ throw notFound('Person not found')
130
130
  }
131
131
  return person
132
132
  }
@@ -145,7 +145,7 @@ async function requireCompanyEntity(
145
145
  { tenantId, organizationId },
146
146
  )
147
147
  if (!company) {
148
- throw new CrudHttpError(404, { error: 'Company not found' })
148
+ throw notFound('Company not found')
149
149
  }
150
150
  return company
151
151
  }
@@ -162,7 +162,7 @@ async function requirePersonProfile(
162
162
  { tenantId: person.tenantId, organizationId: person.organizationId },
163
163
  )
164
164
  if (!profile) {
165
- throw new CrudHttpError(404, { error: 'Person profile not found' })
165
+ throw notFound('Person profile not found')
166
166
  }
167
167
  return profile
168
168
  }
@@ -433,7 +433,7 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
433
433
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
434
434
  )
435
435
  if (!link) {
436
- throw new CrudHttpError(404, { error: 'Company link not found' })
436
+ throw notFound('Company link not found')
437
437
  }
438
438
 
439
439
  const personId = typeof link.person === 'string' ? link.person : link.person.id
@@ -606,7 +606,7 @@ const deletePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkDeleteInpu
606
606
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
607
607
  )
608
608
  if (!link) {
609
- throw new CrudHttpError(404, { error: 'Company link not found' })
609
+ throw notFound('Company link not found')
610
610
  }
611
611
 
612
612
  const personId = typeof link.person === 'string' ? link.person : link.person.id
@@ -14,7 +14,7 @@ import {
14
14
  type PipelineStageReorderInput,
15
15
  } from '../data/validators'
16
16
  import { ensureOrganizationScope, ensureTenantScope, ensureDictionaryEntry } from './shared'
17
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
17
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
18
18
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
19
19
  import {
20
20
  enforceCommandOptimisticLockWithGuards,
@@ -112,7 +112,7 @@ const updatePipelineStageCommand: CommandHandler<PipelineStageUpdateInput, void>
112
112
  })
113
113
  if (!stage) {
114
114
  enforceRecordGoneIsConflict({ resourceKind: 'customers.pipelineStage', resourceId: parsed.id, request: ctx.request ?? null })
115
- throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
115
+ throw notFound('Pipeline stage not found')
116
116
  }
117
117
 
118
118
  ensureTenantScope(ctx, stage.tenantId)
@@ -164,7 +164,7 @@ const deletePipelineStageCommand: CommandHandler<PipelineStageDeleteInput, void>
164
164
  })
165
165
  if (!stage) {
166
166
  enforceRecordGoneIsConflict({ resourceKind: 'customers.pipelineStage', resourceId: parsed.id, request: ctx.request ?? null })
167
- throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
167
+ throw notFound('Pipeline stage not found')
168
168
  }
169
169
 
170
170
  ensureTenantScope(ctx, stage.tenantId)
@@ -11,7 +11,7 @@ import {
11
11
  type PipelineDeleteInput,
12
12
  } from '../data/validators'
13
13
  import { ensureOrganizationScope, ensureTenantScope } from './shared'
14
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
14
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
15
15
  import {
16
16
  enforceCommandOptimisticLockWithGuards,
17
17
  enforceRecordGoneIsConflict,
@@ -62,7 +62,7 @@ const updatePipelineCommand: CommandHandler<PipelineUpdateInput, void> = {
62
62
  const pipeline = await em.findOne(CustomerPipeline, { id: parsed.id })
63
63
  if (!pipeline) {
64
64
  enforceRecordGoneIsConflict({ resourceKind: 'customers.pipeline', resourceId: parsed.id, request: ctx.request ?? null })
65
- throw new CrudHttpError(404, { error: 'Pipeline not found' })
65
+ throw notFound('Pipeline not found')
66
66
  }
67
67
 
68
68
  ensureTenantScope(ctx, pipeline.tenantId)
@@ -102,7 +102,7 @@ const deletePipelineCommand: CommandHandler<PipelineDeleteInput, void> = {
102
102
  const pipeline = await em.findOne(CustomerPipeline, { id: parsed.id })
103
103
  if (!pipeline) {
104
104
  enforceRecordGoneIsConflict({ resourceKind: 'customers.pipeline', resourceId: parsed.id, request: ctx.request ?? null })
105
- throw new CrudHttpError(404, { error: 'Pipeline not found' })
105
+ throw notFound('Pipeline not found')
106
106
  }
107
107
 
108
108
  ensureTenantScope(ctx, pipeline.tenantId)
@@ -1,6 +1,6 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { CustomerDeal, CustomerEntity, CustomerTag, CustomerTagAssignment, CustomerDictionaryEntry, type CustomerEntityKind } from '../data/entities'
3
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
4
4
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
5
5
  import { ensureOrganizationScope, ensureSameScope } from '@open-mercato/shared/lib/commands/scope'
6
6
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -47,7 +47,7 @@ export async function requireCustomerEntity(
47
47
  tenantId: scope.tenantId,
48
48
  organizationId: scope.organizationId,
49
49
  })
50
- if (!entity) throw new CrudHttpError(404, { error: message })
50
+ if (!entity) throw notFound(message)
51
51
  if (kind && entity.kind !== kind) {
52
52
  throw new CrudHttpError(400, { error: 'Invalid entity type' })
53
53
  }
@@ -80,7 +80,7 @@ export async function requireTimelineParentEntity(
80
80
  if (deal) {
81
81
  throw new CrudHttpError(422, { error: 'entityId must reference a person or company, not a deal' })
82
82
  }
83
- throw new CrudHttpError(404, { error: 'Customer not found' })
83
+ throw notFound('Customer not found')
84
84
  }
85
85
 
86
86
  export async function syncEntityTags(
@@ -21,7 +21,7 @@ import {
21
21
  loadEntityTagIds,
22
22
  } from './shared'
23
23
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
24
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
24
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
25
25
  import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
26
26
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
27
27
  import { emitCustomersEvent } from '../events'
@@ -172,7 +172,7 @@ const updateTagCommand: CommandHandler<TagUpdateInput, { tagId: string }> = {
172
172
  const parsed = tagUpdateSchema.parse(rawInput)
173
173
  const em = (ctx.container.resolve('em') as EntityManager).fork()
174
174
  const tag = await em.findOne(CustomerTag, { id: parsed.id })
175
- if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
175
+ if (!tag) throw notFound('Tag not found')
176
176
  ensureTenantScope(ctx, tag.tenantId)
177
177
  ensureOrganizationScope(ctx, tag.organizationId)
178
178
 
@@ -290,7 +290,7 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
290
290
  const id = requireId(input, 'Tag id required')
291
291
  const em = (ctx.container.resolve('em') as EntityManager).fork()
292
292
  const tag = await em.findOne(CustomerTag, { id })
293
- if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
293
+ if (!tag) throw notFound('Tag not found')
294
294
  ensureTenantScope(ctx, tag.tenantId)
295
295
  ensureOrganizationScope(ctx, tag.organizationId)
296
296
  await withAtomicFlush(em, [
@@ -386,7 +386,7 @@ const assignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: strin
386
386
 
387
387
  const em = (ctx.container.resolve('em') as EntityManager).fork()
388
388
  const tag = await em.findOne(CustomerTag, { id: parsed.tagId, tenantId: parsed.tenantId, organizationId: parsed.organizationId })
389
- if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
389
+ if (!tag) throw notFound('Tag not found')
390
390
  const entity = await requireCustomerEntity(em, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId }, undefined, 'Customer not found')
391
391
  ensureSameScope(entity, parsed.organizationId, parsed.tenantId)
392
392
  const tagIds = await loadEntityTagIds(em, entity)
@@ -477,7 +477,7 @@ const assignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: strin
477
477
  const assignmentId = logEntry?.resourceId ?? null
478
478
  const em = (ctx.container.resolve('em') as EntityManager).fork()
479
479
  const tag = await em.findOne(CustomerTag, { id: before.tagId })
480
- if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
480
+ if (!tag) throw notFound('Tag not found')
481
481
  const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')
482
482
  ensureSameScope(entity, before.organizationId, before.tenantId)
483
483
 
@@ -537,7 +537,7 @@ const unassignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: str
537
537
  tenantId: parsed.tenantId,
538
538
  organizationId: parsed.organizationId,
539
539
  })
540
- if (!existing) throw new CrudHttpError(404, { error: 'Tag assignment not found' })
540
+ if (!existing) throw notFound('Tag assignment not found')
541
541
  await em.remove(existing).flush()
542
542
 
543
543
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
@@ -591,7 +591,7 @@ const unassignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: str
591
591
  const tag = await em.findOne(CustomerTag, { id: before.tagId })
592
592
  const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')
593
593
  ensureSameScope(entity, before.organizationId, before.tenantId)
594
- if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
594
+ if (!tag) throw notFound('Tag not found')
595
595
  const existing = await em.findOne(CustomerTagAssignment, {
596
596
  tag,
597
597
  entity,
@@ -16,7 +16,7 @@ import {
16
16
  resolveParentResourceKind,
17
17
  } from './shared'
18
18
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
19
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
19
+ import { notFound } from '@open-mercato/shared/lib/crud/errors'
20
20
  import {
21
21
  CUSTOMER_INTERACTION_TASK_SOURCE,
22
22
  CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,
@@ -290,7 +290,7 @@ async function resolveTodoTarget(
290
290
  const em = (ctx.container.resolve('em') as EntityManager).fork()
291
291
  const legacyLink = await em.findOne(CustomerTodoLink, { id: linkId }, { populate: ['entity'] })
292
292
  if (!legacyLink) {
293
- throw new CrudHttpError(404, { error: 'Todo link not found' })
293
+ throw notFound('Todo link not found')
294
294
  }
295
295
 
296
296
  const bridgedSnapshot = await loadInteractionSnapshot(legacyLink.todoId, ctx)
@@ -333,7 +333,7 @@ const unlinkTodoCommand: CommandHandler<
333
333
  const target = await resolveTodoTarget(parsed.linkId, ctx)
334
334
  if (!target.canonicalExists) {
335
335
  if (!target.legacyLink) {
336
- throw new CrudHttpError(404, { error: 'Todo link not found' })
336
+ throw notFound('Todo link not found')
337
337
  }
338
338
  const canonicalCreate = getRequiredHandler<
339
339
  InteractionCreateInput & { customValues?: Record<string, unknown> },
@@ -1,5 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
2
+ import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
3
3
  import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
4
  import {
5
5
  CustomerEntity,
@@ -48,7 +48,7 @@ async function requireCompany(
48
48
  ): Promise<CustomerEntity> {
49
49
  const company = await findOneWithDecryption(em, CustomerEntity, { id: companyId, kind: 'company', deletedAt: null }, {}, { tenantId, organizationId })
50
50
  if (!company) {
51
- throw new CrudHttpError(404, { error: 'Company not found' })
51
+ throw notFound('Company not found')
52
52
  }
53
53
  if (company.organizationId !== organizationId || company.tenantId !== tenantId) {
54
54
  throw new CrudHttpError(403, { error: 'Cannot link company outside current scope' })
@@ -260,7 +260,7 @@ export async function updatePersonCompanyLink(
260
260
  }
261
261
 
262
262
  if (!link) {
263
- throw new CrudHttpError(404, { error: 'Company link not found' })
263
+ throw notFound('Company link not found')
264
264
  }
265
265
 
266
266
  if (patch.isPrimary === true) {
@@ -301,7 +301,7 @@ export async function removePersonCompanyLink(
301
301
  profile.company = null
302
302
  return
303
303
  }
304
- throw new CrudHttpError(404, { error: 'Company link not found' })
304
+ throw notFound('Company link not found')
305
305
  }
306
306
 
307
307
  const removedCompanyId = typeof link.company === 'string' ? link.company : link.company.id
@@ -4,6 +4,10 @@ import type { PerspectiveSettings } from '@open-mercato/shared/modules/perspecti
4
4
  export const perspectiveSettingsSchema: z.ZodType<PerspectiveSettings> = z.object({
5
5
  columnOrder: z.array(z.string().min(1)).max(120).optional(),
6
6
  columnVisibility: z.record(z.string(), z.boolean()).optional(),
7
+ // User-resized column widths in pixels, keyed by column id (#1835). Clamped to
8
+ // the same [60, 900] bound the client enforces so a saved perspective cannot
9
+ // carry a column width that collapses or blows out the table.
10
+ columnSizing: z.record(z.string().min(1), z.number().int().min(60).max(900)).optional(),
7
11
  filters: z.record(z.string(), z.unknown()).optional(),
8
12
  sorting: z
9
13
  .array(z.object({ id: z.string().min(1), desc: z.boolean().optional() }))