@open-mercato/core 0.6.8-develop.7067.1.82e9867ea1 → 0.6.8-develop.7071.1.58862d0c9f
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/audit_logs/services/actionLogService.js +57 -12
- package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +39 -30
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/people/route.js +1 -1
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +8 -9
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +8 -25
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/stats/route.js +3 -22
- package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
- package/dist/modules/customers/api/entity-roles-factory.js +4 -4
- package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/context.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +11 -10
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/lib/detailReadAccess.js +12 -0
- package/dist/modules/customers/lib/detailReadAccess.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/audit_logs/services/actionLogService.ts +74 -11
- package/src/modules/auth/backend/roles/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/backend/users/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/components/AclEditor.tsx +63 -33
- package/src/modules/auth/i18n/de.json +380 -0
- package/src/modules/auth/i18n/en.json +380 -0
- package/src/modules/auth/i18n/es.json +380 -0
- package/src/modules/auth/i18n/ko.json +380 -0
- package/src/modules/auth/i18n/pl.json +382 -2
- package/src/modules/customers/api/companies/[id]/people/route.ts +4 -1
- package/src/modules/customers/api/companies/[id]/route.ts +13 -10
- package/src/modules/customers/api/deals/[id]/companies/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/people/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/route.ts +17 -28
- package/src/modules/customers/api/deals/[id]/stats/route.ts +10 -25
- package/src/modules/customers/api/entity-roles-factory.ts +8 -4
- package/src/modules/customers/api/people/[id]/companies/context.ts +4 -1
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +4 -1
- package/src/modules/customers/api/people/[id]/route.ts +16 -11
- package/src/modules/customers/lib/detailReadAccess.ts +35 -0
- package/src/modules/dashboards/i18n/es.json +4 -4
- package/src/modules/dashboards/i18n/pl.json +7 -7
|
@@ -18,7 +18,7 @@ import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fiel
|
|
|
18
18
|
import { normalizeCustomFieldResponse } from "@open-mercato/shared/lib/custom-fields/normalize";
|
|
19
19
|
import { E } from "../../../../../generated/entities.ids.generated.js";
|
|
20
20
|
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
21
|
-
import {
|
|
21
|
+
import { denyCustomerDetailReadAsNotFound } from "../../../lib/detailReadAccess.js";
|
|
22
22
|
import { decryptEntitiesWithFallbackScope } from "@open-mercato/shared/lib/encryption/subscriber";
|
|
23
23
|
import { runWithCacheTenant } from "@open-mercato/cache";
|
|
24
24
|
import {
|
|
@@ -38,9 +38,6 @@ const paramsSchema = z.object({
|
|
|
38
38
|
function notFound(message) {
|
|
39
39
|
return NextResponse.json({ error: message }, { status: 404 });
|
|
40
40
|
}
|
|
41
|
-
function forbidden(message) {
|
|
42
|
-
return NextResponse.json({ error: message }, { status: 403 });
|
|
43
|
-
}
|
|
44
41
|
function normalizePersonAssociation(entity) {
|
|
45
42
|
const displayName = typeof entity.displayName === "string" ? entity.displayName.trim() : "";
|
|
46
43
|
const email = typeof entity.primaryEmail === "string" && entity.primaryEmail.trim().length ? entity.primaryEmail.trim() : null;
|
|
@@ -281,22 +278,6 @@ async function GET(request, context) {
|
|
|
281
278
|
if (!auth?.sub && !auth?.isApiKey) {
|
|
282
279
|
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
|
|
283
280
|
}
|
|
284
|
-
let rbac = null;
|
|
285
|
-
try {
|
|
286
|
-
rbac = container.resolve("rbacService");
|
|
287
|
-
} catch {
|
|
288
|
-
rbac = null;
|
|
289
|
-
}
|
|
290
|
-
if (!rbac || !auth?.sub) {
|
|
291
|
-
return forbidden("Access denied");
|
|
292
|
-
}
|
|
293
|
-
const hasFeature = await rbac.userHasAllFeatures(auth.sub, ["customers.deals.view"], {
|
|
294
|
-
tenantId: auth.tenantId ?? null,
|
|
295
|
-
organizationId: auth.orgId ?? null
|
|
296
|
-
});
|
|
297
|
-
if (!hasFeature) {
|
|
298
|
-
return forbidden("Access denied");
|
|
299
|
-
}
|
|
300
281
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request });
|
|
301
282
|
const em = container.resolve("em");
|
|
302
283
|
const deal = await findOneWithDecryption(
|
|
@@ -314,9 +295,11 @@ async function GET(request, context) {
|
|
|
314
295
|
if (auth.tenantId && deal.tenantId && auth.tenantId !== deal.tenantId) {
|
|
315
296
|
return notFound("Deal not found");
|
|
316
297
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
298
|
+
const organizationReadDenied = denyCustomerDetailReadAsNotFound(
|
|
299
|
+
{ scope, auth, organizationId: deal.organizationId },
|
|
300
|
+
"Deal not found"
|
|
301
|
+
);
|
|
302
|
+
if (organizationReadDenied) return organizationReadDenied;
|
|
320
303
|
const decryptionScope = {
|
|
321
304
|
tenantId: deal.tenantId ?? auth.tenantId ?? null,
|
|
322
305
|
organizationId: deal.organizationId ?? auth.orgId ?? null
|
|
@@ -763,8 +746,8 @@ const openApi = {
|
|
|
763
746
|
],
|
|
764
747
|
errors: [
|
|
765
748
|
{ status: 401, description: "Unauthorized", schema: dealDetailErrorSchema },
|
|
766
|
-
{ status: 403, description: "Forbidden
|
|
767
|
-
{ status: 404, description: "Deal not found", schema: dealDetailErrorSchema }
|
|
749
|
+
{ status: 403, description: "Forbidden \u2014 caller lacks the required feature", schema: dealDetailErrorSchema },
|
|
750
|
+
{ status: 404, description: "Deal not found, or its organization is not in the caller\u2019s scope", schema: dealDetailErrorSchema }
|
|
768
751
|
]
|
|
769
752
|
}
|
|
770
753
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/deals/%5Bid%5D/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 { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerDeal,\n CustomerDealPersonLink,\n CustomerDealCompanyLink,\n CustomerDealStageTransition,\n CustomerDictionaryEntry,\n CustomerEntity,\n CustomerPipeline,\n CustomerPipelineStage,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { E } from '#generated/entities.ids.generated'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n expandResourceAliases,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isMissingDealStageTransitionTable, warnMissingDealStageTransitionTable } from '../../../lib/dealStageTransitionTable'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction forbidden(message: string) {\n return NextResponse.json({ error: message }, { status: 403 })\n}\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n isPrimary?: boolean\n}\n\nfunction normalizePersonAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const email =\n typeof entity.primaryEmail === 'string' && entity.primaryEmail.trim().length\n ? entity.primaryEmail.trim()\n : null\n const phone =\n typeof entity.primaryPhone === 'string' && entity.primaryPhone.trim().length\n ? entity.primaryPhone.trim()\n : null\n const jobTitle =\n entity.personProfile &&\n typeof (entity.personProfile as { jobTitle?: string | null })?.jobTitle === 'string' &&\n (entity.personProfile as { jobTitle?: string | null }).jobTitle?.trim().length\n ? ((entity.personProfile as { jobTitle?: string | null }).jobTitle as string).trim()\n : null\n const subtitle = jobTitle ?? email ?? phone ?? null\n const label = displayName.length ? displayName : email ?? phone ?? entity.id\n return { label, subtitle }\n}\n\nfunction normalizeCompanyAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const domain =\n entity.companyProfile &&\n typeof (entity.companyProfile as { domain?: string | null })?.domain === 'string' &&\n (entity.companyProfile as { domain?: string | null }).domain?.trim().length\n ? ((entity.companyProfile as { domain?: string | null }).domain as string).trim()\n : null\n const website =\n entity.companyProfile &&\n typeof (entity.companyProfile as { websiteUrl?: string | null })?.websiteUrl === 'string' &&\n (entity.companyProfile as { websiteUrl?: string | null }).websiteUrl?.trim().length\n ? ((entity.companyProfile as { websiteUrl?: string | null }).websiteUrl as string).trim()\n : null\n const subtitle = domain ?? website ?? null\n const label = displayName.length ? displayName : domain ?? website ?? entity.id\n return { label, subtitle }\n}\n\nfunction readIncludeFlags(request: Request): Set<string> {\n const flags = new Set<string>()\n const url = new URL(request.url)\n for (const rawValue of url.searchParams.getAll('include')) {\n rawValue\n .split(',')\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean)\n .forEach((value) => flags.add(value))\n }\n return flags\n}\n\nfunction readViewMode(request: Request): 'full' | 'lite' {\n const raw = new URL(request.url).searchParams.get('view')\n return raw === 'lite' || raw === 'detail-lite' ? 'lite' : 'full'\n}\n\nfunction normalizeStageLabel(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim().toLowerCase() : ''\n}\n\ntype StageTransitionPayload = {\n stageId: string\n stageLabel: string\n stageOrder: number\n transitionedAt: string\n}\n\ntype DealSnapshotStageInfo = {\n pipelineId: string | null\n stageId: string | null\n stageLabel: string | null\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null\n}\n\nfunction readRecordString(record: Record<string, unknown> | null, ...keys: string[]): string | null {\n if (!record) return null\n for (const key of keys) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction readSnapshotDealRecord(snapshot: unknown): Record<string, unknown> | null {\n const root = asObject(snapshot)\n if (!root) return null\n return asObject(root.deal) ?? root\n}\n\nfunction readSnapshotStageInfo(snapshot: unknown): DealSnapshotStageInfo {\n const dealRecord = readSnapshotDealRecord(snapshot)\n return {\n pipelineId: readRecordString(dealRecord, 'pipelineId', 'pipeline_id'),\n stageId: readRecordString(dealRecord, 'pipelineStageId', 'pipeline_stage_id'),\n stageLabel: readRecordString(dealRecord, 'pipelineStage', 'pipeline_stage'),\n }\n}\n\nasync function loadAuditStageTransitionsFallback({\n container,\n deal,\n pipelineStages,\n}: {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n deal: CustomerDeal\n pipelineStages: CustomerPipelineStage[]\n}): Promise<StageTransitionPayload[]> {\n if (!deal.tenantId || !deal.organizationId || !pipelineStages.length) return []\n\n let actionLogs: ActionLogService | null = null\n try {\n actionLogs = container.resolve('actionLogService') as ActionLogService\n } catch {\n return []\n }\n if (!actionLogs || typeof actionLogs.list !== 'function') return []\n const stageOrderById = new Map(pipelineStages.map((stage) => [stage.id, stage.order]))\n const stageLabelById = new Map(pipelineStages.map((stage) => [stage.id, stage.label]))\n const transitionsByStageId = new Map<string, StageTransitionPayload>()\n const logsResult = await actionLogs.list({\n tenantId: deal.tenantId,\n organizationId: deal.organizationId,\n resourceKind: 'customers.deal',\n resourceId: deal.id,\n limit: 200,\n offset: 0,\n sortField: 'createdAt',\n sortDir: 'asc',\n }).catch(() => null)\n const logs = logsResult?.items ?? []\n\n let previousStageId: string | null = null\n for (const log of logs) {\n if (log.executionState === 'failed' || log.executionState === 'undone') continue\n\n const before = readSnapshotStageInfo(log.snapshotBefore)\n const after = readSnapshotStageInfo(log.snapshotAfter)\n const nextStageId = after.stageId\n if (!nextStageId) continue\n\n const stageOrder = stageOrderById.get(nextStageId)\n if (typeof stageOrder !== 'number') {\n previousStageId = nextStageId\n continue\n }\n\n const effectivePreviousStageId: string | null = before.stageId ?? previousStageId\n if (effectivePreviousStageId === nextStageId && transitionsByStageId.has(nextStageId)) {\n previousStageId = nextStageId\n continue\n }\n\n transitionsByStageId.set(nextStageId, {\n stageId: nextStageId,\n stageLabel: after.stageLabel ?? stageLabelById.get(nextStageId) ?? nextStageId,\n stageOrder,\n transitionedAt: log.createdAt.toISOString(),\n })\n previousStageId = nextStageId\n }\n\n return Array.from(transitionsByStageId.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nfunction mergeStageTransitions({\n persisted,\n recovered,\n currentStage,\n fallbackTimestamp,\n}: {\n persisted: StageTransitionPayload[]\n recovered: StageTransitionPayload[]\n currentStage: { id: string; label: string; order: number } | null\n fallbackTimestamp: string\n}): StageTransitionPayload[] {\n const merged = new Map<string, StageTransitionPayload>()\n for (const transition of persisted) {\n merged.set(transition.stageId, transition)\n }\n for (const transition of recovered) {\n if (!merged.has(transition.stageId)) {\n merged.set(transition.stageId, transition)\n }\n }\n if (currentStage && !merged.has(currentStage.id)) {\n merged.set(currentStage.id, {\n stageId: currentStage.id,\n stageLabel: currentStage.label,\n stageOrder: currentStage.order,\n transitionedAt: fallbackTimestamp,\n })\n }\n return Array.from(merged.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nasync function loadPipelineStageAppearanceMap(\n em: EntityManager,\n stages: CustomerPipelineStage[],\n organizationId: string,\n tenantId: string,\n): Promise<Map<string, CustomerDictionaryEntry>> {\n const normalizedValues = stages\n .map((stage) => stage.label.trim().toLowerCase())\n .filter((value) => value.length > 0)\n if (!normalizedValues.length) return new Map<string, CustomerDictionaryEntry>()\n const entries = await findWithDecryption(\n em,\n CustomerDictionaryEntry,\n {\n organizationId,\n tenantId,\n kind: 'pipeline_stage',\n normalizedValue: { $in: normalizedValues },\n },\n undefined,\n { tenantId, organizationId },\n )\n const map = new Map<string, CustomerDictionaryEntry>()\n entries.forEach((entry) => map.set(entry.normalizedValue, entry))\n return map\n}\n\nasync function resolveEffectivePipelineStage(\n em: EntityManager,\n deal: CustomerDeal,\n decryptionScope: { tenantId: string | null; organizationId: string | null },\n): Promise<CustomerPipelineStage | null> {\n if (deal.pipelineStageId) {\n const exactStage = await findOneWithDecryption(\n em,\n CustomerPipelineStage,\n {\n id: deal.pipelineStageId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n if (exactStage) return exactStage\n }\n\n const normalizedStageLabel = normalizeStageLabel(deal.pipelineStage)\n if (!normalizedStageLabel) return null\n\n const scopedStages = await findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n ...(deal.pipelineId ? { pipelineId: deal.pipelineId } : {}),\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n\n const matchingStages = scopedStages.filter((stage) => normalizeStageLabel(stage.label) === normalizedStageLabel)\n if (matchingStages.length === 1) return matchingStages[0] ?? null\n if (matchingStages.length > 1) {\n const distinctPipelineIds = new Set(matchingStages.map((stage) => stage.pipelineId))\n if (distinctPipelineIds.size === 1) return matchingStages[0] ?? null\n }\n return null\n}\n\nconst DEAL_DETAIL_CACHE_RESOURCE = 'customers.deal'\n// Every entity the detail payload reads, expressed with the same resourceKind\n// strings the customers commands declare. `expandResourceAliases` canonicalizes\n// them exactly the way `invalidateCrudCache` does on the write side (camelCase is\n// split, `_`/`-` become `.`), so `customers.pipelineStage` resolves to\n// `customers.pipeline.stage` and `customers.dictionary_entry` to\n// `customers.dictionary.entry`. That guarantees the collection tags stored here\n// match the ones the deal/pipeline/stage/dictionary commands flush.\nconst DEAL_DETAIL_CACHE_TAG_RESOURCES = [\n 'customers.pipeline',\n 'customers.pipelineStage',\n 'customers.dictionary_entry',\n]\nconst DEAL_DETAIL_CACHE_TTL_MS = 60_000\n\ntype DealDetailCacheAuth = {\n isSuperAdmin?: boolean | null\n orgId?: string | null\n}\n\n// Capture every input `isOrganizationReadAccessAllowed` consults, so two requests\n// that share a signature also share an authorization outcome for the same deal.\n// The lookup runs after the access check, so this is defense in depth plus the\n// per-scope cache partitioning the issue asks for.\nfunction buildDealDetailScopeSignature(\n auth: DealDetailCacheAuth,\n scope: Pick<OrganizationScope, 'allowedIds' | 'filterIds'> | null | undefined,\n): string {\n if (auth?.isSuperAdmin === true) return 'super'\n if (scope?.allowedIds === null) return 'all'\n const filterIds = Array.isArray(scope?.filterIds)\n ? scope!.filterIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : []\n if (filterIds.length) return `filter:${[...filterIds].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',')}`\n return `home:${auth?.orgId ?? 'null'}`\n}\n\nfunction buildDealDetailCacheKey(params: {\n tenantId: string | null\n organizationId: string | null\n dealId: string\n scopeSignature: string\n view: 'full' | 'lite'\n includeKey: string\n}): string {\n return [\n 'customers:deal:detail',\n `tenant=${params.tenantId ?? 'null'}`,\n `org=${params.organizationId ?? 'null'}`,\n `deal=${params.dealId}`,\n `scope=${params.scopeSignature}`,\n `view=${params.view}`,\n `include=${params.includeKey}`,\n ].join(':')\n}\n\nfunction buildDealDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {\n const tags = new Set<string>()\n for (const key of expandResourceAliases(DEAL_DETAIL_CACHE_RESOURCE, DEAL_DETAIL_CACHE_TAG_RESOURCES)) {\n for (const tag of buildCollectionTags(key, tenantId, [organizationId])) {\n tags.add(tag)\n }\n }\n return Array.from(tags)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound('Deal not found')\n }\n\n const includeFlags = readIncludeFlags(request)\n const viewMode = readViewMode(request)\n const liteView = viewMode === 'lite'\n const includeStages = includeFlags.has('stages')\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: 'Authentication required' }, { status: 401 })\n }\n\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n if (!rbac || !auth?.sub) {\n return forbidden('Access denied')\n }\n const hasFeature = await rbac.userHasAllFeatures(auth.sub, ['customers.deals.view'], {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n })\n if (!hasFeature) {\n return forbidden('Access denied')\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, deletedAt: null },\n {\n populate: ['people.person', 'people.person.personProfile', 'companies.company', 'companies.company.companyProfile'],\n },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound('Deal not found')\n }\n\n if (auth.tenantId && deal.tenantId && auth.tenantId !== deal.tenantId) {\n return notFound('Deal not found')\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n return forbidden('Access denied')\n }\n\n const decryptionScope = {\n tenantId: deal.tenantId ?? auth.tenantId ?? null,\n organizationId: deal.organizationId ?? auth.orgId ?? null,\n }\n\n const viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n const resolveViewer = async (): Promise<{ name: string | null; email: string | null }> => {\n if (!viewerUserId) {\n return { name: null, email: auth.email ?? null }\n }\n const viewer = await findOneWithDecryption(\n em,\n User,\n { id: viewerUserId, tenantId: auth.tenantId ?? null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n return {\n name: viewer?.name ?? null,\n email: viewer?.email ?? auth.email ?? null,\n }\n }\n\n // Opt-in detail cache (ENABLE_CRUD_API_CACHE). The lookup runs after the deal is\n // resolved and the tenant + organization read-access checks pass, so the cache\n // only ever memoizes a payload this caller is already authorized to see; it then\n // skips the heavy association / custom-field / pipeline-stage / dictionary /\n // audit-fallback sweeps below. The viewer block is request-specific, so it is\n // excluded from the cached value and always resolved fresh.\n const cacheTenantId = deal.tenantId ?? auth.tenantId ?? null\n const cacheOrganizationId = deal.organizationId ?? null\n const detailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null\n const includeKey = includeFlags.size ? Array.from(includeFlags).sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',') : 'none'\n const detailCacheKey = detailCache\n ? buildDealDetailCacheKey({\n tenantId: cacheTenantId,\n organizationId: cacheOrganizationId,\n dealId: deal.id,\n scopeSignature: buildDealDetailScopeSignature(auth, scope),\n view: viewMode,\n includeKey,\n })\n : null\n\n if (detailCache && detailCacheKey) {\n try {\n const cached = await runWithCacheTenant(cacheTenantId, () => detailCache.get(detailCacheKey))\n if (cached && typeof cached === 'object') {\n const viewerInfo = await resolveViewer()\n return NextResponse.json({\n ...(cached as Record<string, unknown>),\n viewer: {\n userId: viewerUserId,\n name: viewerInfo.name,\n email: viewerInfo.email,\n },\n })\n }\n } catch (err) {\n // A cache-backend read error must degrade to a fresh DB read, never a 500.\n debugCrudCache('get', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n // Issue #3175: the reads below only depend on the already-resolved deal +\n // organization scope + decryption scope, so they are grouped into Promise.all\n // batches instead of being awaited one after another. True dependencies stay\n // ordered: preview/decrypt run after their link rows, pipeline reads after the\n // effective stage is known, and the appearance map / audit fallback after the\n // pipeline stages load. This mirrors the parallel-read pattern already used\n // across the customers list/interactions/activities routes.\n const resolveAssociations = async (): Promise<{\n people: DealAssociation[]\n companies: DealAssociation[]\n linkedPersonIds: string[]\n linkedCompanyIds: string[]\n }> => {\n if (liteView) {\n const [personLinkRows, companyLinkRows] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n ])\n\n const linkedPersonIds = Array.from(\n new Set(\n personLinkRows\n .map((link) => {\n const personRef = link.person\n if (!personRef) return null\n if (typeof personRef === 'string') return personRef\n const personIdValue = personRef.id\n return typeof personIdValue === 'string' ? personIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n const linkedCompanyIds = Array.from(\n new Set(\n companyLinkRows\n .map((link) => {\n const companyRef = link.company\n if (!companyRef) return null\n if (typeof companyRef === 'string') return companyRef\n const companyIdValue = companyRef.id\n return typeof companyIdValue === 'string' ? companyIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n\n const [previewPeople, previewCompanies] = await Promise.all([\n linkedPersonIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedPersonIds.slice(0, 3) } },\n { populate: ['personProfile'] },\n decryptionScope,\n )\n : [],\n linkedCompanyIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedCompanyIds.slice(0, 3) } },\n { populate: ['companyProfile'] },\n decryptionScope,\n )\n : [],\n ])\n const primaryPersonIds = new Set(\n personLinkRows\n .filter((link) => link.isPrimary === true)\n .map((link) => (typeof link.person === 'string' ? link.person : link.person?.id))\n .filter((value): value is string => typeof value === 'string'),\n )\n const previewPeopleMap = new Map(previewPeople.map((entity) => [entity.id, entity]))\n const previewCompaniesMap = new Map(previewCompanies.map((entity) => [entity.id, entity]))\n const people = linkedPersonIds.slice(0, 3).reduce<DealAssociation[]>((acc, personId) => {\n const entity = previewPeopleMap.get(personId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: primaryPersonIds.has(entity.id) })\n return acc\n }, [])\n const companies = linkedCompanyIds.slice(0, 3).reduce<DealAssociation[]>((acc, companyId) => {\n const entity = previewCompaniesMap.get(companyId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return { people, companies, linkedPersonIds, linkedCompanyIds }\n }\n\n const [personLinks, companyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { populate: ['person', 'person.personProfile'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { populate: ['company', 'company.companyProfile'] },\n decryptionScope,\n ),\n ])\n const fallbackTenantId = deal.tenantId ?? auth.tenantId ?? null\n const fallbackOrgId = deal.organizationId ?? auth.orgId ?? null\n await decryptEntitiesWithFallbackScope(personLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n await decryptEntitiesWithFallbackScope(companyLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n\n const people = personLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.person as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: link.isPrimary === true })\n return acc\n }, [])\n\n const companies = companyLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.company as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return {\n people,\n companies,\n linkedPersonIds: people.map((entry) => entry.id),\n linkedCompanyIds: companies.map((entry) => entry.id),\n }\n }\n\n const resolveOwner = async () =>\n deal.ownerUserId\n ? await findOneWithDecryption(\n em,\n User,\n { id: deal.ownerUserId, tenantId: deal.tenantId ?? auth.tenantId ?? null },\n {},\n decryptionScope,\n )\n : null\n\n const resolveStageTransitions = async (): Promise<CustomerDealStageTransition[]> => {\n if (!includeStages) return []\n try {\n return await findWithDecryption(\n em,\n CustomerDealStageTransition,\n { deal: deal.id, deletedAt: null },\n { orderBy: { stageOrder: 'ASC', transitionedAt: 'ASC' } },\n decryptionScope,\n )\n } catch (error) {\n if (!isMissingDealStageTransitionTable(error)) {\n throw error\n }\n warnMissingDealStageTransitionTable('customers.api.deals.detail.GET')\n return []\n }\n }\n\n const [associations, customFieldValues, viewerInfo, owner, effectiveStage, stageTransitions] = await Promise.all([\n resolveAssociations(),\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_deal,\n recordIds: [deal.id],\n tenantIdByRecord: { [deal.id]: deal.tenantId ?? null },\n organizationIdByRecord: { [deal.id]: deal.organizationId ?? null },\n tenantFallbacks: [deal.tenantId ?? auth.tenantId ?? null].filter((value): value is string => !!value),\n }),\n resolveViewer(),\n resolveOwner(),\n includeStages ? resolveEffectivePipelineStage(em, deal, decryptionScope) : Promise.resolve(null),\n resolveStageTransitions(),\n ])\n\n const { people, companies, linkedPersonIds, linkedCompanyIds } = associations\n const customFields = normalizeCustomFieldResponse(customFieldValues[deal.id]) ?? {}\n const viewerName = viewerInfo.name\n const viewerEmail = viewerInfo.email\n const ownerPayload = owner\n ? {\n id: owner.id,\n name: owner.name ?? owner.email ?? owner.id,\n email: owner.email ?? '',\n }\n : null\n\n const effectivePipelineId = deal.pipelineId ?? effectiveStage?.pipelineId ?? null\n const effectivePipelineStageId = deal.pipelineStageId ?? effectiveStage?.id ?? null\n const effectivePipelineStageLabel = deal.pipelineStage ?? effectiveStage?.label ?? null\n\n const [pipelineStages, pipeline] = await Promise.all([\n includeStages && effectivePipelineId\n ? findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n pipelineId: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n : [],\n effectivePipelineId\n ? findOneWithDecryption(\n em,\n CustomerPipeline,\n {\n id: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n : null,\n ])\n\n const persistedStageTransitions = stageTransitions.map((transition) => ({\n stageId: transition.stageId,\n stageLabel: transition.stageLabel,\n stageOrder: transition.stageOrder,\n transitionedAt: transition.transitionedAt.toISOString(),\n }))\n const [pipelineStageAppearanceMap, recoveredStageTransitions] = await Promise.all([\n pipelineStages.length\n ? loadPipelineStageAppearanceMap(em, pipelineStages, deal.organizationId, deal.tenantId)\n : new Map<string, CustomerDictionaryEntry>(),\n includeStages && persistedStageTransitions.length === 0\n ? loadAuditStageTransitionsFallback({ container, deal, pipelineStages })\n : [],\n ])\n const effectiveCurrentStage = (() => {\n if (!effectivePipelineStageId) return null\n const matchingStage = pipelineStages.find((stage) => stage.id === effectivePipelineStageId)\n if (matchingStage) {\n return {\n id: matchingStage.id,\n label: matchingStage.label,\n order: matchingStage.order,\n }\n }\n if (!effectivePipelineStageLabel) return null\n return {\n id: effectivePipelineStageId,\n label: effectivePipelineStageLabel,\n order: 0,\n }\n })()\n const stageTransitionPayload = mergeStageTransitions({\n persisted: persistedStageTransitions,\n recovered: recoveredStageTransitions,\n currentStage: effectiveCurrentStage,\n fallbackTimestamp: deal.createdAt.toISOString(),\n })\n\n const viewer = {\n userId: viewerUserId,\n name: viewerName,\n email: viewerEmail,\n }\n\n // Everything except the request-specific viewer block is shareable across\n // authorized callers, so it is what gets cached.\n const cacheableBody = {\n deal: {\n id: deal.id,\n title: deal.title,\n description: deal.description ?? null,\n status: deal.status ?? null,\n pipelineStage: effectivePipelineStageLabel,\n pipelineId: effectivePipelineId,\n pipelineStageId: effectivePipelineStageId,\n valueAmount: deal.valueAmount ?? null,\n valueCurrency: deal.valueCurrency ?? null,\n probability: deal.probability ?? null,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId ?? null,\n source: deal.source ?? null,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n organizationId: deal.organizationId ?? null,\n tenantId: deal.tenantId ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n },\n people,\n companies,\n linkedPersonIds,\n linkedCompanyIds,\n counts: {\n people: linkedPersonIds.length,\n companies: linkedCompanyIds.length,\n },\n customFields,\n pipelineStages: pipelineStages.map((stage) => {\n const appearance = pipelineStageAppearanceMap.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n label: stage.label,\n order: stage.order,\n color: appearance?.color ?? null,\n icon: appearance?.icon ?? null,\n }\n }),\n pipelineName: pipeline?.name ?? null,\n stageTransitions: stageTransitionPayload,\n owner: ownerPayload,\n }\n\n if (detailCache && detailCacheKey) {\n try {\n await runWithCacheTenant(cacheTenantId, () =>\n detailCache.set(detailCacheKey, cacheableBody, {\n ttl: DEAL_DETAIL_CACHE_TTL_MS,\n tags: buildDealDetailCacheTags(cacheTenantId, cacheOrganizationId),\n }),\n )\n } catch (err) {\n // A cache write must never break the request; log it for observability\n // instead of failing the response (matches the CRUD factory).\n debugCrudCache('store', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return NextResponse.json({ ...cacheableBody, viewer })\n}\n\nconst dealDetailQuerySchema = z.object({\n include: z.string().optional(),\n})\n\nconst pipelineStageInfoSchema = z.object({\n id: z.string().uuid(),\n label: z.string(),\n order: z.number().int(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n})\n\nconst stageTransitionInfoSchema = z.object({\n stageId: z.string().uuid(),\n stageLabel: z.string(),\n stageOrder: z.number().int(),\n transitionedAt: z.string(),\n})\n\nconst dealDetailResponseSchema = z.object({\n deal: z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n pipelineId: z.string().uuid().nullable().optional(),\n pipelineStageId: z.string().uuid().nullable().optional(),\n valueAmount: z.string().nullable().optional(),\n valueCurrency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expectedCloseAt: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n closureOutcome: z.enum(['won', 'lost']).nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n people: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('person'),\n isPrimary: z.boolean().optional(),\n }),\n ),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('company'),\n }),\n ),\n customFields: z.record(z.string(), z.unknown()),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n pipelineStages: z.array(pipelineStageInfoSchema),\n stageTransitions: z.array(stageTransitionInfoSchema),\n owner: z.object({\n id: z.string().uuid(),\n name: z.string(),\n email: z.string(),\n }).nullable(),\n})\n\nconst dealDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal detail',\n methods: {\n GET: {\n summary: 'Fetch deal with associations and pipeline context',\n description: 'Returns a deal with linked people, companies, closure fields, optional pipeline history, custom fields, and viewer context.',\n query: dealDetailQuerySchema,\n responses: [\n { status: 200, description: 'Deal detail payload', schema: dealDetailResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dealDetailErrorSchema },\n { status: 403, description: 'Forbidden for tenant/organization scope', schema: dealDetailErrorSchema },\n { status: 404, description: 'Deal not found', schema: dealDetailErrorSchema },\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 { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerDeal,\n CustomerDealPersonLink,\n CustomerDealCompanyLink,\n CustomerDealStageTransition,\n CustomerDictionaryEntry,\n CustomerEntity,\n CustomerPipeline,\n CustomerPipelineStage,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { E } from '#generated/entities.ids.generated'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { denyCustomerDetailReadAsNotFound } from '../../../lib/detailReadAccess'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n expandResourceAliases,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isMissingDealStageTransitionTable, warnMissingDealStageTransitionTable } from '../../../lib/dealStageTransitionTable'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n isPrimary?: boolean\n}\n\nfunction normalizePersonAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const email =\n typeof entity.primaryEmail === 'string' && entity.primaryEmail.trim().length\n ? entity.primaryEmail.trim()\n : null\n const phone =\n typeof entity.primaryPhone === 'string' && entity.primaryPhone.trim().length\n ? entity.primaryPhone.trim()\n : null\n const jobTitle =\n entity.personProfile &&\n typeof (entity.personProfile as { jobTitle?: string | null })?.jobTitle === 'string' &&\n (entity.personProfile as { jobTitle?: string | null }).jobTitle?.trim().length\n ? ((entity.personProfile as { jobTitle?: string | null }).jobTitle as string).trim()\n : null\n const subtitle = jobTitle ?? email ?? phone ?? null\n const label = displayName.length ? displayName : email ?? phone ?? entity.id\n return { label, subtitle }\n}\n\nfunction normalizeCompanyAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const domain =\n entity.companyProfile &&\n typeof (entity.companyProfile as { domain?: string | null })?.domain === 'string' &&\n (entity.companyProfile as { domain?: string | null }).domain?.trim().length\n ? ((entity.companyProfile as { domain?: string | null }).domain as string).trim()\n : null\n const website =\n entity.companyProfile &&\n typeof (entity.companyProfile as { websiteUrl?: string | null })?.websiteUrl === 'string' &&\n (entity.companyProfile as { websiteUrl?: string | null }).websiteUrl?.trim().length\n ? ((entity.companyProfile as { websiteUrl?: string | null }).websiteUrl as string).trim()\n : null\n const subtitle = domain ?? website ?? null\n const label = displayName.length ? displayName : domain ?? website ?? entity.id\n return { label, subtitle }\n}\n\nfunction readIncludeFlags(request: Request): Set<string> {\n const flags = new Set<string>()\n const url = new URL(request.url)\n for (const rawValue of url.searchParams.getAll('include')) {\n rawValue\n .split(',')\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean)\n .forEach((value) => flags.add(value))\n }\n return flags\n}\n\nfunction readViewMode(request: Request): 'full' | 'lite' {\n const raw = new URL(request.url).searchParams.get('view')\n return raw === 'lite' || raw === 'detail-lite' ? 'lite' : 'full'\n}\n\nfunction normalizeStageLabel(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim().toLowerCase() : ''\n}\n\ntype StageTransitionPayload = {\n stageId: string\n stageLabel: string\n stageOrder: number\n transitionedAt: string\n}\n\ntype DealSnapshotStageInfo = {\n pipelineId: string | null\n stageId: string | null\n stageLabel: string | null\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null\n}\n\nfunction readRecordString(record: Record<string, unknown> | null, ...keys: string[]): string | null {\n if (!record) return null\n for (const key of keys) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction readSnapshotDealRecord(snapshot: unknown): Record<string, unknown> | null {\n const root = asObject(snapshot)\n if (!root) return null\n return asObject(root.deal) ?? root\n}\n\nfunction readSnapshotStageInfo(snapshot: unknown): DealSnapshotStageInfo {\n const dealRecord = readSnapshotDealRecord(snapshot)\n return {\n pipelineId: readRecordString(dealRecord, 'pipelineId', 'pipeline_id'),\n stageId: readRecordString(dealRecord, 'pipelineStageId', 'pipeline_stage_id'),\n stageLabel: readRecordString(dealRecord, 'pipelineStage', 'pipeline_stage'),\n }\n}\n\nasync function loadAuditStageTransitionsFallback({\n container,\n deal,\n pipelineStages,\n}: {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n deal: CustomerDeal\n pipelineStages: CustomerPipelineStage[]\n}): Promise<StageTransitionPayload[]> {\n if (!deal.tenantId || !deal.organizationId || !pipelineStages.length) return []\n\n let actionLogs: ActionLogService | null = null\n try {\n actionLogs = container.resolve('actionLogService') as ActionLogService\n } catch {\n return []\n }\n if (!actionLogs || typeof actionLogs.list !== 'function') return []\n const stageOrderById = new Map(pipelineStages.map((stage) => [stage.id, stage.order]))\n const stageLabelById = new Map(pipelineStages.map((stage) => [stage.id, stage.label]))\n const transitionsByStageId = new Map<string, StageTransitionPayload>()\n const logsResult = await actionLogs.list({\n tenantId: deal.tenantId,\n organizationId: deal.organizationId,\n resourceKind: 'customers.deal',\n resourceId: deal.id,\n limit: 200,\n offset: 0,\n sortField: 'createdAt',\n sortDir: 'asc',\n }).catch(() => null)\n const logs = logsResult?.items ?? []\n\n let previousStageId: string | null = null\n for (const log of logs) {\n if (log.executionState === 'failed' || log.executionState === 'undone') continue\n\n const before = readSnapshotStageInfo(log.snapshotBefore)\n const after = readSnapshotStageInfo(log.snapshotAfter)\n const nextStageId = after.stageId\n if (!nextStageId) continue\n\n const stageOrder = stageOrderById.get(nextStageId)\n if (typeof stageOrder !== 'number') {\n previousStageId = nextStageId\n continue\n }\n\n const effectivePreviousStageId: string | null = before.stageId ?? previousStageId\n if (effectivePreviousStageId === nextStageId && transitionsByStageId.has(nextStageId)) {\n previousStageId = nextStageId\n continue\n }\n\n transitionsByStageId.set(nextStageId, {\n stageId: nextStageId,\n stageLabel: after.stageLabel ?? stageLabelById.get(nextStageId) ?? nextStageId,\n stageOrder,\n transitionedAt: log.createdAt.toISOString(),\n })\n previousStageId = nextStageId\n }\n\n return Array.from(transitionsByStageId.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nfunction mergeStageTransitions({\n persisted,\n recovered,\n currentStage,\n fallbackTimestamp,\n}: {\n persisted: StageTransitionPayload[]\n recovered: StageTransitionPayload[]\n currentStage: { id: string; label: string; order: number } | null\n fallbackTimestamp: string\n}): StageTransitionPayload[] {\n const merged = new Map<string, StageTransitionPayload>()\n for (const transition of persisted) {\n merged.set(transition.stageId, transition)\n }\n for (const transition of recovered) {\n if (!merged.has(transition.stageId)) {\n merged.set(transition.stageId, transition)\n }\n }\n if (currentStage && !merged.has(currentStage.id)) {\n merged.set(currentStage.id, {\n stageId: currentStage.id,\n stageLabel: currentStage.label,\n stageOrder: currentStage.order,\n transitionedAt: fallbackTimestamp,\n })\n }\n return Array.from(merged.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nasync function loadPipelineStageAppearanceMap(\n em: EntityManager,\n stages: CustomerPipelineStage[],\n organizationId: string,\n tenantId: string,\n): Promise<Map<string, CustomerDictionaryEntry>> {\n const normalizedValues = stages\n .map((stage) => stage.label.trim().toLowerCase())\n .filter((value) => value.length > 0)\n if (!normalizedValues.length) return new Map<string, CustomerDictionaryEntry>()\n const entries = await findWithDecryption(\n em,\n CustomerDictionaryEntry,\n {\n organizationId,\n tenantId,\n kind: 'pipeline_stage',\n normalizedValue: { $in: normalizedValues },\n },\n undefined,\n { tenantId, organizationId },\n )\n const map = new Map<string, CustomerDictionaryEntry>()\n entries.forEach((entry) => map.set(entry.normalizedValue, entry))\n return map\n}\n\nasync function resolveEffectivePipelineStage(\n em: EntityManager,\n deal: CustomerDeal,\n decryptionScope: { tenantId: string | null; organizationId: string | null },\n): Promise<CustomerPipelineStage | null> {\n if (deal.pipelineStageId) {\n const exactStage = await findOneWithDecryption(\n em,\n CustomerPipelineStage,\n {\n id: deal.pipelineStageId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n if (exactStage) return exactStage\n }\n\n const normalizedStageLabel = normalizeStageLabel(deal.pipelineStage)\n if (!normalizedStageLabel) return null\n\n const scopedStages = await findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n ...(deal.pipelineId ? { pipelineId: deal.pipelineId } : {}),\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n\n const matchingStages = scopedStages.filter((stage) => normalizeStageLabel(stage.label) === normalizedStageLabel)\n if (matchingStages.length === 1) return matchingStages[0] ?? null\n if (matchingStages.length > 1) {\n const distinctPipelineIds = new Set(matchingStages.map((stage) => stage.pipelineId))\n if (distinctPipelineIds.size === 1) return matchingStages[0] ?? null\n }\n return null\n}\n\nconst DEAL_DETAIL_CACHE_RESOURCE = 'customers.deal'\n// Every entity the detail payload reads, expressed with the same resourceKind\n// strings the customers commands declare. `expandResourceAliases` canonicalizes\n// them exactly the way `invalidateCrudCache` does on the write side (camelCase is\n// split, `_`/`-` become `.`), so `customers.pipelineStage` resolves to\n// `customers.pipeline.stage` and `customers.dictionary_entry` to\n// `customers.dictionary.entry`. That guarantees the collection tags stored here\n// match the ones the deal/pipeline/stage/dictionary commands flush.\nconst DEAL_DETAIL_CACHE_TAG_RESOURCES = [\n 'customers.pipeline',\n 'customers.pipelineStage',\n 'customers.dictionary_entry',\n]\nconst DEAL_DETAIL_CACHE_TTL_MS = 60_000\n\ntype DealDetailCacheAuth = {\n isSuperAdmin?: boolean | null\n orgId?: string | null\n}\n\n// Capture every input `isOrganizationReadAccessAllowed` consults, so two requests\n// that share a signature also share an authorization outcome for the same deal.\n// The lookup runs after the access check, so this is defense in depth plus the\n// per-scope cache partitioning the issue asks for.\nfunction buildDealDetailScopeSignature(\n auth: DealDetailCacheAuth,\n scope: Pick<OrganizationScope, 'allowedIds' | 'filterIds'> | null | undefined,\n): string {\n if (auth?.isSuperAdmin === true) return 'super'\n if (scope?.allowedIds === null) return 'all'\n const filterIds = Array.isArray(scope?.filterIds)\n ? scope!.filterIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : []\n if (filterIds.length) return `filter:${[...filterIds].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',')}`\n return `home:${auth?.orgId ?? 'null'}`\n}\n\nfunction buildDealDetailCacheKey(params: {\n tenantId: string | null\n organizationId: string | null\n dealId: string\n scopeSignature: string\n view: 'full' | 'lite'\n includeKey: string\n}): string {\n return [\n 'customers:deal:detail',\n `tenant=${params.tenantId ?? 'null'}`,\n `org=${params.organizationId ?? 'null'}`,\n `deal=${params.dealId}`,\n `scope=${params.scopeSignature}`,\n `view=${params.view}`,\n `include=${params.includeKey}`,\n ].join(':')\n}\n\nfunction buildDealDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {\n const tags = new Set<string>()\n for (const key of expandResourceAliases(DEAL_DETAIL_CACHE_RESOURCE, DEAL_DETAIL_CACHE_TAG_RESOURCES)) {\n for (const tag of buildCollectionTags(key, tenantId, [organizationId])) {\n tags.add(tag)\n }\n }\n return Array.from(tags)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound('Deal not found')\n }\n\n const includeFlags = readIncludeFlags(request)\n const viewMode = readViewMode(request)\n const liteView = viewMode === 'lite'\n const includeStages = includeFlags.has('stages')\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: 'Authentication required' }, { status: 401 })\n }\n\n // The API dispatcher already enforces `requireFeatures: ['customers.deals.view']`\n // against the caller's effective (selected) organization before this handler\n // runs, so no in-route feature re-check is needed. Re-checking here against the\n // token org (auth.orgId) would wrongly 403 org-switched callers (#5012).\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, deletedAt: null },\n {\n populate: ['people.person', 'people.person.personProfile', 'companies.company', 'companies.company.companyProfile'],\n },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound('Deal not found')\n }\n\n if (auth.tenantId && deal.tenantId && auth.tenantId !== deal.tenantId) {\n return notFound('Deal not found')\n }\n\n // Existence oracle (issue #5504): a caller who holds customers.deals.view but\n // whose scope excludes the record's organization must get the SAME response as\n // for a non-existent id, so 403-when-present / 404-when-absent collapses to a\n // uniform 404 not-found. The dispatcher already returns a uniform 403 for\n // callers who lack the feature entirely.\n const organizationReadDenied = denyCustomerDetailReadAsNotFound(\n { scope, auth, organizationId: deal.organizationId },\n 'Deal not found',\n )\n if (organizationReadDenied) return organizationReadDenied\n\n const decryptionScope = {\n tenantId: deal.tenantId ?? auth.tenantId ?? null,\n organizationId: deal.organizationId ?? auth.orgId ?? null,\n }\n\n const viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n const resolveViewer = async (): Promise<{ name: string | null; email: string | null }> => {\n if (!viewerUserId) {\n return { name: null, email: auth.email ?? null }\n }\n const viewer = await findOneWithDecryption(\n em,\n User,\n { id: viewerUserId, tenantId: auth.tenantId ?? null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n return {\n name: viewer?.name ?? null,\n email: viewer?.email ?? auth.email ?? null,\n }\n }\n\n // Opt-in detail cache (ENABLE_CRUD_API_CACHE). The lookup runs after the deal is\n // resolved and the tenant + organization read-access checks pass, so the cache\n // only ever memoizes a payload this caller is already authorized to see; it then\n // skips the heavy association / custom-field / pipeline-stage / dictionary /\n // audit-fallback sweeps below. The viewer block is request-specific, so it is\n // excluded from the cached value and always resolved fresh.\n const cacheTenantId = deal.tenantId ?? auth.tenantId ?? null\n const cacheOrganizationId = deal.organizationId ?? null\n const detailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null\n const includeKey = includeFlags.size ? Array.from(includeFlags).sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',') : 'none'\n const detailCacheKey = detailCache\n ? buildDealDetailCacheKey({\n tenantId: cacheTenantId,\n organizationId: cacheOrganizationId,\n dealId: deal.id,\n scopeSignature: buildDealDetailScopeSignature(auth, scope),\n view: viewMode,\n includeKey,\n })\n : null\n\n if (detailCache && detailCacheKey) {\n try {\n const cached = await runWithCacheTenant(cacheTenantId, () => detailCache.get(detailCacheKey))\n if (cached && typeof cached === 'object') {\n const viewerInfo = await resolveViewer()\n return NextResponse.json({\n ...(cached as Record<string, unknown>),\n viewer: {\n userId: viewerUserId,\n name: viewerInfo.name,\n email: viewerInfo.email,\n },\n })\n }\n } catch (err) {\n // A cache-backend read error must degrade to a fresh DB read, never a 500.\n debugCrudCache('get', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n // Issue #3175: the reads below only depend on the already-resolved deal +\n // organization scope + decryption scope, so they are grouped into Promise.all\n // batches instead of being awaited one after another. True dependencies stay\n // ordered: preview/decrypt run after their link rows, pipeline reads after the\n // effective stage is known, and the appearance map / audit fallback after the\n // pipeline stages load. This mirrors the parallel-read pattern already used\n // across the customers list/interactions/activities routes.\n const resolveAssociations = async (): Promise<{\n people: DealAssociation[]\n companies: DealAssociation[]\n linkedPersonIds: string[]\n linkedCompanyIds: string[]\n }> => {\n if (liteView) {\n const [personLinkRows, companyLinkRows] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n ])\n\n const linkedPersonIds = Array.from(\n new Set(\n personLinkRows\n .map((link) => {\n const personRef = link.person\n if (!personRef) return null\n if (typeof personRef === 'string') return personRef\n const personIdValue = personRef.id\n return typeof personIdValue === 'string' ? personIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n const linkedCompanyIds = Array.from(\n new Set(\n companyLinkRows\n .map((link) => {\n const companyRef = link.company\n if (!companyRef) return null\n if (typeof companyRef === 'string') return companyRef\n const companyIdValue = companyRef.id\n return typeof companyIdValue === 'string' ? companyIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n\n const [previewPeople, previewCompanies] = await Promise.all([\n linkedPersonIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedPersonIds.slice(0, 3) } },\n { populate: ['personProfile'] },\n decryptionScope,\n )\n : [],\n linkedCompanyIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedCompanyIds.slice(0, 3) } },\n { populate: ['companyProfile'] },\n decryptionScope,\n )\n : [],\n ])\n const primaryPersonIds = new Set(\n personLinkRows\n .filter((link) => link.isPrimary === true)\n .map((link) => (typeof link.person === 'string' ? link.person : link.person?.id))\n .filter((value): value is string => typeof value === 'string'),\n )\n const previewPeopleMap = new Map(previewPeople.map((entity) => [entity.id, entity]))\n const previewCompaniesMap = new Map(previewCompanies.map((entity) => [entity.id, entity]))\n const people = linkedPersonIds.slice(0, 3).reduce<DealAssociation[]>((acc, personId) => {\n const entity = previewPeopleMap.get(personId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: primaryPersonIds.has(entity.id) })\n return acc\n }, [])\n const companies = linkedCompanyIds.slice(0, 3).reduce<DealAssociation[]>((acc, companyId) => {\n const entity = previewCompaniesMap.get(companyId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return { people, companies, linkedPersonIds, linkedCompanyIds }\n }\n\n const [personLinks, companyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { populate: ['person', 'person.personProfile'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { populate: ['company', 'company.companyProfile'] },\n decryptionScope,\n ),\n ])\n const fallbackTenantId = deal.tenantId ?? auth.tenantId ?? null\n const fallbackOrgId = deal.organizationId ?? auth.orgId ?? null\n await decryptEntitiesWithFallbackScope(personLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n await decryptEntitiesWithFallbackScope(companyLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n\n const people = personLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.person as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: link.isPrimary === true })\n return acc\n }, [])\n\n const companies = companyLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.company as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return {\n people,\n companies,\n linkedPersonIds: people.map((entry) => entry.id),\n linkedCompanyIds: companies.map((entry) => entry.id),\n }\n }\n\n const resolveOwner = async () =>\n deal.ownerUserId\n ? await findOneWithDecryption(\n em,\n User,\n { id: deal.ownerUserId, tenantId: deal.tenantId ?? auth.tenantId ?? null },\n {},\n decryptionScope,\n )\n : null\n\n const resolveStageTransitions = async (): Promise<CustomerDealStageTransition[]> => {\n if (!includeStages) return []\n try {\n return await findWithDecryption(\n em,\n CustomerDealStageTransition,\n { deal: deal.id, deletedAt: null },\n { orderBy: { stageOrder: 'ASC', transitionedAt: 'ASC' } },\n decryptionScope,\n )\n } catch (error) {\n if (!isMissingDealStageTransitionTable(error)) {\n throw error\n }\n warnMissingDealStageTransitionTable('customers.api.deals.detail.GET')\n return []\n }\n }\n\n const [associations, customFieldValues, viewerInfo, owner, effectiveStage, stageTransitions] = await Promise.all([\n resolveAssociations(),\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_deal,\n recordIds: [deal.id],\n tenantIdByRecord: { [deal.id]: deal.tenantId ?? null },\n organizationIdByRecord: { [deal.id]: deal.organizationId ?? null },\n tenantFallbacks: [deal.tenantId ?? auth.tenantId ?? null].filter((value): value is string => !!value),\n }),\n resolveViewer(),\n resolveOwner(),\n includeStages ? resolveEffectivePipelineStage(em, deal, decryptionScope) : Promise.resolve(null),\n resolveStageTransitions(),\n ])\n\n const { people, companies, linkedPersonIds, linkedCompanyIds } = associations\n const customFields = normalizeCustomFieldResponse(customFieldValues[deal.id]) ?? {}\n const viewerName = viewerInfo.name\n const viewerEmail = viewerInfo.email\n const ownerPayload = owner\n ? {\n id: owner.id,\n name: owner.name ?? owner.email ?? owner.id,\n email: owner.email ?? '',\n }\n : null\n\n const effectivePipelineId = deal.pipelineId ?? effectiveStage?.pipelineId ?? null\n const effectivePipelineStageId = deal.pipelineStageId ?? effectiveStage?.id ?? null\n const effectivePipelineStageLabel = deal.pipelineStage ?? effectiveStage?.label ?? null\n\n const [pipelineStages, pipeline] = await Promise.all([\n includeStages && effectivePipelineId\n ? findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n pipelineId: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n : [],\n effectivePipelineId\n ? findOneWithDecryption(\n em,\n CustomerPipeline,\n {\n id: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n : null,\n ])\n\n const persistedStageTransitions = stageTransitions.map((transition) => ({\n stageId: transition.stageId,\n stageLabel: transition.stageLabel,\n stageOrder: transition.stageOrder,\n transitionedAt: transition.transitionedAt.toISOString(),\n }))\n const [pipelineStageAppearanceMap, recoveredStageTransitions] = await Promise.all([\n pipelineStages.length\n ? loadPipelineStageAppearanceMap(em, pipelineStages, deal.organizationId, deal.tenantId)\n : new Map<string, CustomerDictionaryEntry>(),\n includeStages && persistedStageTransitions.length === 0\n ? loadAuditStageTransitionsFallback({ container, deal, pipelineStages })\n : [],\n ])\n const effectiveCurrentStage = (() => {\n if (!effectivePipelineStageId) return null\n const matchingStage = pipelineStages.find((stage) => stage.id === effectivePipelineStageId)\n if (matchingStage) {\n return {\n id: matchingStage.id,\n label: matchingStage.label,\n order: matchingStage.order,\n }\n }\n if (!effectivePipelineStageLabel) return null\n return {\n id: effectivePipelineStageId,\n label: effectivePipelineStageLabel,\n order: 0,\n }\n })()\n const stageTransitionPayload = mergeStageTransitions({\n persisted: persistedStageTransitions,\n recovered: recoveredStageTransitions,\n currentStage: effectiveCurrentStage,\n fallbackTimestamp: deal.createdAt.toISOString(),\n })\n\n const viewer = {\n userId: viewerUserId,\n name: viewerName,\n email: viewerEmail,\n }\n\n // Everything except the request-specific viewer block is shareable across\n // authorized callers, so it is what gets cached.\n const cacheableBody = {\n deal: {\n id: deal.id,\n title: deal.title,\n description: deal.description ?? null,\n status: deal.status ?? null,\n pipelineStage: effectivePipelineStageLabel,\n pipelineId: effectivePipelineId,\n pipelineStageId: effectivePipelineStageId,\n valueAmount: deal.valueAmount ?? null,\n valueCurrency: deal.valueCurrency ?? null,\n probability: deal.probability ?? null,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId ?? null,\n source: deal.source ?? null,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n organizationId: deal.organizationId ?? null,\n tenantId: deal.tenantId ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n },\n people,\n companies,\n linkedPersonIds,\n linkedCompanyIds,\n counts: {\n people: linkedPersonIds.length,\n companies: linkedCompanyIds.length,\n },\n customFields,\n pipelineStages: pipelineStages.map((stage) => {\n const appearance = pipelineStageAppearanceMap.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n label: stage.label,\n order: stage.order,\n color: appearance?.color ?? null,\n icon: appearance?.icon ?? null,\n }\n }),\n pipelineName: pipeline?.name ?? null,\n stageTransitions: stageTransitionPayload,\n owner: ownerPayload,\n }\n\n if (detailCache && detailCacheKey) {\n try {\n await runWithCacheTenant(cacheTenantId, () =>\n detailCache.set(detailCacheKey, cacheableBody, {\n ttl: DEAL_DETAIL_CACHE_TTL_MS,\n tags: buildDealDetailCacheTags(cacheTenantId, cacheOrganizationId),\n }),\n )\n } catch (err) {\n // A cache write must never break the request; log it for observability\n // instead of failing the response (matches the CRUD factory).\n debugCrudCache('store', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return NextResponse.json({ ...cacheableBody, viewer })\n}\n\nconst dealDetailQuerySchema = z.object({\n include: z.string().optional(),\n})\n\nconst pipelineStageInfoSchema = z.object({\n id: z.string().uuid(),\n label: z.string(),\n order: z.number().int(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n})\n\nconst stageTransitionInfoSchema = z.object({\n stageId: z.string().uuid(),\n stageLabel: z.string(),\n stageOrder: z.number().int(),\n transitionedAt: z.string(),\n})\n\nconst dealDetailResponseSchema = z.object({\n deal: z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n pipelineId: z.string().uuid().nullable().optional(),\n pipelineStageId: z.string().uuid().nullable().optional(),\n valueAmount: z.string().nullable().optional(),\n valueCurrency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expectedCloseAt: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n closureOutcome: z.enum(['won', 'lost']).nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n people: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('person'),\n isPrimary: z.boolean().optional(),\n }),\n ),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('company'),\n }),\n ),\n customFields: z.record(z.string(), z.unknown()),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n pipelineStages: z.array(pipelineStageInfoSchema),\n stageTransitions: z.array(stageTransitionInfoSchema),\n owner: z.object({\n id: z.string().uuid(),\n name: z.string(),\n email: z.string(),\n }).nullable(),\n})\n\nconst dealDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal detail',\n methods: {\n GET: {\n summary: 'Fetch deal with associations and pipeline context',\n description: 'Returns a deal with linked people, companies, closure fields, optional pipeline history, custom fields, and viewer context.',\n query: dealDetailQuerySchema,\n responses: [\n { status: 200, description: 'Deal detail payload', schema: dealDetailResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dealDetailErrorSchema },\n { status: 403, description: 'Forbidden \u2014 caller lacks the required feature', schema: dealDetailErrorSchema },\n { status: 404, description: 'Deal not found, or its organization is not in the caller\u2019s scope', schema: dealDetailErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["viewer", "viewerInfo", "linkedPersonIds", "linkedCompanyIds", "people", "companies"]
|
|
7
7
|
}
|
|
@@ -17,9 +17,6 @@ const paramsSchema = z.object({
|
|
|
17
17
|
function notFound(message) {
|
|
18
18
|
return NextResponse.json({ error: message }, { status: 404 });
|
|
19
19
|
}
|
|
20
|
-
function forbidden(message) {
|
|
21
|
-
return NextResponse.json({ error: message }, { status: 403 });
|
|
22
|
-
}
|
|
23
20
|
function badRequest(message, code) {
|
|
24
21
|
return NextResponse.json(
|
|
25
22
|
code ? { error: message, code } : { error: message },
|
|
@@ -53,22 +50,6 @@ async function GET(request, context) {
|
|
|
53
50
|
if (!auth?.sub && !auth?.isApiKey) {
|
|
54
51
|
return NextResponse.json({ error: translate("customers.errors.authentication_required", "Authentication required") }, { status: 401 });
|
|
55
52
|
}
|
|
56
|
-
let rbac = null;
|
|
57
|
-
try {
|
|
58
|
-
rbac = container.resolve("rbacService");
|
|
59
|
-
} catch {
|
|
60
|
-
rbac = null;
|
|
61
|
-
}
|
|
62
|
-
if (!rbac || !auth?.sub) {
|
|
63
|
-
return forbidden(translate("customers.errors.access_denied", "Access denied"));
|
|
64
|
-
}
|
|
65
|
-
const hasFeature = await rbac.userHasAllFeatures(auth.sub, ["customers.deals.view"], {
|
|
66
|
-
tenantId: auth.tenantId ?? null,
|
|
67
|
-
organizationId: auth.orgId ?? null
|
|
68
|
-
});
|
|
69
|
-
if (!hasFeature) {
|
|
70
|
-
return forbidden(translate("customers.errors.access_denied", "Access denied"));
|
|
71
|
-
}
|
|
72
53
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request });
|
|
73
54
|
const em = container.resolve("em");
|
|
74
55
|
const deal = await findOneWithDecryption(
|
|
@@ -82,7 +63,7 @@ async function GET(request, context) {
|
|
|
82
63
|
return notFound(translate("customers.errors.deal_not_found", "Deal not found"));
|
|
83
64
|
}
|
|
84
65
|
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {
|
|
85
|
-
return
|
|
66
|
+
return notFound(translate("customers.errors.deal_not_found", "Deal not found"));
|
|
86
67
|
}
|
|
87
68
|
if (!deal.closureOutcome) {
|
|
88
69
|
return badRequest(translate("customers.errors.deal_not_closed", "Deal is not closed"), "DEAL_NOT_CLOSED");
|
|
@@ -174,8 +155,8 @@ const openApi = {
|
|
|
174
155
|
errors: [
|
|
175
156
|
{ status: 400, description: "Deal is not closed", schema: dealStatsErrorSchema },
|
|
176
157
|
{ status: 401, description: "Unauthorized", schema: dealStatsErrorSchema },
|
|
177
|
-
{ status: 403, description: "Forbidden
|
|
178
|
-
{ status: 404, description: "Deal not found", schema: dealStatsErrorSchema }
|
|
158
|
+
{ status: 403, description: "Forbidden \u2014 caller lacks the required feature", schema: dealStatsErrorSchema },
|
|
159
|
+
{ status: 404, description: "Deal not found, or its organization is not in the caller\u2019s scope", schema: dealStatsErrorSchema }
|
|
179
160
|
]
|
|
180
161
|
}
|
|
181
162
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/api/deals/%5Bid%5D/stats/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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerDeal, CustomerPipeline } from '../../../../data/entities'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport type {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAEpC,SAAS,cAAc,wBAAwB;AAC/C,SAAS,uBAAuB;
|
|
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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerDeal, CustomerPipeline } from '../../../../data/entities'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction badRequest(message: string, code?: string) {\n return NextResponse.json(\n code ? { error: message, code } : { error: message },\n { status: 400 },\n )\n}\n\nfunction startOfIsoWeek(date: Date): Date {\n const value = new Date(date)\n const day = value.getDay()\n const diff = day === 0 ? -6 : 1 - day\n value.setHours(0, 0, 0, 0)\n value.setDate(value.getDate() + diff)\n return value\n}\n\nfunction startOfQuarter(date: Date): Date {\n return new Date(date.getFullYear(), Math.floor(date.getMonth() / 3) * 3, 1)\n}\n\nfunction calculateSalesCycleDays(createdAt: Date, closedAt: Date): number {\n const diffMs = closedAt.getTime() - createdAt.getTime()\n if (diffMs <= 0) return 0\n return Math.floor(diffMs / 86400000)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const { translate } = await resolveTranslations()\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: translate('customers.errors.authentication_required', 'Authentication required') }, { status: 401 })\n }\n\n // The API dispatcher already enforces `requireFeatures: ['customers.deals.view']`\n // against the caller's effective (selected) organization before this handler\n // runs, so no in-route feature re-check is needed. Re-checking against the token\n // org (auth.orgId) would wrongly 403 org-switched callers (#5012).\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, tenantId: auth.tenantId ?? null, deletedAt: null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n // Existence oracle (#5504): deny a cross-org read as not-found \u2014 identical to\n // the deal-not-found above \u2014 so it cannot reveal that a deal exists in an\n // organization the caller cannot see.\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n if (!deal.closureOutcome) {\n return badRequest(translate('customers.errors.deal_not_closed', 'Deal is not closed'), 'DEAL_NOT_CLOSED')\n }\n\n const now = new Date()\n const weekStart = startOfIsoWeek(now)\n const quarterStart = startOfQuarter(now)\n const dealsClosedThisPeriod = await em.count(CustomerDeal, {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n closureOutcome: deal.closureOutcome,\n deletedAt: null,\n updatedAt: { $gte: weekStart },\n })\n\n let dealRankInQuarter: number | null = null\n if (deal.closureOutcome === 'won' && deal.valueAmount !== null) {\n const higherValueDeals = await em.count(CustomerDeal, {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n closureOutcome: 'won',\n deletedAt: null,\n updatedAt: { $gte: quarterStart },\n valueAmount: { $gt: deal.valueAmount },\n })\n dealRankInQuarter = higherValueDeals + 1\n }\n\n const pipeline = deal.pipelineId\n ? await findOneWithDecryption(\n em,\n CustomerPipeline,\n { id: deal.pipelineId, tenantId: deal.tenantId, organizationId: deal.organizationId },\n {},\n { tenantId: deal.tenantId, organizationId: deal.organizationId },\n )\n : null\n\n let lossReasonLabel: string | null = null\n if (deal.lossReasonId) {\n const dictionaryEntry = await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: deal.lossReasonId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { populate: ['dictionary'] },\n { tenantId: deal.tenantId, organizationId: deal.organizationId },\n )\n const dictionaryKey =\n dictionaryEntry?.dictionary &&\n typeof (dictionaryEntry.dictionary as { key?: unknown }).key === 'string'\n ? (dictionaryEntry.dictionary as { key: string }).key\n : null\n if (dictionaryKey === 'sales.deal_loss_reason') {\n lossReasonLabel = dictionaryEntry?.label ?? dictionaryEntry?.value ?? null\n }\n }\n\n return NextResponse.json({\n dealValue: deal.valueAmount !== null ? Number(deal.valueAmount) : null,\n dealCurrency: deal.valueCurrency ?? null,\n closureOutcome: deal.closureOutcome,\n closedAt: deal.updatedAt.toISOString(),\n pipelineName: pipeline?.name ?? null,\n dealsClosedThisPeriod,\n salesCycleDays: calculateSalesCycleDays(deal.createdAt, deal.updatedAt),\n dealRankInQuarter,\n lossReason: lossReasonLabel,\n })\n}\n\nconst dealStatsResponseSchema = z.object({\n dealValue: z.number().nullable(),\n dealCurrency: z.string().nullable(),\n closureOutcome: z.enum(['won', 'lost']),\n closedAt: z.string(),\n pipelineName: z.string().nullable(),\n dealsClosedThisPeriod: z.number().int(),\n salesCycleDays: z.number().int().nullable(),\n dealRankInQuarter: z.number().int().nullable(),\n lossReason: z.string().nullable(),\n})\n\nconst dealStatsErrorSchema = z.object({\n error: z.string(),\n code: z.string().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal closure stats',\n methods: {\n GET: {\n summary: 'Fetch analytics for a closed deal',\n description: 'Returns week-to-date closure counts, sales cycle length, quarter ranking, and loss reason context for a closed deal.',\n responses: [\n { status: 200, description: 'Deal closure stats payload', schema: dealStatsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Deal is not closed', schema: dealStatsErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dealStatsErrorSchema },\n { status: 403, description: 'Forbidden \u2014 caller lacks the required feature', schema: dealStatsErrorSchema },\n { status: 404, description: 'Deal not found, or its organization is not in the caller\u2019s scope', schema: dealStatsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -57,9 +57,9 @@ async function buildContext(request) {
|
|
|
57
57
|
ctx: context.commandContext
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
function ensureRouteOrganizationAccess(organizationId, scope, auth,
|
|
60
|
+
function ensureRouteOrganizationAccess(organizationId, scope, auth, notFoundMessage) {
|
|
61
61
|
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {
|
|
62
|
-
throw
|
|
62
|
+
throw notFound(notFoundMessage);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
async function ensureFeatureOnOrganization(container, auth, feature, organizationId, translate) {
|
|
@@ -96,7 +96,7 @@ async function resolveEntityRouteScope(em, auth, scope, entityType, entityId, tr
|
|
|
96
96
|
if (!entity || entity.tenantId !== auth.tenantId) {
|
|
97
97
|
throw notFound(translate("customers.errors.customer_not_found", "Customer not found"));
|
|
98
98
|
}
|
|
99
|
-
ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate);
|
|
99
|
+
ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate("customers.errors.customer_not_found", "Customer not found"));
|
|
100
100
|
return {
|
|
101
101
|
entity,
|
|
102
102
|
organizationId: entity.organizationId,
|
|
@@ -114,7 +114,7 @@ async function resolveRoleRouteScope(em, auth, scope, entityType, entityId, role
|
|
|
114
114
|
if (!role || role.tenantId !== auth.tenantId || role.entityType !== entityType || role.entityId !== entityId) {
|
|
115
115
|
throw notFound(translate("customers.errors.role_not_found", "Role not found"));
|
|
116
116
|
}
|
|
117
|
-
ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate);
|
|
117
|
+
ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate("customers.errors.role_not_found", "Role not found"));
|
|
118
118
|
return {
|
|
119
119
|
role,
|
|
120
120
|
organizationId: role.organizationId,
|