@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7131.1.20abf9e885

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 (203) hide show
  1. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
  2. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  3. package/dist/modules/auth/api/profile/route.js +5 -0
  4. package/dist/modules/auth/api/profile/route.js.map +2 -2
  5. package/dist/modules/customers/api/activities/route.js +13 -0
  6. package/dist/modules/customers/api/activities/route.js.map +2 -2
  7. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
  8. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  9. package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
  10. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  11. package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
  12. package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
  13. package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
  14. package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
  15. package/dist/modules/customers/api/interactions/complete/route.js +5 -0
  16. package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
  17. package/dist/modules/customers/api/labels/assign/route.js +5 -0
  18. package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
  19. package/dist/modules/customers/api/labels/route.js +5 -0
  20. package/dist/modules/customers/api/labels/route.js.map +2 -2
  21. package/dist/modules/customers/api/labels/unassign/route.js +5 -0
  22. package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
  23. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
  24. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
  25. package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
  26. package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
  27. package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
  28. package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
  29. package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
  30. package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
  31. package/dist/modules/customers/api/pipelines/route.js +13 -0
  32. package/dist/modules/customers/api/pipelines/route.js.map +2 -2
  33. package/dist/modules/customers/api/settings/address-format/route.js +5 -0
  34. package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
  35. package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
  36. package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
  37. package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
  38. package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
  39. package/dist/modules/customers/api/tags/assign/route.js +5 -0
  40. package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
  41. package/dist/modules/customers/api/tags/unassign/route.js +5 -0
  42. package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
  43. package/dist/modules/customers/api/todos/route.js +13 -0
  44. package/dist/modules/customers/api/todos/route.js.map +2 -2
  45. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
  46. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  47. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
  48. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
  49. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
  50. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  51. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
  52. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
  53. package/dist/modules/directory/api/organization-branding/route.js +5 -0
  54. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  55. package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
  56. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
  57. package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
  58. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  59. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
  60. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
  61. package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
  62. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
  63. package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
  64. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
  65. package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
  66. package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
  67. package/dist/modules/messages/api/[id]/forward/route.js +5 -0
  68. package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
  69. package/dist/modules/messages/api/[id]/reply/route.js +5 -0
  70. package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
  71. package/dist/modules/messages/api/[id]/route.js +9 -0
  72. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  73. package/dist/modules/messages/api/token/[token]/route.js +5 -0
  74. package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
  75. package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
  76. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  77. package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
  78. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  79. package/dist/modules/sales/api/quotes/accept/route.js +5 -0
  80. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  81. package/dist/modules/sales/api/quotes/convert/route.js +5 -0
  82. package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
  83. package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
  84. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  85. package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
  86. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  87. package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
  88. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  89. package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
  90. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  91. package/dist/modules/staff/api/team-members/self/route.js +5 -0
  92. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  93. package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
  94. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  95. package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
  96. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  97. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
  98. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
  99. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
  100. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  101. package/dist/modules/warranty_claims/api/ai/assess/route.js +5 -0
  102. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  103. package/dist/modules/warranty_claims/api/assign/route.js +5 -0
  104. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  105. package/dist/modules/warranty_claims/api/credit-memo/route.js +5 -0
  106. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  107. package/dist/modules/warranty_claims/api/events/route.js +5 -0
  108. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +5 -0
  110. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +5 -0
  112. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  113. package/dist/modules/warranty_claims/api/portal/claims/route.js +5 -0
  114. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  115. package/dist/modules/warranty_claims/api/portal/events/route.js +5 -0
  116. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  117. package/dist/modules/warranty_claims/api/receiving/route.js +5 -0
  118. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  119. package/dist/modules/warranty_claims/api/replacement-order/route.js +5 -0
  120. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  121. package/dist/modules/warranty_claims/api/return-label/route.js +5 -0
  122. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js +5 -0
  124. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  125. package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
  126. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  127. package/dist/modules/warranty_claims/api/submit/route.js +5 -0
  128. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  129. package/dist/modules/warranty_claims/api/transition/route.js +5 -0
  130. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  131. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +5 -0
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  133. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
  134. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
  135. package/package.json +7 -7
  136. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
  137. package/src/modules/auth/api/profile/route.ts +5 -0
  138. package/src/modules/customers/api/activities/route.ts +13 -0
  139. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
  140. package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
  141. package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
  142. package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
  143. package/src/modules/customers/api/interactions/complete/route.ts +5 -0
  144. package/src/modules/customers/api/labels/assign/route.ts +5 -0
  145. package/src/modules/customers/api/labels/route.ts +5 -0
  146. package/src/modules/customers/api/labels/unassign/route.ts +5 -0
  147. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
  148. package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
  149. package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
  150. package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
  151. package/src/modules/customers/api/pipelines/route.ts +13 -0
  152. package/src/modules/customers/api/settings/address-format/route.ts +5 -0
  153. package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
  154. package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
  155. package/src/modules/customers/api/tags/assign/route.ts +5 -0
  156. package/src/modules/customers/api/tags/unassign/route.ts +5 -0
  157. package/src/modules/customers/api/todos/route.ts +13 -0
  158. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
  159. package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
  160. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
  161. package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
  162. package/src/modules/directory/api/organization-branding/route.ts +5 -0
  163. package/src/modules/eudr/api/plots/import/route.ts +7 -0
  164. package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
  165. package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
  166. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
  167. package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
  168. package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
  169. package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
  170. package/src/modules/messages/api/[id]/forward/route.ts +5 -0
  171. package/src/modules/messages/api/[id]/reply/route.ts +5 -0
  172. package/src/modules/messages/api/[id]/route.ts +9 -0
  173. package/src/modules/messages/api/token/[token]/route.ts +5 -0
  174. package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
  175. package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
  176. package/src/modules/sales/api/quotes/accept/route.ts +5 -0
  177. package/src/modules/sales/api/quotes/convert/route.ts +5 -0
  178. package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
  179. package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
  180. package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
  181. package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
  182. package/src/modules/staff/api/team-members/self/route.ts +5 -0
  183. package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
  184. package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
  185. package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
  186. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
  187. package/src/modules/warranty_claims/api/ai/assess/route.ts +5 -0
  188. package/src/modules/warranty_claims/api/assign/route.ts +5 -0
  189. package/src/modules/warranty_claims/api/credit-memo/route.ts +5 -0
  190. package/src/modules/warranty_claims/api/events/route.ts +5 -0
  191. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +5 -0
  192. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +5 -0
  193. package/src/modules/warranty_claims/api/portal/claims/route.ts +5 -0
  194. package/src/modules/warranty_claims/api/portal/events/route.ts +5 -0
  195. package/src/modules/warranty_claims/api/receiving/route.ts +5 -0
  196. package/src/modules/warranty_claims/api/replacement-order/route.ts +5 -0
  197. package/src/modules/warranty_claims/api/return-label/route.ts +5 -0
  198. package/src/modules/warranty_claims/api/sales-return/route.ts +5 -0
  199. package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
  200. package/src/modules/warranty_claims/api/submit/route.ts +5 -0
  201. package/src/modules/warranty_claims/api/transition/route.ts +5 -0
  202. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
  203. package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/api/labels/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CustomerLabel, CustomerLabelAssignment } from '../../data/entities'\nimport { labelCreateCommandSchema, labelCreateSchema, type LabelCreateCommandInput } from '../../data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'\nimport { resolveEncryptedSortMaxRows, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'\nimport { SortDir } from '@open-mercato/shared/lib/query/types'\nimport { resolveLabelActorUserId } from './auth'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n createMissingCustomerLabelTablesError,\n isMissingCustomerLabelTable,\n} from './table-errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nconst DECRYPT_CONCURRENCY = 8\n\nconst querySchema = z.object({\n entityId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n ids: z.string().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n})\n\nconst createLabelRequestSchema = labelCreateSchema.extend({\n organizationId: z.string().uuid().optional(),\n})\n\nexport async function GET(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const em = container.resolve('em') as EntityManager\n const url = new URL(req.url)\n const query = querySchema.parse({\n entityId: url.searchParams.get('entityId') ?? undefined,\n organizationId: url.searchParams.get('organizationId') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n })\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: query.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n // `label` is encrypted at rest; SQL can't sort it meaningfully, so the\n // candidate set is decrypted and sorted in memory below.\n const cap = resolveEncryptedSortMaxRows()\n const candidateLabels = await em.find(CustomerLabel, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n } as FilterQuery<CustomerLabel>, cap !== null ? { limit: cap, orderBy: { id: 'asc' } } : {})\n if (cap !== null && candidateLabels.length >= cap) {\n logger.warn('Encrypted sort candidate scan hit OM_ENCRYPTED_SORT_MAX_ROWS cap; results may be incomplete', {\n component: 'labels.GET',\n cap,\n tenantId: auth.tenantId,\n organizationId,\n })\n }\n\n await mapWithConcurrency(candidateLabels, DECRYPT_CONCURRENCY, (label) =>\n decryptEntitiesWithFallbackScope(label, { em, tenantId: auth.tenantId, organizationId }),\n )\n\n const labels = sortRowsInMemory(\n candidateLabels as unknown as Record<string, unknown>[],\n [{ field: 'label', dir: SortDir.Asc }],\n ) as unknown as CustomerLabel[]\n\n // If entityId provided, also load assignments for that entity\n let assignedLabelIds: string[] = []\n if (query.entityId) {\n const assignments = await findWithDecryption(em, CustomerLabelAssignment, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n entity: query.entityId,\n } as FilterQuery<CustomerLabelAssignment>, {}, { tenantId: auth.tenantId, organizationId })\n assignedLabelIds = assignments.map((a) => {\n try { return a.label.id } catch { return '' }\n })\n // Handle unloaded references\n if (assignedLabelIds.some((id) => !id)) {\n const loaded = await findWithDecryption(em, CustomerLabelAssignment, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n entity: query.entityId,\n } as FilterQuery<CustomerLabelAssignment>, { populate: ['label'] }, { tenantId: auth.tenantId, organizationId })\n assignedLabelIds = loaded.map((a) => a.label.id)\n }\n }\n\n const requestedIds = query.ids\n ? new Set(\n query.ids\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean),\n )\n : null\n const scopedLabels = requestedIds\n ? labels.filter((label) => requestedIds.has(label.id))\n : labels\n const filteredLabels = query.search?.trim().length\n ? scopedLabels.filter((label) => label.label.toLowerCase().includes(query.search!.trim().toLowerCase()))\n : scopedLabels\n const total = filteredLabels.length\n const totalPages = Math.max(1, Math.ceil(total / query.pageSize))\n const page = Math.min(query.page, totalPages)\n const start = (page - 1) * query.pageSize\n const items = filteredLabels.slice(start, start + query.pageSize)\n\n return NextResponse.json({\n items: items.map((l) => ({\n id: l.id,\n slug: l.slug,\n label: l.label,\n })),\n assignedIds: assignedLabelIds.filter(Boolean),\n total,\n page,\n pageSize: query.pageSize,\n totalPages,\n })\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n return NextResponse.json({ items: [], assignedIds: [] })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers/labels.GET', { err })\n return NextResponse.json({ error: translate('customers.errors.labels_load_failed', 'Failed to load labels') }, { status: 500 })\n }\n}\n\nfunction slugifyLabel(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const body = createLabelRequestSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: body.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const slug = body.slug || slugifyLabel(body.label)\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind: 'customers.label',\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = labelCreateCommandSchema.parse({\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n slug,\n label: body.label,\n } satisfies LabelCreateCommandInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<LabelCreateCommandInput, { labelId: string; slug: string; label: string }>(\n 'customers.labels.create',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind: 'customers.label',\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n id: result.labelId,\n slug: result.slug,\n label: result.label,\n }, { status: 201 })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.label',\n resourceId: logEntry.resourceId ?? result.labelId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n const migrationError = await createMissingCustomerLabelTablesError()\n return NextResponse.json(migrationError.body, { status: migrationError.status })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers/labels.POST', { err })\n return NextResponse.json({ error: 'Failed to create label' }, { status: 500 })\n }\n}\n\nconst labelSchema = z.object({\n id: z.string().uuid(),\n slug: z.string(),\n label: z.string(),\n})\n\nconst labelsListSchema = z.object({\n items: z.array(labelSchema),\n assignedIds: z.array(z.string().uuid()),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer labels (user- and organization-scoped)',\n methods: {\n GET: {\n summary: 'List labels',\n description: 'Returns labels for the current user within the selected organization. Optionally includes assignment status for a specific entity.',\n responses: [\n { status: 200, description: 'Labels list', schema: labelsListSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Create label',\n description: 'Creates a new label scoped to the current user and selected organization.',\n requestBody: { contentType: 'application/json', schema: createLabelRequestSchema },\n responses: [\n { status: 201, description: 'Label created', schema: labelSchema },\n ],\n errors: [\n { status: 409, description: 'Duplicate slug', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CustomerLabel, CustomerLabelAssignment } from '../../data/entities'\nimport { labelCreateCommandSchema, labelCreateSchema, type LabelCreateCommandInput } from '../../data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'\nimport { resolveEncryptedSortMaxRows, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'\nimport { SortDir } from '@open-mercato/shared/lib/query/types'\nimport { resolveLabelActorUserId } from './auth'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n createMissingCustomerLabelTablesError,\n isMissingCustomerLabelTable,\n} from './table-errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nconst DECRYPT_CONCURRENCY = 8\n\nconst querySchema = z.object({\n entityId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n ids: z.string().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n})\n\nconst createLabelRequestSchema = labelCreateSchema.extend({\n organizationId: z.string().uuid().optional(),\n})\n\nexport async function GET(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const em = container.resolve('em') as EntityManager\n const url = new URL(req.url)\n const query = querySchema.parse({\n entityId: url.searchParams.get('entityId') ?? undefined,\n organizationId: url.searchParams.get('organizationId') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n })\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: query.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n // `label` is encrypted at rest; SQL can't sort it meaningfully, so the\n // candidate set is decrypted and sorted in memory below.\n const cap = resolveEncryptedSortMaxRows()\n const candidateLabels = await em.find(CustomerLabel, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n } as FilterQuery<CustomerLabel>, cap !== null ? { limit: cap, orderBy: { id: 'asc' } } : {})\n if (cap !== null && candidateLabels.length >= cap) {\n logger.warn('Encrypted sort candidate scan hit OM_ENCRYPTED_SORT_MAX_ROWS cap; results may be incomplete', {\n component: 'labels.GET',\n cap,\n tenantId: auth.tenantId,\n organizationId,\n })\n }\n\n await mapWithConcurrency(candidateLabels, DECRYPT_CONCURRENCY, (label) =>\n decryptEntitiesWithFallbackScope(label, { em, tenantId: auth.tenantId, organizationId }),\n )\n\n const labels = sortRowsInMemory(\n candidateLabels as unknown as Record<string, unknown>[],\n [{ field: 'label', dir: SortDir.Asc }],\n ) as unknown as CustomerLabel[]\n\n // If entityId provided, also load assignments for that entity\n let assignedLabelIds: string[] = []\n if (query.entityId) {\n const assignments = await findWithDecryption(em, CustomerLabelAssignment, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n entity: query.entityId,\n } as FilterQuery<CustomerLabelAssignment>, {}, { tenantId: auth.tenantId, organizationId })\n assignedLabelIds = assignments.map((a) => {\n try { return a.label.id } catch { return '' }\n })\n // Handle unloaded references\n if (assignedLabelIds.some((id) => !id)) {\n const loaded = await findWithDecryption(em, CustomerLabelAssignment, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n entity: query.entityId,\n } as FilterQuery<CustomerLabelAssignment>, { populate: ['label'] }, { tenantId: auth.tenantId, organizationId })\n assignedLabelIds = loaded.map((a) => a.label.id)\n }\n }\n\n const requestedIds = query.ids\n ? new Set(\n query.ids\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean),\n )\n : null\n const scopedLabels = requestedIds\n ? labels.filter((label) => requestedIds.has(label.id))\n : labels\n const filteredLabels = query.search?.trim().length\n ? scopedLabels.filter((label) => label.label.toLowerCase().includes(query.search!.trim().toLowerCase()))\n : scopedLabels\n const total = filteredLabels.length\n const totalPages = Math.max(1, Math.ceil(total / query.pageSize))\n const page = Math.min(query.page, totalPages)\n const start = (page - 1) * query.pageSize\n const items = filteredLabels.slice(start, start + query.pageSize)\n\n return NextResponse.json({\n items: items.map((l) => ({\n id: l.id,\n slug: l.slug,\n label: l.label,\n })),\n assignedIds: assignedLabelIds.filter(Boolean),\n total,\n page,\n pageSize: query.pageSize,\n totalPages,\n })\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n return NextResponse.json({ items: [], assignedIds: [] })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers/labels.GET', { err })\n return NextResponse.json({ error: translate('customers.errors.labels_load_failed', 'Failed to load labels') }, { status: 500 })\n }\n}\n\nfunction slugifyLabel(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const body = createLabelRequestSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: body.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const slug = body.slug || slugifyLabel(body.label)\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind: 'customers.label',\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = labelCreateCommandSchema.parse({\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n slug,\n label: body.label,\n } satisfies LabelCreateCommandInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<LabelCreateCommandInput, { labelId: string; slug: string; label: string }>(\n 'customers.labels.create',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind: 'customers.label',\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n id: result.labelId,\n slug: result.slug,\n label: result.label,\n }, { status: 201 })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.label',\n resourceId: logEntry.resourceId ?? result.labelId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n const migrationError = await createMissingCustomerLabelTablesError()\n return NextResponse.json(migrationError.body, { status: migrationError.status })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers/labels.POST', { err })\n return NextResponse.json({ error: 'Failed to create label' }, { status: 500 })\n }\n}\n\nconst labelSchema = z.object({\n id: z.string().uuid(),\n slug: z.string(),\n label: z.string(),\n})\n\nconst labelsListSchema = z.object({\n items: z.array(labelSchema),\n assignedIds: z.array(z.string().uuid()),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer labels (user- and organization-scoped)',\n methods: {\n GET: {\n summary: 'List labels',\n description: 'Returns labels for the current user within the selected organization. Optionally includes assignment status for a specific entity.',\n responses: [\n { status: 200, description: 'Labels list', schema: labelsListSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Create label',\n description: 'Creates a new label scoped to the current user and selected organization.',\n requestBody: { contentType: 'application/json', schema: createLabelRequestSchema },\n responses: [\n { status: 201, description: 'Label created', schema: labelSchema },\n ],\n errors: [\n { status: 409, description: 'Duplicate slug', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -20,6 +20,7 @@ import {
20
20
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
21
21
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
22
22
  import { createLogger } from "@open-mercato/shared/lib/logger";
23
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
23
24
  const logger = createLogger("customers");
24
25
  const metadata = {
25
26
  POST: { requireAuth: true }
@@ -156,6 +157,10 @@ async function POST(req) {
156
157
  if (isCrudHttpError(err)) {
157
158
  return NextResponse.json(err.body, { status: err.status });
158
159
  }
160
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
161
+ if (interceptorRejection) {
162
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
163
+ }
159
164
  logger.error("customers/labels/unassign.POST", { err });
160
165
  return NextResponse.json({ error: "Failed to unassign label" }, { status: 500 });
161
166
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/api/labels/unassign/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { CustomerEntity } from '../../../data/entities'\nimport { labelUnassignCommandSchema, labelAssignmentSchema, type LabelUnassignCommandInput } from '../../../data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { z } from 'zod'\nimport { resolveLabelActorUserId } from '../auth'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n createMissingCustomerLabelTablesError,\n isMissingCustomerLabelTable,\n} from '../table-errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nconst labelAssignmentRequestSchema = labelAssignmentSchema.extend({\n organizationId: z.string().uuid().optional(),\n})\n\nfunction resolveResourceKind(kind: 'person' | 'company' | null | undefined): string {\n if (kind === 'company') return 'customers.company'\n return 'customers.person'\n}\n\nfunction resolveRequiredFeature(kind: 'person' | 'company' | null | undefined): string {\n return kind === 'company' ? 'customers.companies.manage' : 'customers.people.manage'\n}\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const body = labelAssignmentRequestSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: body.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const targetEntity = await findOneWithDecryption(\n em,\n CustomerEntity,\n {\n id: body.entityId,\n tenantId: auth.tenantId,\n organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: auth.tenantId, organizationId },\n )\n if (!targetEntity) {\n throw notFound(translate('customers.errors.entity_not_found', 'Entity not found'))\n }\n const entityKind = targetEntity.kind === 'person' || targetEntity.kind === 'company' ? targetEntity.kind : null\n const resourceKind = resolveResourceKind(entityKind)\n\n const rbac = container.resolve('rbacService') as RbacService | undefined\n if (!rbac) {\n throw new CrudHttpError(500, { error: translate('customers.errors.internal', 'Internal error') })\n }\n const requiredFeature = resolveRequiredFeature(entityKind)\n const hasFeature = await rbac.userHasAllFeatures(actorUserId, [requiredFeature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasFeature) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind,\n resourceId: body.entityId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = labelUnassignCommandSchema.parse({\n labelId: body.labelId,\n entityId: body.entityId,\n tenantId: auth.tenantId,\n organizationId,\n } satisfies LabelUnassignCommandInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<LabelUnassignCommandInput, { assignmentId: string | null; entityKind: 'person' | 'company' | null }>(\n 'customers.labels.unassign',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind,\n resourceId: body.entityId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ id: result.assignmentId })\n if (result.assignmentId && logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.labelAssignment',\n resourceId: logEntry.resourceId ?? result.assignmentId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n const migrationError = await createMissingCustomerLabelTablesError()\n return NextResponse.json(migrationError.body, { status: migrationError.status })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers/labels/unassign.POST', { err })\n return NextResponse.json({ error: 'Failed to unassign label' }, { status: 500 })\n }\n}\n\nconst responseSchema = z.object({ id: z.string().uuid().nullable() })\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Unassign label from entity',\n methods: {\n POST: {\n summary: 'Unassign label',\n requestBody: { contentType: 'application/json', schema: labelAssignmentRequestSchema },\n responses: [\n { status: 200, description: 'Unassigned', schema: responseSchema },\n ],\n errors: [\n { status: 404, description: 'Label or entity not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { CustomerEntity } from '../../../data/entities'\nimport { labelUnassignCommandSchema, labelAssignmentSchema, type LabelUnassignCommandInput } from '../../../data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { z } from 'zod'\nimport { resolveLabelActorUserId } from '../auth'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n createMissingCustomerLabelTablesError,\n isMissingCustomerLabelTable,\n} from '../table-errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nconst labelAssignmentRequestSchema = labelAssignmentSchema.extend({\n organizationId: z.string().uuid().optional(),\n})\n\nfunction resolveResourceKind(kind: 'person' | 'company' | null | undefined): string {\n if (kind === 'company') return 'customers.company'\n return 'customers.person'\n}\n\nfunction resolveRequiredFeature(kind: 'person' | 'company' | null | undefined): string {\n return kind === 'company' ? 'customers.companies.manage' : 'customers.people.manage'\n}\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveLabelActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('customers.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const body = labelAssignmentRequestSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: body.organizationId ?? undefined,\n })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const targetEntity = await findOneWithDecryption(\n em,\n CustomerEntity,\n {\n id: body.entityId,\n tenantId: auth.tenantId,\n organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: auth.tenantId, organizationId },\n )\n if (!targetEntity) {\n throw notFound(translate('customers.errors.entity_not_found', 'Entity not found'))\n }\n const entityKind = targetEntity.kind === 'person' || targetEntity.kind === 'company' ? targetEntity.kind : null\n const resourceKind = resolveResourceKind(entityKind)\n\n const rbac = container.resolve('rbacService') as RbacService | undefined\n if (!rbac) {\n throw new CrudHttpError(500, { error: translate('customers.errors.internal', 'Internal error') })\n }\n const requiredFeature = resolveRequiredFeature(entityKind)\n const hasFeature = await rbac.userHasAllFeatures(actorUserId, [requiredFeature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasFeature) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind,\n resourceId: body.entityId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = labelUnassignCommandSchema.parse({\n labelId: body.labelId,\n entityId: body.entityId,\n tenantId: auth.tenantId,\n organizationId,\n } satisfies LabelUnassignCommandInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<LabelUnassignCommandInput, { assignmentId: string | null; entityKind: 'person' | 'company' | null }>(\n 'customers.labels.unassign',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId: actorUserId,\n resourceKind,\n resourceId: body.entityId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ id: result.assignmentId })\n if (result.assignmentId && logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.labelAssignment',\n resourceId: logEntry.resourceId ?? result.assignmentId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isMissingCustomerLabelTable(err)) {\n const migrationError = await createMissingCustomerLabelTablesError()\n return NextResponse.json(migrationError.body, { status: migrationError.status })\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers/labels/unassign.POST', { err })\n return NextResponse.json({ error: 'Failed to unassign label' }, { status: 500 })\n }\n}\n\nconst responseSchema = z.object({ id: z.string().uuid().nullable() })\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Unassign label from entity',\n methods: {\n POST: {\n summary: 'Unassign label',\n requestBody: { contentType: 'application/json', schema: labelAssignmentRequestSchema },\n responses: [\n { status: 200, description: 'Unassigned', schema: responseSchema },\n ],\n errors: [\n { status: 404, description: 'Label or entity not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,6 +16,7 @@ import {
16
16
  } from "@open-mercato/core/modules/customers/data/validators";
17
17
  import { loadPersonContext } from "../context.js";
18
18
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
19
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
19
20
  const paramsSchema = z.object({
20
21
  id: z.string().uuid(),
21
22
  linkId: z.string().uuid()
@@ -186,6 +187,10 @@ async function PATCH(req, ctx) {
186
187
  if (isCrudHttpError(err)) {
187
188
  return NextResponse.json(err.body, { status: err.status });
188
189
  }
190
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
191
+ if (interceptorRejection) {
192
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
193
+ }
189
194
  return NextResponse.json({ error: translate("customers.errors.internal", "Internal server error") }, { status: 500 });
190
195
  }
191
196
  }
@@ -277,6 +282,10 @@ async function DELETE(req, ctx) {
277
282
  if (isCrudHttpError(err)) {
278
283
  return NextResponse.json(err.body, { status: err.status });
279
284
  }
285
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
286
+ if (interceptorRejection) {
287
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
288
+ }
280
289
  return NextResponse.json({ error: translate("customers.errors.internal", "Internal server error") }, { status: 500 });
281
290
  }
282
291
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/customers/api/people/%5Bid%5D/companies/%5BlinkId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveAuthActorId } from '@open-mercato/core/modules/customers/lib/interactionRequestContext'\nimport { CustomerPersonCompanyLink } from '@open-mercato/core/modules/customers/data/entities'\nimport {\n personCompanyLinkDeleteSchema,\n personCompanyLinkUpdateSchema,\n type PersonCompanyLinkDeleteInput,\n type PersonCompanyLinkUpdateInput,\n} from '@open-mercato/core/modules/customers/data/validators'\nimport { loadPersonContext } from '../context'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n linkId: z.string().uuid(),\n})\n\n/**\n * Resolve the second path segment to a concrete `customer_person_company_links` row id.\n * The UI on company/person detail pages calls this route with the company entity id as\n * the last segment (symmetric with the other company-centric endpoints); older callers\n * may pass the actual link row id. We try a direct id lookup first, then fall back to\n * resolving via `(person, company)` pair.\n */\nasync function resolveLinkId(\n em: EntityManager,\n rawLinkId: string,\n personEntityId: string,\n tenantId: string,\n organizationId: string | null,\n): Promise<string | null> {\n const decryptionScope = { tenantId, organizationId }\n const directFilter: FilterQuery<CustomerPersonCompanyLink> = organizationId\n ? { id: rawLinkId, tenantId, organizationId, deletedAt: null }\n : { id: rawLinkId, tenantId, deletedAt: null }\n const direct = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n directFilter,\n undefined,\n decryptionScope,\n )\n if (direct) return direct.id\n\n const companyFilter: FilterQuery<CustomerPersonCompanyLink> = organizationId\n ? {\n person: personEntityId,\n company: rawLinkId,\n tenantId,\n organizationId,\n deletedAt: null,\n }\n : { person: personEntityId, company: rawLinkId, tenantId, deletedAt: null }\n const viaCompany = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n companyFilter,\n undefined,\n decryptionScope,\n )\n return viaCompany?.id ?? null\n}\n\nconst updateSchema = z.object({\n isPrimary: z.boolean().optional(),\n})\n\nconst updateResponseSchema = z.object({\n ok: z.literal(true),\n result: z\n .object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n })\n .nullable(),\n})\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n PATCH: {\n summary: 'Update a linked company for a person',\n requestBody: { schema: updateSchema },\n responses: [{ status: 200, description: 'Updated company link', schema: updateResponseSchema }],\n },\n DELETE: {\n summary: 'Remove a linked company from a person',\n responses: [{ status: 200, description: 'Deletion result', schema: z.object({ ok: z.literal(true) }) }],\n },\n },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { id?: string; linkId?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id, linkId } = paramsSchema.parse({ id: ctx.params?.id, linkId: ctx.params?.linkId })\n const payload = updateSchema.parse(await readJsonSafe(req, {}))\n const { container, auth, selectedOrganizationId, person } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (payload.isPrimary === undefined) {\n return NextResponse.json({ ok: true as const, result: null })\n }\n\n const resolveEm = (container.resolve('em') as EntityManager).fork()\n const resolvedLinkId = await resolveLinkId(\n resolveEm,\n linkId,\n person.id,\n auth.tenantId,\n selectedOrganizationId,\n )\n if (!resolvedLinkId) {\n throw notFound(translate('customers.errors.person_company_link_not_found', 'Person-company link not found'))\n }\n\n const commandInput = personCompanyLinkUpdateSchema.parse({\n linkId: resolvedLinkId,\n isPrimary: payload.isPrimary,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkUpdateInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkUpdateInput, { linkId: string }>(\n 'customers.personCompanyLinks.update',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const freshEm = (container.resolve('em') as EntityManager).fork()\n const linkRecord = await findOneWithDecryption(\n freshEm,\n CustomerPersonCompanyLink,\n { id: result.linkId, tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n { populate: ['company'] },\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n const company = linkRecord && typeof linkRecord.company !== 'string' ? linkRecord.company : null\n const response = NextResponse.json({\n ok: true as const,\n result: linkRecord\n ? {\n id: linkRecord.id,\n companyId: company?.id ?? '',\n displayName: company?.displayName ?? '',\n isPrimary: Boolean(linkRecord.isPrimary),\n }\n : null,\n })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { id?: string; linkId?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id, linkId } = paramsSchema.parse({ id: ctx.params?.id, linkId: ctx.params?.linkId })\n const { container, auth, selectedOrganizationId, person, profile } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { id: linkId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const resolveEm = (container.resolve('em') as EntityManager).fork()\n const resolvedLinkId = await resolveLinkId(\n resolveEm,\n linkId,\n person.id,\n auth.tenantId,\n selectedOrganizationId,\n )\n // No `CustomerPersonCompanyLink` row resolves for the profile-only association case\n // (`CustomerPersonProfile.company` set without a link row, e.g. from a CRM migration).\n // The delete command accepts that shape too, so it is dispatched instead of 404ing and\n // the detach keeps its audit entry, undo token and cache invalidation (#5114).\n const isProfileOnlyMatch =\n profile.company && typeof profile.company !== 'string' && profile.company.id === linkId\n if (!resolvedLinkId && !isProfileOnlyMatch) {\n throw notFound(translate('customers.errors.person_company_link_not_found', 'Person-company link not found'))\n }\n\n const commandInput = personCompanyLinkDeleteSchema.parse({\n ...(resolvedLinkId\n ? { linkId: resolvedLinkId }\n : { personEntityId: person.id, companyEntityId: linkId }),\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkDeleteInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkDeleteInput, { linkId: string | null }>(\n 'customers.personCompanyLinks.delete',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ ok: true as const })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveAuthActorId } from '@open-mercato/core/modules/customers/lib/interactionRequestContext'\nimport { CustomerPersonCompanyLink } from '@open-mercato/core/modules/customers/data/entities'\nimport {\n personCompanyLinkDeleteSchema,\n personCompanyLinkUpdateSchema,\n type PersonCompanyLinkDeleteInput,\n type PersonCompanyLinkUpdateInput,\n} from '@open-mercato/core/modules/customers/data/validators'\nimport { loadPersonContext } from '../context'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n linkId: z.string().uuid(),\n})\n\n/**\n * Resolve the second path segment to a concrete `customer_person_company_links` row id.\n * The UI on company/person detail pages calls this route with the company entity id as\n * the last segment (symmetric with the other company-centric endpoints); older callers\n * may pass the actual link row id. We try a direct id lookup first, then fall back to\n * resolving via `(person, company)` pair.\n */\nasync function resolveLinkId(\n em: EntityManager,\n rawLinkId: string,\n personEntityId: string,\n tenantId: string,\n organizationId: string | null,\n): Promise<string | null> {\n const decryptionScope = { tenantId, organizationId }\n const directFilter: FilterQuery<CustomerPersonCompanyLink> = organizationId\n ? { id: rawLinkId, tenantId, organizationId, deletedAt: null }\n : { id: rawLinkId, tenantId, deletedAt: null }\n const direct = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n directFilter,\n undefined,\n decryptionScope,\n )\n if (direct) return direct.id\n\n const companyFilter: FilterQuery<CustomerPersonCompanyLink> = organizationId\n ? {\n person: personEntityId,\n company: rawLinkId,\n tenantId,\n organizationId,\n deletedAt: null,\n }\n : { person: personEntityId, company: rawLinkId, tenantId, deletedAt: null }\n const viaCompany = await findOneWithDecryption(\n em,\n CustomerPersonCompanyLink,\n companyFilter,\n undefined,\n decryptionScope,\n )\n return viaCompany?.id ?? null\n}\n\nconst updateSchema = z.object({\n isPrimary: z.boolean().optional(),\n})\n\nconst updateResponseSchema = z.object({\n ok: z.literal(true),\n result: z\n .object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n })\n .nullable(),\n})\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n PATCH: {\n summary: 'Update a linked company for a person',\n requestBody: { schema: updateSchema },\n responses: [{ status: 200, description: 'Updated company link', schema: updateResponseSchema }],\n },\n DELETE: {\n summary: 'Remove a linked company from a person',\n responses: [{ status: 200, description: 'Deletion result', schema: z.object({ ok: z.literal(true) }) }],\n },\n },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { id?: string; linkId?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id, linkId } = paramsSchema.parse({ id: ctx.params?.id, linkId: ctx.params?.linkId })\n const payload = updateSchema.parse(await readJsonSafe(req, {}))\n const { container, auth, selectedOrganizationId, person } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (payload.isPrimary === undefined) {\n return NextResponse.json({ ok: true as const, result: null })\n }\n\n const resolveEm = (container.resolve('em') as EntityManager).fork()\n const resolvedLinkId = await resolveLinkId(\n resolveEm,\n linkId,\n person.id,\n auth.tenantId,\n selectedOrganizationId,\n )\n if (!resolvedLinkId) {\n throw notFound(translate('customers.errors.person_company_link_not_found', 'Person-company link not found'))\n }\n\n const commandInput = personCompanyLinkUpdateSchema.parse({\n linkId: resolvedLinkId,\n isPrimary: payload.isPrimary,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkUpdateInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkUpdateInput, { linkId: string }>(\n 'customers.personCompanyLinks.update',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const freshEm = (container.resolve('em') as EntityManager).fork()\n const linkRecord = await findOneWithDecryption(\n freshEm,\n CustomerPersonCompanyLink,\n { id: result.linkId, tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n { populate: ['company'] },\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n const company = linkRecord && typeof linkRecord.company !== 'string' ? linkRecord.company : null\n const response = NextResponse.json({\n ok: true as const,\n result: linkRecord\n ? {\n id: linkRecord.id,\n companyId: company?.id ?? '',\n displayName: company?.displayName ?? '',\n isPrimary: Boolean(linkRecord.isPrimary),\n }\n : null,\n })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { id?: string; linkId?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id, linkId } = paramsSchema.parse({ id: ctx.params?.id, linkId: ctx.params?.linkId })\n const { container, auth, selectedOrganizationId, person, profile } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { id: linkId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const resolveEm = (container.resolve('em') as EntityManager).fork()\n const resolvedLinkId = await resolveLinkId(\n resolveEm,\n linkId,\n person.id,\n auth.tenantId,\n selectedOrganizationId,\n )\n // No `CustomerPersonCompanyLink` row resolves for the profile-only association case\n // (`CustomerPersonProfile.company` set without a link row, e.g. from a CRM migration).\n // The delete command accepts that shape too, so it is dispatched instead of 404ing and\n // the detach keeps its audit entry, undo token and cache invalidation (#5114).\n const isProfileOnlyMatch =\n profile.company && typeof profile.company !== 'string' && profile.company.id === linkId\n if (!resolvedLinkId && !isProfileOnlyMatch) {\n throw notFound(translate('customers.errors.person_company_link_not_found', 'Person-company link not found'))\n }\n\n const commandInput = personCompanyLinkDeleteSchema.parse({\n ...(resolvedLinkId\n ? { linkId: resolvedLinkId }\n : { personEntityId: person.id, companyEntityId: linkId }),\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkDeleteInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkDeleteInput, { linkId: string | null }>(\n 'customers.personCompanyLinks.delete',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ ok: true as const })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,eAAe,iBAAiB,gBAAgB;AACzD;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAGP,SAAS,kCAAkC;AAC3C,SAAS,oBAAoB;AAC7B,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC,SAAS,iCAAiC;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AAEpC,SAAS,0CAA0C;AAEnD,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,QAAQ,EAAE,OAAO,EAAE,KAAK;AAC1B,CAAC;AASD,eAAe,cACb,IACA,WACA,gBACA,UACA,gBACwB;AACxB,QAAM,kBAAkB,EAAE,UAAU,eAAe;AACnD,QAAM,eAAuD,iBACzD,EAAE,IAAI,WAAW,UAAU,gBAAgB,WAAW,KAAK,IAC3D,EAAE,IAAI,WAAW,UAAU,WAAW,KAAK;AAC/C,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,OAAQ,QAAO,OAAO;AAE1B,QAAM,gBAAwD,iBAC1D;AAAA,IACE,QAAQ;AAAA,IACR,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb,IACA,EAAE,QAAQ,gBAAgB,SAAS,WAAW,UAAU,WAAW,KAAK;AAC5E,QAAM,aAAa,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,YAAY,MAAM;AAC3B;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,WAAW,EAAE,QAAQ,EAAE,SAAS;AAClC,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,QAAQ,EACL,OAAO;AAAA,IACN,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,IAC3B,aAAa,EAAE,OAAO;AAAA,IACtB,WAAW,EAAE,QAAQ;AAAA,EACvB,CAAC,EACA,SAAS;AACd,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAAA,EACzE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAC5E;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,OAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,EAAE,QAAQ,aAAa;AAAA,MACpC,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,qBAAqB,CAAC;AAAA,IAChG;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;AAAA,IACxG;AAAA,EACF;AACF;AAEA,eAAsB,MAAM,KAAc,KAAoD;AAC5F,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI;AACF,UAAM,EAAE,IAAI,OAAO,IAAI,aAAa,MAAM,EAAE,IAAI,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ,OAAO,CAAC;AAC5F,UAAM,UAAU,aAAa,MAAM,MAAM,aAAa,KAAK,CAAC,CAAC,CAAC;AAC9D,UAAM,EAAE,WAAW,MAAM,wBAAwB,OAAO,IAAI,MAAM,kBAAkB,KAAK,EAAE;AAC3F,QAAI,CAAC,wBAAwB;AAC3B,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,0CAA0C,kCAAkC,EAAE,CAAC;AAAA,IACjI;AACA,UAAM,cAAc,mBAAmB,IAAI;AAC3C,UAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,MAC7D,UAAU,KAAK;AAAA,MACf,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB;AAAA,IACnB,CAAC;AACD,QAAI,eAAe,CAAC,YAAY,IAAI;AAClC,aAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,IAC3E;AAEA,QAAI,QAAQ,cAAc,QAAW;AACnC,aAAO,aAAa,KAAK,EAAE,IAAI,MAAe,QAAQ,KAAK,CAAC;AAAA,IAC9D;AAEA,UAAM,YAAa,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAClE,UAAM,iBAAiB,MAAM;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,KAAK;AAAA,MACL;AAAA,IACF;AACA,QAAI,CAAC,gBAAgB;AACnB,YAAM,SAAS,UAAU,kDAAkD,+BAA+B,CAAC;AAAA,IAC7G;AAEA,UAAM,eAAe,8BAA8B,MAAM;AAAA,MACvD,QAAQ;AAAA,MACR,WAAW,QAAQ;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,gBAAgB;AAAA,IAClB,CAAwC;AAExC,UAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW;AAAA,MAC5C;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,KAAK;AAAA,UACH;AAAA,UACA;AAAA,UACA,mBAAmB;AAAA,UACnB;AAAA,UACA,iBAAiB,CAAC,sBAAsB;AAAA,UACxC,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,YAAM,iCAAiC,WAAW;AAAA,QAChD,UAAU,KAAK;AAAA,QACf,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY,OAAO;AAAA,QACnB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,UAAU,YAAY,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,UAAM,UAAW,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAChE,UAAM,aAAa,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,OAAO,QAAQ,UAAU,KAAK,UAAU,gBAAgB,uBAAuB;AAAA,MACrF,EAAE,UAAU,CAAC,SAAS,EAAE;AAAA,MACxB,EAAE,UAAU,KAAK,UAAU,gBAAgB,uBAAuB;AAAA,IACpE;AACA,UAAM,UAAU,cAAc,OAAO,WAAW,YAAY,WAAW,WAAW,UAAU;AAC5F,UAAM,WAAW,aAAa,KAAK;AAAA,MACjC,IAAI;AAAA,MACJ,QAAQ,aACJ;AAAA,QACE,IAAI,WAAW;AAAA,QACf,WAAW,SAAS,MAAM;AAAA,QAC1B,aAAa,SAAS,eAAe;AAAA,QACrC,WAAW,QAAQ,WAAW,SAAS;AAAA,MACzC,IACA;AAAA,IACN,CAAC;AACD,QAAI,UAAU,aAAa,SAAS,MAAM,SAAS,WAAW;AAC5D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY,SAAS,cAAc,OAAO;AAAA,UAC1C,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC7G,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,6BAA6B,uBAAuB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtH;AACF;AAEA,eAAsB,OAAO,KAAc,KAAoD;AAC7F,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI;AACF,UAAM,EAAE,IAAI,OAAO,IAAI,aAAa,MAAM,EAAE,IAAI,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ,OAAO,CAAC;AAC5F,UAAM,EAAE,WAAW,MAAM,wBAAwB,QAAQ,QAAQ,IAAI,MAAM,kBAAkB,KAAK,EAAE;AACpG,QAAI,CAAC,wBAAwB;AAC3B,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,0CAA0C,kCAAkC,EAAE,CAAC;AAAA,IACjI;AACA,UAAM,cAAc,mBAAmB,IAAI;AAC3C,UAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,MAC7D,UAAU,KAAK;AAAA,MACf,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB,EAAE,IAAI,OAAO;AAAA,IAChC,CAAC;AACD,QAAI,eAAe,CAAC,YAAY,IAAI;AAClC,aAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,IAC3E;AAEA,UAAM,YAAa,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAClE,UAAM,iBAAiB,MAAM;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,KAAK;AAAA,MACL;AAAA,IACF;AAKA,UAAM,qBACJ,QAAQ,WAAW,OAAO,QAAQ,YAAY,YAAY,QAAQ,QAAQ,OAAO;AACnF,QAAI,CAAC,kBAAkB,CAAC,oBAAoB;AAC1C,YAAM,SAAS,UAAU,kDAAkD,+BAA+B,CAAC;AAAA,IAC7G;AAEA,UAAM,eAAe,8BAA8B,MAAM;AAAA,MACvD,GAAI,iBACA,EAAE,QAAQ,eAAe,IACzB,EAAE,gBAAgB,OAAO,IAAI,iBAAiB,OAAO;AAAA,MACzD,UAAU,KAAK;AAAA,MACf,gBAAgB;AAAA,IAClB,CAAwC;AAExC,UAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW;AAAA,MAC5C;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,KAAK;AAAA,UACH;AAAA,UACA;AAAA,UACA,mBAAmB;AAAA,UACnB;AAAA,UACA,iBAAiB,CAAC,sBAAsB;AAAA,UACxC,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,YAAM,iCAAiC,WAAW;AAAA,QAChD,UAAU,KAAK;AAAA,QACf,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY,OAAO;AAAA,QACnB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,UAAU,YAAY,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,KAAc,CAAC;AACxD,QAAI,UAAU,aAAa,SAAS,MAAM,SAAS,WAAW;AAC5D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY,SAAS,cAAc,OAAO;AAAA,UAC1C,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC7G,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,6BAA6B,uBAAuB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtH;AACF;",
6
6
  "names": []
7
7
  }
@@ -22,6 +22,7 @@ import {
22
22
  } from "@open-mercato/core/modules/customers/data/validators";
23
23
  import { loadPersonContext } from "./context.js";
24
24
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
25
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
25
26
  const paramsSchema = z.object({
26
27
  id: z.string().uuid()
27
28
  });
@@ -193,6 +194,10 @@ async function POST(req, ctx) {
193
194
  if (isCrudHttpError(err)) {
194
195
  return NextResponse.json(err.body, { status: err.status });
195
196
  }
197
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
198
+ if (interceptorRejection) {
199
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
200
+ }
196
201
  return NextResponse.json({ error: translate("customers.errors.internal", "Internal server error") }, { status: 500 });
197
202
  }
198
203
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/customers/api/people/%5Bid%5D/companies/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n loadPersonCompanyLinks,\n summarizePersonCompanies,\n} from '@open-mercato/core/modules/customers/lib/personCompanies'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveAuthActorId } from '@open-mercato/core/modules/customers/lib/interactionRequestContext'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n} from '@open-mercato/core/modules/customers/data/entities'\nimport {\n personCompanyLinkCreateSchema,\n type PersonCompanyLinkCreateInput,\n} from '@open-mercato/core/modules/customers/data/validators'\nimport { loadPersonContext } from './context'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst createSchema = z.object({\n companyId: z.string().uuid(),\n isPrimary: z.boolean().optional(),\n})\n\nconst listResponseSchema = z.object({\n items: z.array(\n z.object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n ),\n})\n\nconst createResponseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n GET: {\n summary: 'List linked companies for a person',\n responses: [{ status: 200, description: 'Linked company rows', schema: listResponseSchema }],\n },\n POST: {\n summary: 'Link a company to a person',\n requestBody: { schema: createSchema },\n responses: [{ status: 200, description: 'Linked company row', schema: createResponseSchema }],\n },\n },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const { em, person, profile } = await loadPersonContext(req, id)\n const links = await loadPersonCompanyLinks(em, person)\n const items = summarizePersonCompanies(profile, links).map((entry) => ({\n id: entry.linkId ?? entry.companyId,\n companyId: entry.companyId,\n displayName: entry.displayName,\n isPrimary: entry.isPrimary,\n }))\n return NextResponse.json({ items })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const payload = createSchema.parse(await readJsonSafe(req, {}))\n const { container, auth, selectedOrganizationId, em, person } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = personCompanyLinkCreateSchema.parse({\n personEntityId: person.id,\n companyEntityId: payload.companyId,\n isPrimary: payload.isPrimary,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkCreateInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkCreateInput, { linkId: string; created: boolean; undeleted: boolean }>(\n 'customers.personCompanyLinks.create',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const freshEm = (container.resolve('em') as EntityManager).fork()\n const linkRecord = await findOneWithDecryption(\n freshEm,\n CustomerPersonCompanyLink,\n { id: result.linkId, tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n { populate: ['company'] },\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n const company = linkRecord && typeof linkRecord.company !== 'string' ? linkRecord.company : null\n let displayName = company?.displayName ?? ''\n if (!displayName && !company) {\n const fallbackCompany = await findOneWithDecryption(\n freshEm,\n CustomerEntity,\n { id: payload.companyId, tenantId: auth.tenantId, organizationId: selectedOrganizationId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n displayName = fallbackCompany?.displayName ?? ''\n }\n\n const response = NextResponse.json({\n ok: true as const,\n result: {\n id: result.linkId,\n companyId: company?.id ?? payload.companyId,\n displayName,\n isPrimary: linkRecord ? Boolean(linkRecord.isPrimary) : Boolean(payload.isPrimary),\n },\n })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n loadPersonCompanyLinks,\n summarizePersonCompanies,\n} from '@open-mercato/core/modules/customers/lib/personCompanies'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveAuthActorId } from '@open-mercato/core/modules/customers/lib/interactionRequestContext'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CustomerEntity,\n CustomerPersonCompanyLink,\n} from '@open-mercato/core/modules/customers/data/entities'\nimport {\n personCompanyLinkCreateSchema,\n type PersonCompanyLinkCreateInput,\n} from '@open-mercato/core/modules/customers/data/validators'\nimport { loadPersonContext } from './context'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst createSchema = z.object({\n companyId: z.string().uuid(),\n isPrimary: z.boolean().optional(),\n})\n\nconst listResponseSchema = z.object({\n items: z.array(\n z.object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n ),\n})\n\nconst createResponseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n id: z.string().uuid(),\n companyId: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] },\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n GET: {\n summary: 'List linked companies for a person',\n responses: [{ status: 200, description: 'Linked company rows', schema: listResponseSchema }],\n },\n POST: {\n summary: 'Link a company to a person',\n requestBody: { schema: createSchema },\n responses: [{ status: 200, description: 'Linked company row', schema: createResponseSchema }],\n },\n },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const { em, person, profile } = await loadPersonContext(req, id)\n const links = await loadPersonCompanyLinks(em, person)\n const items = summarizePersonCompanies(profile, links).map((entry) => ({\n id: entry.linkId ?? entry.companyId,\n companyId: entry.companyId,\n displayName: entry.displayName,\n isPrimary: entry.isPrimary,\n }))\n return NextResponse.json({ items })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const payload = createSchema.parse(await readJsonSafe(req, {}))\n const { container, auth, selectedOrganizationId, em, person } = await loadPersonContext(req, id)\n if (!selectedOrganizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = personCompanyLinkCreateSchema.parse({\n personEntityId: person.id,\n companyEntityId: payload.companyId,\n isPrimary: payload.isPrimary,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n } satisfies PersonCompanyLinkCreateInput)\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<PersonCompanyLinkCreateInput, { linkId: string; created: boolean; undeleted: boolean }>(\n 'customers.personCompanyLinks.create',\n {\n input: commandInput,\n ctx: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId,\n organizationIds: [selectedOrganizationId],\n request: req,\n },\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.person',\n resourceId: person.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const freshEm = (container.resolve('em') as EntityManager).fork()\n const linkRecord = await findOneWithDecryption(\n freshEm,\n CustomerPersonCompanyLink,\n { id: result.linkId, tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n { populate: ['company'] },\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n const company = linkRecord && typeof linkRecord.company !== 'string' ? linkRecord.company : null\n let displayName = company?.displayName ?? ''\n if (!displayName && !company) {\n const fallbackCompany = await findOneWithDecryption(\n freshEm,\n CustomerEntity,\n { id: payload.companyId, tenantId: auth.tenantId, organizationId: selectedOrganizationId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: selectedOrganizationId },\n )\n displayName = fallbackCompany?.displayName ?? ''\n }\n\n const response = NextResponse.json({\n ok: true as const,\n result: {\n id: result.linkId,\n companyId: company?.id ?? payload.companyId,\n displayName,\n isPrimary: linkRecord ? Boolean(linkRecord.isPrimary) : Boolean(payload.isPrimary),\n },\n })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.personCompanyLink',\n resourceId: logEntry.resourceId ?? result.linkId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n return NextResponse.json({ error: translate('customers.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,6 +12,7 @@ import {
12
12
  validateCrudMutationGuard
13
13
  } from "@open-mercato/shared/lib/crud/mutation-guard";
14
14
  import { createLogger } from "@open-mercato/shared/lib/logger";
15
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
15
16
  const logger = createLogger("customers");
16
17
  const PIPELINE_STAGE_RESOURCE_KIND = "customers.pipelineStage";
17
18
  const metadata = {
@@ -77,6 +78,10 @@ async function POST(req) {
77
78
  if (isCrudHttpError(err)) {
78
79
  return NextResponse.json(err.body, { status: err.status });
79
80
  }
81
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
82
+ if (interceptorRejection) {
83
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
84
+ }
80
85
  logger.error("customers.pipeline-stages.reorder failed", { err });
81
86
  return NextResponse.json({ error: "Failed to reorder pipeline stages" }, { status: 400 });
82
87
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/api/pipeline-stages/reorder/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../../../data/validators'\nimport { withScopedPayload } from '../../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageReorderSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageReorderInput, void>(\n 'customers.pipeline-stages.reorder',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages.reorder failed', { err })\n return NextResponse.json({ error: 'Failed to reorder pipeline stages' }, { status: 400 })\n }\n}\n\nconst reorderOkResponseSchema = z.object({ ok: z.boolean() })\nconst reorderErrorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Reorder pipeline stages',\n methods: {\n POST: {\n summary: 'Reorder pipeline stages',\n description: 'Updates the order of pipeline stages in bulk.',\n requestBody: { contentType: 'application/json', schema: pipelineStageReorderSchema },\n responses: [\n { status: 200, description: 'Stages reordered', schema: reorderOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: reorderErrorSchema },\n { status: 401, description: 'Unauthorized', schema: reorderErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../../../data/validators'\nimport { withScopedPayload } from '../../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageReorderSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageReorderInput, void>(\n 'customers.pipeline-stages.reorder',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipeline-stages.reorder failed', { err })\n return NextResponse.json({ error: 'Failed to reorder pipeline stages' }, { status: 400 })\n }\n}\n\nconst reorderOkResponseSchema = z.object({ ok: z.boolean() })\nconst reorderErrorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Reorder pipeline stages',\n methods: {\n POST: {\n summary: 'Reorder pipeline stages',\n description: 'Updates the order of pipeline stages in bulk.',\n requestBody: { contentType: 'application/json', schema: pipelineStageReorderSchema },\n responses: [\n { status: 200, description: 'Stages reordered', schema: reorderOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: reorderErrorSchema },\n { status: 401, description: 'Unauthorized', schema: reorderErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -19,6 +19,7 @@ import {
19
19
  validateCrudMutationGuard
20
20
  } from "@open-mercato/shared/lib/crud/mutation-guard";
21
21
  import { createLogger } from "@open-mercato/shared/lib/logger";
22
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
22
23
  const logger = createLogger("customers");
23
24
  const PIPELINE_STAGE_RESOURCE_KIND = "customers.pipelineStage";
24
25
  const metadata = {
@@ -152,6 +153,10 @@ async function POST(req) {
152
153
  if (isCrudHttpError(err)) {
153
154
  return NextResponse.json(err.body, { status: err.status });
154
155
  }
156
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
157
+ if (interceptorRejection) {
158
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
159
+ }
155
160
  logger.error("customers.pipeline-stages POST failed", { err });
156
161
  return NextResponse.json({ error: "Failed to create pipeline stage" }, { status: 400 });
157
162
  }
@@ -217,6 +222,10 @@ async function PUT(req) {
217
222
  if (isCrudHttpError(err)) {
218
223
  return NextResponse.json(err.body, { status: err.status });
219
224
  }
225
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
226
+ if (interceptorRejection) {
227
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
228
+ }
220
229
  logger.error("customers.pipeline-stages PUT failed", { err });
221
230
  return NextResponse.json({ error: "Failed to update pipeline stage" }, { status: 400 });
222
231
  }
@@ -267,6 +276,10 @@ async function DELETE(req) {
267
276
  if (isCrudHttpError(err)) {
268
277
  return NextResponse.json(err.body, { status: err.status });
269
278
  }
279
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
280
+ if (interceptorRejection) {
281
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
282
+ }
270
283
  logger.error("customers.pipeline-stages DELETE failed", { err });
271
284
  return NextResponse.json({ error: "Failed to delete pipeline stage" }, { status: 400 });
272
285
  }