@open-mercato/core 0.6.6-develop.6429.1.2fba7258d1 → 0.6.6-develop.6431.1.g1037269810
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.
|
@@ -35,12 +35,47 @@ import { loadPersonCompanyLinks, summarizePersonCompanies } from "../../../lib/p
|
|
|
35
35
|
import { normalizeCustomerDetailCustomFields } from "../../detailCustomFields.js";
|
|
36
36
|
import { buildEmailVisibilityMikroFilter } from "../../../lib/visibilityFilter.js";
|
|
37
37
|
import { resolveCustomerDetailTenantScope } from "../../../lib/detailTenantScope.js";
|
|
38
|
+
import { runWithCacheTenant } from "@open-mercato/cache";
|
|
39
|
+
import { buildCollectionTags, canonicalizeResourceTag, isCrudCacheEnabled, resolveCrudCache } from "@open-mercato/shared/lib/crud/cache";
|
|
38
40
|
const metadata = {
|
|
39
41
|
GET: { requireAuth: true, requireFeatures: ["customers.people.view"] }
|
|
40
42
|
};
|
|
41
43
|
const paramsSchema = z.object({
|
|
42
44
|
id: z.string().uuid()
|
|
43
45
|
});
|
|
46
|
+
const PERSON_DETAIL_TTL_MS = 6e4;
|
|
47
|
+
const PERSON_DETAIL_TAG_RESOURCES = [
|
|
48
|
+
"customers.person",
|
|
49
|
+
"customers.address",
|
|
50
|
+
"customers.tagAssignment",
|
|
51
|
+
"customers.labelAssignment",
|
|
52
|
+
"customers.personCompanyLink",
|
|
53
|
+
"customers.interaction",
|
|
54
|
+
"customers.activity"
|
|
55
|
+
];
|
|
56
|
+
function buildPersonDetailCacheKey(params) {
|
|
57
|
+
const include = [...params.includeTokens].sort((a, b) => a.localeCompare(b)).join("|") || "none";
|
|
58
|
+
const scoped = [...params.scopedOrganizationIds].sort((a, b) => a.localeCompare(b)).join("|") || "null";
|
|
59
|
+
return [
|
|
60
|
+
"customers:person-detail",
|
|
61
|
+
params.personId,
|
|
62
|
+
`tenant=${params.tenantId ?? "null"}`,
|
|
63
|
+
`org=${params.organizationId ?? "null"}`,
|
|
64
|
+
`caller=${params.callerId ?? "null"}`,
|
|
65
|
+
`selOrg=${params.selectedOrganizationId ?? "null"}`,
|
|
66
|
+
`scope=${scoped}`,
|
|
67
|
+
`mode=${params.interactionMode}`,
|
|
68
|
+
`include=${include}`
|
|
69
|
+
].join(":");
|
|
70
|
+
}
|
|
71
|
+
function buildPersonDetailCacheTags(tenantId, organizationId) {
|
|
72
|
+
const tags = [];
|
|
73
|
+
for (const resource of PERSON_DETAIL_TAG_RESOURCES) {
|
|
74
|
+
const canonical = canonicalizeResourceTag(resource) ?? resource;
|
|
75
|
+
tags.push(...buildCollectionTags(canonical, tenantId, [organizationId]));
|
|
76
|
+
}
|
|
77
|
+
return tags;
|
|
78
|
+
}
|
|
44
79
|
function parseIncludeParams(request) {
|
|
45
80
|
const url = new URL(request.url);
|
|
46
81
|
const raw = url.searchParams.getAll("include");
|
|
@@ -410,6 +445,29 @@ async function GET(_req, ctx) {
|
|
|
410
445
|
profileMeta = { reason: "organization_forbidden" };
|
|
411
446
|
return forbidden("Access denied");
|
|
412
447
|
}
|
|
448
|
+
const personDetailTenantId = person.tenantId ?? auth.tenantId ?? null;
|
|
449
|
+
const personDetailOrganizationId = person.organizationId ?? null;
|
|
450
|
+
const personDetailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null;
|
|
451
|
+
const personDetailCacheKey = personDetailCache ? buildPersonDetailCacheKey({
|
|
452
|
+
personId: person.id,
|
|
453
|
+
tenantId: personDetailTenantId,
|
|
454
|
+
organizationId: personDetailOrganizationId,
|
|
455
|
+
callerId: auth.sub ?? null,
|
|
456
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
457
|
+
scopedOrganizationIds: Array.isArray(scope?.filterIds) ? scope.filterIds : [],
|
|
458
|
+
interactionMode,
|
|
459
|
+
includeTokens: Array.from(includeTokens)
|
|
460
|
+
}) : null;
|
|
461
|
+
const personDetailCacheTags = personDetailCache ? buildPersonDetailCacheTags(personDetailTenantId, personDetailOrganizationId) : [];
|
|
462
|
+
if (personDetailCache && personDetailCacheKey) {
|
|
463
|
+
const cached = await runWithCacheTenant(personDetailTenantId, () => personDetailCache.get(personDetailCacheKey));
|
|
464
|
+
if (cached) {
|
|
465
|
+
statusCode = 200;
|
|
466
|
+
profileMeta = { cacheHit: true, include: Array.from(includeTokens), interactionMode };
|
|
467
|
+
profiler.mark("cache_hit");
|
|
468
|
+
return NextResponse.json(cached);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
413
471
|
const [profileResult, personCompanyLinks] = await Promise.all([
|
|
414
472
|
findOneWithDecryption(em, CustomerPersonProfile, { entity: person }, { populate: ["company"] }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }),
|
|
415
473
|
loadPersonCompanyLinks(em, person)
|
|
@@ -656,7 +714,7 @@ async function GET(_req, ctx) {
|
|
|
656
714
|
deals: dealsCount,
|
|
657
715
|
companies: companies.length
|
|
658
716
|
};
|
|
659
|
-
const
|
|
717
|
+
const payload = {
|
|
660
718
|
interactionMode,
|
|
661
719
|
person: {
|
|
662
720
|
id: person.id,
|
|
@@ -830,7 +888,20 @@ async function GET(_req, ctx) {
|
|
|
830
888
|
name: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.name ?? null : null,
|
|
831
889
|
email: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.email ?? auth.email ?? null : auth.email ?? null
|
|
832
890
|
}
|
|
833
|
-
}
|
|
891
|
+
};
|
|
892
|
+
if (personDetailCache && personDetailCacheKey) {
|
|
893
|
+
try {
|
|
894
|
+
await runWithCacheTenant(
|
|
895
|
+
personDetailTenantId,
|
|
896
|
+
() => personDetailCache.set(personDetailCacheKey, payload, {
|
|
897
|
+
ttl: PERSON_DETAIL_TTL_MS,
|
|
898
|
+
tags: personDetailCacheTags
|
|
899
|
+
})
|
|
900
|
+
);
|
|
901
|
+
} catch {
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const response = NextResponse.json(payload);
|
|
834
905
|
statusCode = 200;
|
|
835
906
|
profileMeta = {
|
|
836
907
|
include: Array.from(includeTokens),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/people/%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 CustomerEntity,\n CustomerPersonProfile,\n CustomerAddress,\n CustomerComment,\n CustomerActivity,\n CustomerTagAssignment,\n CustomerTag,\n CustomerLabelAssignment,\n CustomerLabel,\n CustomerDealPersonLink,\n CustomerDeal,\n CustomerTodoLink,\n CustomerInteraction,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport { mergePersonCustomFieldValues, resolvePersonCustomFieldRouting } from '../../../lib/customFieldRouting'\nimport {\n CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n EXAMPLE_TODO_SOURCE,\n CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n mapInteractionRecordToActivitySummary,\n mapInteractionRecordToTodoSummary,\n} from '../../../lib/interactionCompatibility'\nimport { resolveCustomerInteractionFeatureFlags } from '../../../lib/interactionFeatureFlags'\nimport { hydrateCanonicalInteractions } from '../../../lib/interactionReadModel'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanFromUnknown, parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/personCompanies'\nimport { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'\nimport { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'\nimport { resolveCustomerDetailTenantScope } from '../../../lib/detailTenantScope'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction parseIncludeParams(request: Request): Set<string> {\n const url = new URL(request.url)\n const raw = url.searchParams.getAll('include')\n const tokens = new Set<string>()\n raw.forEach((entry) => {\n if (!entry) return\n entry\n .split(',')\n .map((part) => part.trim().toLowerCase())\n .filter((part) => part.length > 0)\n .forEach((part) => tokens.add(part))\n })\n return tokens\n}\n\nfunction forbidden(message: string) {\n return NextResponse.json({ error: message }, { status: 403 })\n}\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction serializeTags(assignments: CustomerTagAssignment[]): Array<{ id: string; label: string; color: string | null }> {\n return assignments\n .map((assignment) => {\n const tag = assignment.tag as CustomerTag | string | null\n if (!tag || typeof tag === 'string') return null\n return {\n id: tag.id,\n label: tag.label,\n color: tag.color ?? null,\n }\n })\n .filter(\n (tag: { id: string; label: string; color: string | null } | null): tag is {\n id: string\n label: string\n color: string | null\n } => tag !== null,\n )\n}\n\ntype TodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n customValues: Record<string, unknown> | null\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n const candidates = ['title', 'subject', 'name', 'summary', 'text', 'description']\n for (const key of candidates) {\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 parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n const ts = value.getTime()\n return Number.isNaN(ts) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return null\n const candidate = new Date(trimmed)\n if (!Number.isNaN(candidate.getTime())) return candidate.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\ntype RouteProfilerMark = { label: string; time: bigint; extra?: Record<string, unknown> }\ntype RouteProfiler = { enabled: boolean; mark: (label: string, extra?: Record<string, unknown>) => void; end: (extra?: Record<string, unknown>) => void }\n\nfunction normalizeProfilerTokens(input: string | null | undefined): string[] {\n if (!input) return []\n return input\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter((token) => token.length > 0)\n}\n\nfunction profilerMatches(scope: string, tokens: string[]): boolean {\n if (!tokens.length) return false\n const lower = scope.toLowerCase()\n return tokens.some((token) => {\n if (token === '*' || token === 'all' || parseBooleanToken(token) === true) return true\n if (token.endsWith('*')) {\n const prefix = token.slice(0, -1)\n return prefix.length === 0 ? true : lower.startsWith(prefix)\n }\n return token === lower\n })\n}\n\nfunction isRouteProfilingEnabled(scope: string): boolean {\n const candidates = [\n process.env.OM_CRUD_PROFILE,\n process.env.NEXT_PUBLIC_OM_CRUD_PROFILE,\n process.env.OM_ROUTE_PROFILE,\n process.env.NEXT_PUBLIC_OM_ROUTE_PROFILE,\n ]\n for (const raw of candidates) {\n if (!raw) continue\n const tokens = normalizeProfilerTokens(raw)\n if (profilerMatches(scope, tokens)) return true\n }\n return false\n}\n\nfunction createRouteProfiler(scope: string): RouteProfiler {\n if (!isRouteProfilingEnabled(scope)) {\n return { enabled: false, mark: () => {}, end: () => {} }\n }\n const marks: RouteProfilerMark[] = [{ label: 'start', time: process.hrtime.bigint() }]\n const round = (value: number) => Math.round(value * 1000) / 1000\n return {\n enabled: true,\n mark(label, extra) {\n marks.push({ label, time: process.hrtime.bigint(), extra })\n },\n end(extra) {\n marks.push({ label: 'end', time: process.hrtime.bigint(), extra })\n const segments: Array<{ step: string; durationMs: number; extra?: Record<string, unknown> }> = []\n for (let idx = 1; idx < marks.length; idx += 1) {\n const current = marks[idx]\n const previous = marks[idx - 1]\n if (current.label === 'end') continue\n const durationMs = Number(current.time - previous.time) / 1_000_000\n segments.push({\n step: current.label,\n durationMs: round(durationMs),\n ...(current.extra ? { extra: current.extra } : {}),\n })\n }\n const totalNs = marks[marks.length - 1].time - marks[0].time\n const payload: Record<string, unknown> = {\n scope,\n totalMs: round(Number(totalNs) / 1_000_000),\n steps: segments,\n }\n const tail = marks[marks.length - 1]\n if (tail.extra && Object.keys(tail.extra).length > 0) payload.meta = tail.extra\n else if (extra && Object.keys(extra).length > 0) payload.meta = extra\n try {\n console.info('[route:profile]', payload)\n } catch {\n // ignore logging failures\n }\n },\n }\n}\n\nasync function resolveTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n profiler?: RouteProfiler,\n): Promise<Map<string, TodoDetail>> {\n const details = new Map<string, TodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrgIds = organizationIds\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source = typeof link.todoSource === 'string' && link.todoSource.trim().length > 0 ? link.todoSource : EXAMPLE_TODO_SOURCE\n const id = typeof link.todoId === 'string' && link.todoId.trim().length > 0 ? link.todoId : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n profiler?.mark('todo_links_grouped', { sourceCount: idsBySource.size, linkCount: links.length })\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n profiler?.mark('todo_query_start', { source, count: ids.length })\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrgIds.length > 0 ? scopedOrgIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n const itemCount = Array.isArray(result.items) ? result.items.length : 0\n profiler?.mark('todo_query_complete', { source, itemCount })\n let enriched = 0\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId = typeof record.id === 'string' && record.id.trim().length > 0 ? record.id : String(record.id ?? '')\n if (!rawId) continue\n const title = extractTodoTitle(record)\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n })()\n const descriptionValue = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n })()\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n const organizationId = typeof record.organization_id === 'string' && record.organization_id.trim().length > 0\n ? record.organization_id\n : (typeof record.organizationId === 'string' && record.organizationId.trim().length > 0 ? record.organizationId : null)\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: unknown, value: unknown) => {\n if (typeof key !== 'string') return\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [nestedKey, nestedValue] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(nestedKey, nestedValue)\n }\n }\n\n details.set(`${source}:${rawId}`, {\n title,\n isDone,\n priority,\n severity,\n description: descriptionValue,\n dueAt,\n organizationId,\n customValues: Object.keys(customValues).length ? customValues : null,\n })\n enriched += 1\n }\n profiler?.mark('todo_items_processed', { source, enriched })\n } catch (err) {\n profiler?.mark('todo_query_failed', { source, error: err instanceof Error ? err.message : String(err) })\n console.warn(`customers.people.detail: failed to resolve todos for source ${source}`, err)\n }\n }\n\n profiler?.mark('todo_details_ready', { enrichedCount: details.size })\n return details\n}\n\nexport async function GET(_req: Request, ctx: { params?: { id?: string } }) {\n const profiler = createRouteProfiler('customers.people.detail')\n profiler.mark('request_received')\n\n const includeTokens = parseIncludeParams(_req)\n profiler.mark('includes_parsed', { include: Array.from(includeTokens) })\n\n const includeActivities = includeTokens.has('activities')\n const includeAddresses = includeTokens.has('addresses')\n const includeComments = includeTokens.has('comments') || includeTokens.has('notes')\n const includeDeals = includeTokens.has('deals')\n const includeInteractions = includeTokens.has('interactions')\n const includeTodos = includeTokens.has('todos') || includeTokens.has('tasks')\n const plannedPreviewLimit = 5\n\n let statusCode = 500\n let profileMeta: Record<string, unknown> | undefined\n let addresses: CustomerAddress[] = []\n let comments: CustomerComment[] = []\n let activities: CustomerActivity[] = []\n let todoLinks: CustomerTodoLink[] = []\n let todoDetails = new Map<string, TodoDetail>()\n let deals: CustomerDeal[] = []\n let tagAssignments: CustomerTagAssignment[] = []\n let userMap = new Map<string, { name: string | null; email: string | null }>()\n let customFields: Record<string, unknown> = {}\n let viewerUserId: string | null = null\n let profile: CustomerPersonProfile | null = null\n let companies: Array<{ linkId: string | null; companyId: string; displayName: string; isPrimary: boolean; synthetic?: boolean }> = []\n\n try {\n const parse = paramsSchema.safeParse({ id: ctx.params?.id })\n if (!parse.success) {\n statusCode = 400\n profileMeta = { reason: 'invalid_person_id' }\n return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })\n }\n profiler.mark('params_resolved', { id: parse.data.id })\n\n const auth = await getAuthFromRequest(_req)\n if (!auth) {\n profiler.mark('auth_missing')\n statusCode = 401\n profileMeta = { reason: 'unauthorized' }\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n profiler.mark('auth_resolved', {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n userId: auth.sub ?? null,\n isApiKey: !!auth.isApiKey,\n })\n viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n\n const container = await createRequestContainer()\n profiler.mark('container_resolved')\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: _req })\n const interactionFlags = await resolveCustomerInteractionFeatureFlags(container, scope?.tenantId ?? auth.tenantId)\n const interactionMode = interactionFlags.unified ? 'canonical' : 'legacy'\n profiler.mark('scope_resolved', {\n scopedOrganizations: Array.isArray(scope?.filterIds) ? scope.filterIds.length : 0,\n })\n const em = (container.resolve('em') as EntityManager)\n\n const tenantScope = resolveCustomerDetailTenantScope(parse.data.id, 'person', auth)\n if (!tenantScope.allowed) {\n statusCode = 404\n profileMeta = { reason: 'person_tenant_mismatch' }\n return notFound('Person not found')\n }\n\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n tenantScope.where,\n {},\n {\n tenantId: scope?.tenantId ?? auth.tenantId ?? null,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n },\n )\n profiler.mark('person_loaded', { found: !!person })\n if (!person) {\n statusCode = 404\n profileMeta = { reason: 'person_not_found' }\n return notFound('Person not found')\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: person.organizationId })) {\n statusCode = 403\n profileMeta = { reason: 'organization_forbidden' }\n return forbidden('Access denied')\n }\n\n const [profileResult, personCompanyLinks] = await Promise.all([\n findOneWithDecryption(em, CustomerPersonProfile, { entity: person }, { populate: ['company'] }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }),\n loadPersonCompanyLinks(em, person),\n ])\n profile = profileResult\n profiler.mark('profile_loaded', { found: !!profile })\n companies = summarizePersonCompanies(profile, personCompanyLinks)\n\n // Per-user email privacy (CRM email integration spec, \"Layer 1 \u2014 DB filter\"):\n // exclude private email interactions owned by other users from every read\n // path that returns customer_interactions. Non-email rows, shared emails, and\n // legacy null-visibility rows pass through. v1 is strict owner-only: there is\n // NO admin bypass \u2014 the filter ignores caller features, and\n // `customers.email.view_private` is reserved (inert) for v2 oversight.\n const emailVisibilityFilter = buildEmailVisibilityMikroFilter({\n currentUserId: viewerUserId,\n userFeatures: undefined,\n })\n\n const personScope = { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }\n const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos\n\n // Audited for #3386 rollout (P3): every findWithDecryption call in this\n // block and in the activities/todoLinks fetches below sorts only on\n // non-encrypted system columns (isPrimary, createdAt, occurredAt,\n // scheduledAt). Each fetch is bounded by entity scope (one person) plus\n // an explicit limit, so neither the paginated-list hazard (#3278) nor the\n // two-phase encrypted-sort migration apply here.\n //\n // These reads only depend on the resolved person + scope + email visibility\n // filter, so dispatch them together to avoid a server-side request waterfall\n // before the detail surface can render (issue #3203).\n const [\n addressesResult,\n tagAssignmentsResult,\n labelAssignments,\n commentsResult,\n canonicalInteractionRows,\n ] = await Promise.all([\n includeAddresses\n ? findWithDecryption(em, CustomerAddress, { entity: person.id }, { orderBy: { isPrimary: 'desc', createdAt: 'desc' } }, personScope)\n : Promise.resolve<CustomerAddress[]>([]),\n findWithDecryption(em, CustomerTagAssignment, { entity: person.id }, { populate: ['tag'] }, personScope),\n findWithDecryption(em, CustomerLabelAssignment, { entity: person.id }, { populate: ['label'] }, personScope),\n includeComments\n ? findWithDecryption(em, CustomerComment, { entity: person.id }, { orderBy: { createdAt: 'desc' }, limit: 50 }, personScope)\n : Promise.resolve<CustomerComment[]>([]),\n shouldLoadCanonicalInteractions\n ? findWithDecryption(\n em,\n CustomerInteraction,\n interactionFlags.unified\n ? { entity: person.id, deletedAt: null, ...emailVisibilityFilter }\n : { entity: person.id, ...emailVisibilityFilter },\n { orderBy: { scheduledAt: 'asc', createdAt: 'desc' }, limit: 100 },\n personScope,\n )\n : Promise.resolve<CustomerInteraction[]>([]),\n ])\n addresses = addressesResult\n tagAssignments = tagAssignmentsResult\n comments = commentsResult\n profiler.mark('addresses_loaded', { count: addresses.length })\n profiler.mark('tags_loaded', { count: tagAssignments.length })\n profiler.mark('labels_loaded', { count: labelAssignments.length })\n profiler.mark('comments_loaded', { count: comments.length })\n profiler.mark('canonical_interactions_loaded', { count: canonicalInteractionRows.length })\n const canonicalActiveInteractions = canonicalInteractionRows.filter((interaction) => !interaction.deletedAt)\n const canonicalInteractions = shouldLoadCanonicalInteractions\n ? await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n interactions: canonicalActiveInteractions,\n enrich: includeInteractions,\n })\n : []\n profiler.mark('canonical_interactions_hydrated', { count: canonicalInteractions.length })\n\n const plannedPreviewInteractions = shouldLoadCanonicalInteractions\n ? (() => {\n const sorted = [...canonicalInteractions]\n .filter((interaction) => interaction.status === 'planned' && interaction.interactionType !== 'task')\n .sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.createdAt).getTime()\n if (leftTime === rightTime) return left.id.localeCompare(right.id)\n return leftTime - rightTime\n })\n return sorted.slice(0, plannedPreviewLimit)\n })()\n : await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n interactions: await findWithDecryption(\n em,\n CustomerInteraction,\n {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n status: 'planned',\n interactionType: { $ne: 'task' },\n ...emailVisibilityFilter,\n },\n { orderBy: { scheduledAt: 'ASC', createdAt: 'ASC' }, limit: plannedPreviewLimit },\n { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },\n ),\n enrich: true,\n })\n\n if (includeActivities && !interactionFlags.unified) {\n activities = await findWithDecryption(em, CustomerActivity, { entity: person.id }, { orderBy: { occurredAt: 'desc', createdAt: 'desc' }, limit: 50 }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n profiler.mark('activities_loaded', { count: activities.length })\n }\n\n if (includeInteractions) {\n profiler.mark('interactions_loaded', { count: canonicalInteractions.length })\n }\n\n if (includeTodos && !interactionFlags.unified) {\n todoLinks = await findWithDecryption(em, CustomerTodoLink, { entity: person.id }, { orderBy: { createdAt: 'desc' }, limit: 50 }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n profiler.mark('todo_links_loaded', { count: todoLinks.length })\n if (todoLinks.length) {\n const queryEngine = (container.resolve('queryEngine') as QueryEngine)\n try {\n todoDetails = await resolveTodoDetails(\n queryEngine,\n todoLinks,\n person.tenantId ?? auth.tenantId ?? null,\n [person.organizationId ?? null, ...(scope?.filterIds ?? [])],\n profiler,\n )\n } catch (err) {\n console.warn('customers.people.detail: failed to enrich todo links', err)\n }\n profiler.mark('todo_details_enriched', { count: todoDetails.size, links: todoLinks.length })\n }\n }\n\n const canonicalActivityBridgeIds = new Set(\n canonicalInteractionRows\n .filter((interaction) => interaction.source === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE)\n .map((interaction) => interaction.id),\n )\n const canonicalTodoBridgeIds = new Set(\n canonicalInteractionRows\n .filter((interaction) => interaction.source === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE)\n .map((interaction) => interaction.id),\n )\n const canonicalActivityItems = canonicalInteractions\n .filter((interaction) => interaction.interactionType !== 'task')\n .map((interaction) => mapInteractionRecordToActivitySummary(interaction))\n const canonicalTodoItems = canonicalInteractions\n .filter((interaction) => interaction.interactionType === 'task')\n .map((interaction) => mapInteractionRecordToTodoSummary(interaction))\n\n const authorIds = new Set<string>()\n if (includeActivities) {\n for (const activity of activities) {\n if (activity.authorUserId) authorIds.add(activity.authorUserId)\n }\n }\n if (includeComments) {\n for (const comment of comments) {\n if (comment.authorUserId) authorIds.add(comment.authorUserId)\n }\n }\n if (viewerUserId) authorIds.add(viewerUserId)\n\n if (authorIds.size) {\n const users = await findWithDecryption(em, User, { id: { $in: Array.from(authorIds) } }, {}, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n userMap = new Map(\n users.map((user) => [\n user.id,\n {\n name: user.name ?? null,\n email: user.email ?? null,\n },\n ])\n )\n profiler.mark('authors_loaded', { count: userMap.size })\n }\n\n if (includeDeals) {\n const dealLinks = await findWithDecryption(\n em,\n CustomerDealPersonLink,\n { person: person.id },\n { populate: ['deal'] },\n { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },\n )\n deals = dealLinks\n .map((link) => (link.deal as CustomerDeal | string | null) ?? null)\n .filter((deal): deal is CustomerDeal => !!deal && typeof deal !== 'string')\n profiler.mark('deals_loaded', { count: deals.length })\n }\n\n // Entity custom fields, profile custom fields, and the routing lookup do not\n // depend on each other (profileId is already resolved above), so load them in\n // parallel instead of three sequential awaits (issue #3203).\n const profileId = profile?.id ?? null\n const [entityCustomFieldValues, profileCustomFieldValues, routing] = await Promise.all([\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_entity,\n recordIds: [person.id],\n tenantIdByRecord: { [person.id]: person.tenantId ?? null },\n organizationIdByRecord: { [person.id]: person.organizationId ?? null },\n tenantFallbacks: [\n person.tenantId ?? auth.tenantId ?? null,\n ].filter((v): v is string => !!v),\n }),\n profileId\n ? loadCustomFieldValues({\n em,\n entityId: E.customers.customer_person_profile,\n recordIds: [profileId],\n tenantIdByRecord: { [profileId]: profile?.tenantId ?? null },\n organizationIdByRecord: { [profileId]: profile?.organizationId ?? null },\n tenantFallbacks: [\n profile?.tenantId ?? person.tenantId ?? auth.tenantId ?? null,\n ].filter((v): v is string => !!v),\n })\n : Promise.resolve<Record<string, Record<string, unknown>>>({}),\n resolvePersonCustomFieldRouting(em, person.tenantId ?? null, person.organizationId ?? null),\n ])\n profiler.mark('entity_custom_fields_loaded', { keys: Object.keys(entityCustomFieldValues?.[person.id] ?? {}).length })\n if (profileId) {\n profiler.mark('profile_custom_fields_loaded', { keys: Object.keys(profileCustomFieldValues?.[profileId] ?? {}).length })\n }\n profiler.mark('custom_field_routing_resolved', { keys: routing.size })\n customFields = normalizeCustomerDetailCustomFields(\n mergePersonCustomFieldValues(\n routing,\n entityCustomFieldValues?.[person.id] ?? {},\n profileId ? profileCustomFieldValues?.[profileId] ?? {} : {},\n ),\n )\n profiler.mark('custom_fields_merged', { keys: Object.keys(customFields).length })\n\n const viewerUserIdFinal = viewerUserId\n // The count fields are independent of each other; dispatch them together so\n // the response counts object is assembled in one round of parallel queries\n // instead of an inline await waterfall (issue #3203).\n const [\n commentsCount,\n activitiesCount,\n interactionsCount,\n todosCount,\n addressesCount,\n dealsCount,\n ] = await Promise.all([\n includeComments\n ? Promise.resolve(comments.length)\n : em.count(CustomerComment, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n interactionType: { $ne: 'task' },\n ...emailVisibilityFilter,\n }),\n em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n ...emailVisibilityFilter,\n }),\n interactionFlags.unified\n ? em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n interactionType: 'task',\n })\n : em.count(CustomerTodoLink, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n includeAddresses\n ? Promise.resolve(addresses.length)\n : em.count(CustomerAddress, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n includeDeals\n ? Promise.resolve(deals.length)\n : em.count(CustomerDealPersonLink, {\n person: person.id,\n }),\n ])\n const counts = {\n tags: tagAssignments.length + labelAssignments.length,\n comments: commentsCount,\n activities: activitiesCount,\n interactions: interactionsCount,\n todos: todosCount,\n addresses: addressesCount,\n deals: dealsCount,\n companies: companies.length,\n }\n\n const response = NextResponse.json({\n interactionMode,\n person: {\n id: person.id,\n displayName: person.displayName,\n description: person.description,\n ownerUserId: person.ownerUserId,\n primaryEmail: person.primaryEmail,\n primaryPhone: person.primaryPhone,\n status: person.status,\n lifecycleStage: person.lifecycleStage,\n source: person.source,\n temperature: person.temperature,\n renewalQuarter: person.renewalQuarter,\n nextInteractionAt: person.nextInteractionAt ? person.nextInteractionAt.toISOString() : null,\n nextInteractionName: person.nextInteractionName,\n nextInteractionRefId: person.nextInteractionRefId,\n nextInteractionIcon: person.nextInteractionIcon,\n nextInteractionColor: person.nextInteractionColor,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n isActive: person.isActive,\n createdAt: person.createdAt.toISOString(),\n updatedAt: person.updatedAt.toISOString(),\n },\n profile: profile\n ? {\n id: profile.id,\n firstName: profile.firstName,\n lastName: profile.lastName,\n preferredName: profile.preferredName,\n jobTitle: profile.jobTitle,\n department: profile.department,\n seniority: profile.seniority,\n timezone: profile.timezone,\n linkedInUrl: profile.linkedInUrl,\n twitterUrl: profile.twitterUrl,\n companyEntityId: profile.company ? (typeof profile.company === 'string' ? profile.company : profile.company.id) : null,\n updatedAt: profile.updatedAt.toISOString(),\n }\n : null,\n customFields,\n tags: [\n ...serializeTags(tagAssignments),\n ...labelAssignments\n .map((a) => {\n const label = a.label as CustomerLabel | string | null\n if (!label || typeof label === 'string') return null\n return { id: label.id, label: label.label, color: null }\n })\n .filter((t): t is { id: string; label: string; color: null } => t !== null),\n ],\n addresses: includeAddresses\n ? addresses.map((address) => ({\n id: address.id,\n name: address.name,\n purpose: address.purpose,\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2,\n buildingNumber: address.buildingNumber,\n flatNumber: address.flatNumber,\n city: address.city,\n region: address.region,\n postalCode: address.postalCode,\n country: address.country,\n latitude: address.latitude,\n longitude: address.longitude,\n isPrimary: address.isPrimary,\n createdAt: address.createdAt.toISOString(),\n }))\n : [],\n comments: includeComments\n ? comments.map((comment) => {\n const authorInfo = comment.authorUserId ? userMap.get(comment.authorUserId) : null\n return {\n id: comment.id,\n body: comment.body,\n authorUserId: comment.authorUserId,\n authorName: authorInfo?.name ?? null,\n authorEmail: authorInfo?.email ?? null,\n dealId: comment.deal ? (typeof comment.deal === 'string' ? comment.deal : comment.deal.id) : null,\n createdAt: comment.createdAt.toISOString(),\n appearanceIcon: comment.appearanceIcon ?? null,\n appearanceColor: comment.appearanceColor ?? null,\n }\n })\n : [],\n activities: includeActivities\n ? (\n interactionFlags.unified\n ? canonicalActivityItems\n : [\n ...activities\n .filter((activity) => !canonicalActivityBridgeIds.has(activity.id))\n .map((activity) => ({\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject,\n body: activity.body,\n occurredAt: activity.occurredAt ? activity.occurredAt.toISOString() : null,\n dealId: activity.deal ? (typeof activity.deal === 'string' ? activity.deal : activity.deal.id) : null,\n authorUserId: activity.authorUserId,\n authorName: activity.authorUserId ? userMap.get(activity.authorUserId)?.name ?? null : null,\n authorEmail: activity.authorUserId ? userMap.get(activity.authorUserId)?.email ?? null : null,\n createdAt: activity.createdAt.toISOString(),\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n })),\n ...canonicalActivityItems.filter(\n (activity) =>\n canonicalInteractions.some(\n (interaction) =>\n interaction.id === activity.id &&\n interaction.source === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n ),\n ),\n ].sort((left, right) => {\n const leftTime = new Date(left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.occurredAt ?? right.createdAt).getTime()\n if (leftTime === rightTime) return right.id.localeCompare(left.id)\n return rightTime - leftTime\n }).slice(0, 50)\n )\n : [],\n interactions: includeInteractions\n ? canonicalInteractions\n : [],\n deals: includeDeals\n ? deals.map((deal) => ({\n id: deal.id,\n title: deal.title,\n status: deal.status,\n pipelineStage: deal.pipelineStage,\n pipelineId: deal.pipelineId ?? null,\n pipelineStageId: deal.pipelineStageId ?? null,\n valueAmount: deal.valueAmount,\n valueCurrency: deal.valueCurrency,\n probability: deal.probability,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId,\n source: deal.source,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n }))\n : [],\n todos: includeTodos\n ? (\n interactionFlags.unified\n ? canonicalTodoItems\n : [\n ...todoLinks\n .filter((link) => !canonicalTodoBridgeIds.has(link.todoId))\n .map((link) => {\n const source = typeof link.todoSource === 'string' && link.todoSource.trim().length > 0 ? link.todoSource : EXAMPLE_TODO_SOURCE\n const key = `${source}:${link.todoId}`\n const detail = todoDetails.get(key)\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource: source,\n createdAt: link.createdAt.toISOString(),\n createdByUserId: link.createdByUserId,\n title: detail?.title ?? null,\n isDone: detail?.isDone ?? null,\n priority: detail?.priority ?? null,\n severity: detail?.severity ?? null,\n description: detail?.description ?? null,\n dueAt: detail?.dueAt ?? null,\n todoOrganizationId: detail?.organizationId ?? null,\n customValues: detail?.customValues ?? null,\n }\n }),\n ...canonicalTodoItems.filter(\n (todo) =>\n canonicalInteractions.some(\n (interaction) =>\n interaction.id === todo.todoId &&\n interaction.source === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n ),\n ),\n ].sort((left, right) => {\n const leftTime = new Date(left.createdAt).getTime()\n const rightTime = new Date(right.createdAt).getTime()\n if (leftTime === rightTime) return right.id.localeCompare(left.id)\n return rightTime - leftTime\n }).slice(0, 50)\n )\n : [],\n isPrimary: companies.some((entry) => entry.isPrimary),\n companies: companies.map((entry) => ({\n id: entry.companyId,\n displayName: entry.displayName,\n isPrimary: entry.isPrimary,\n })),\n company: (() => {\n const primaryCompany = companies.find((entry) => entry.isPrimary) ?? companies[0] ?? null\n return primaryCompany\n ? { id: primaryCompany.companyId, displayName: primaryCompany.displayName }\n : null\n })(),\n plannedActivitiesPreview: plannedPreviewInteractions,\n counts,\n viewer: {\n userId: viewerUserIdFinal,\n name: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.name ?? null : null,\n email: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.email ?? auth.email ?? null : auth.email ?? null,\n },\n })\n statusCode = 200\n profileMeta = {\n include: Array.from(includeTokens),\n interactionMode,\n counts,\n }\n profiler.mark('response_ready', { status: statusCode })\n return response\n } catch (err) {\n if (statusCode < 400) statusCode = 500\n const message = err instanceof Error ? err.message : String(err)\n profileMeta = { ...(profileMeta ?? {}), error: message }\n throw err\n } finally {\n profiler.end({ status: statusCode, ...(profileMeta ?? {}) })\n }\n}\nconst personDetailQuerySchema = z.object({\n include: z\n .string()\n .optional()\n .describe('Comma-separated list of relations to include (addresses, comments, activities, interactions, deals, todos).'),\n}).passthrough()\n\nconst personDetailResponseSchema = z.object({\n interactionMode: z.enum(['canonical', 'legacy']),\n person: z.object({\n id: z.string().uuid(),\n displayName: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n primaryEmail: z.string().nullable().optional(),\n primaryPhone: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n lifecycleStage: z.string().nullable().optional(),\n source: z.string().nullable().optional(),\n nextInteractionAt: z.string().nullable().optional(),\n nextInteractionName: z.string().nullable().optional(),\n nextInteractionRefId: z.string().nullable().optional(),\n nextInteractionIcon: z.string().nullable().optional(),\n nextInteractionColor: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n isActive: z.boolean().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n profile: z\n .object({\n id: z.string().uuid(),\n firstName: z.string().nullable().optional(),\n lastName: z.string().nullable().optional(),\n preferredName: z.string().nullable().optional(),\n jobTitle: z.string().nullable().optional(),\n department: z.string().nullable().optional(),\n seniority: z.string().nullable().optional(),\n timezone: z.string().nullable().optional(),\n linkedInUrl: z.string().nullable().optional(),\n twitterUrl: z.string().nullable().optional(),\n companyEntityId: z.string().uuid().nullable().optional(),\n updatedAt: z.string(),\n })\n .nullable(),\n customFields: z.record(z.string(), z.unknown()),\n tags: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n color: z.string().nullable().optional(),\n }),\n ),\n addresses: z.array(\n z.object({\n id: z.string().uuid(),\n name: z.string().nullable().optional(),\n purpose: z.string().nullable().optional(),\n addressLine1: z.string().nullable().optional(),\n addressLine2: z.string().nullable().optional(),\n buildingNumber: z.string().nullable().optional(),\n flatNumber: z.string().nullable().optional(),\n city: z.string().nullable().optional(),\n region: z.string().nullable().optional(),\n postalCode: z.string().nullable().optional(),\n country: z.string().nullable().optional(),\n latitude: z.number().nullable().optional(),\n longitude: z.number().nullable().optional(),\n isPrimary: z.boolean().nullable().optional(),\n createdAt: z.string(),\n }),\n ),\n comments: z.array(\n z.object({\n id: z.string().uuid(),\n body: z.string().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n }),\n ),\n activities: z.array(\n z.object({\n id: z.string().uuid(),\n activityType: z.string(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n }),\n ),\n interactions: z.array(\n z.object({\n id: z.string().uuid(),\n entityId: z.string().uuid().nullable().optional(),\n interactionType: z.string(),\n title: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n status: z.string(),\n scheduledAt: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n priority: z.number().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealTitle: z.string().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n source: z.string().nullable().optional(),\n _integrations: z.record(z.string(), z.unknown()).optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n ),\n deals: z.array(\n z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n valueAmount: z.number().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.string().nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n ),\n todos: z.array(\n z.object({\n id: z.string().uuid(),\n todoId: z.string().uuid(),\n todoSource: z.string(),\n createdAt: z.string(),\n createdByUserId: z.string().uuid().nullable().optional(),\n title: z.string().nullable().optional(),\n isDone: z.boolean().nullable().optional(),\n priority: z.number().nullable().optional(),\n severity: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n dueAt: z.string().nullable().optional(),\n todoOrganizationId: z.string().uuid().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n }),\n ),\n isPrimary: z.boolean(),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n ),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n})\n\nconst personDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch person detail',\n methods: {\n GET: {\n summary: 'Fetch person with related data',\n description: 'Returns a person customer record with optional related resources such as addresses, comments, activities, interactions, deals, and todos.',\n query: personDetailQuerySchema,\n responses: [\n { status: 200, description: 'Person detail payload', schema: personDetailResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: personDetailErrorSchema },\n { status: 401, description: 'Unauthorized', schema: personDetailErrorSchema },\n { status: 403, description: 'Forbidden for tenant/organization scope', schema: personDetailErrorSchema },\n { status: 404, description: 'Person not found', schema: personDetailErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY;AACrB,SAAS,6BAA6B;AACtC,SAAS,SAAS;AAClB,SAAS,8BAA8B,uCAAuC;AAC9E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8CAA8C;AACvD,SAAS,oCAAoC;AAI7C,SAAS,oBAAoB,6BAA6B;AAC1D,SAAS,yBAAyB,yBAAyB;AAC3D,SAAS,uCAAuC;AAChD,SAAS,wBAAwB,gCAAgC;AACjE,SAAS,2CAA2C;AACpD,SAAS,uCAAuC;AAChD,SAAS,wCAAwC;AAE1C,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,uBAAuB,EAAE;AACvE;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAED,SAAS,mBAAmB,SAA+B;AACzD,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,MAAM,IAAI,aAAa,OAAO,SAAS;AAC7C,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,QAAQ,CAAC,UAAU;AACrB,QAAI,CAAC,MAAO;AACZ,UACG,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,YAAY,CAAC,EACvC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,QAAQ,CAAC,SAAS,OAAO,IAAI,IAAI,CAAC;AAAA,EACvC,CAAC;AACD,SAAO;AACT;AAEA,SAAS,UAAU,SAAiB;AAClC,SAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9D;AAEA,SAAS,SAAS,SAAiB;AACjC,SAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9D;AAEA,SAAS,cAAc,aAAkG;AACvH,SAAO,YACJ,IAAI,CAAC,eAAe;AACnB,UAAM,MAAM,WAAW;AACvB,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,OAAO,IAAI,SAAS;AAAA,IACtB;AAAA,EACF,CAAC,EACA;AAAA,IACC,CAAC,QAII,QAAQ;AAAA,EACf;AACJ;AAaA,SAAS,iBAAiB,QAAgD;AACxE,QAAM,aAAa,CAAC,SAAS,WAAW,QAAQ,WAAW,QAAQ,aAAa;AAChF,aAAW,OAAO,YAAY;AAC5B,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAA+B;AAClD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,SAAS,OAAO,OAAO;AAC7B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAA+B;AACrD,MAAI,iBAAiB,MAAM;AACzB,UAAM,KAAK,MAAM,QAAQ;AACzB,WAAO,OAAO,MAAM,EAAE,IAAI,OAAO,MAAM,YAAY;AAAA,EACrD;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,YAAY,IAAI,KAAK,OAAO;AAClC,QAAI,CAAC,OAAO,MAAM,UAAU,QAAQ,CAAC,EAAG,QAAO,UAAU,YAAY;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,QAAiC,KAAsB;AAC9E,QAAM,SAAS,OAAO,UAAU,OAAO,gBAAgB,OAAO;AAC9D,MAAI,UAAU,OAAO,WAAW,UAAU;AACxC,UAAM,SAAS;AACf,QAAI,OAAO,OAAQ,QAAO,OAAO,GAAG;AAAA,EACtC;AACA,SAAO;AACT;AAKA,SAAS,wBAAwB,OAA4C;AAC3E,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACvC;AAEA,SAAS,gBAAgB,OAAe,QAA2B;AACjE,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAM,QAAQ,MAAM,YAAY;AAChC,SAAO,OAAO,KAAK,CAAC,UAAU;AAC5B,QAAI,UAAU,OAAO,UAAU,SAAS,kBAAkB,KAAK,MAAM,KAAM,QAAO;AAClF,QAAI,MAAM,SAAS,GAAG,GAAG;AACvB,YAAM,SAAS,MAAM,MAAM,GAAG,EAAE;AAChC,aAAO,OAAO,WAAW,IAAI,OAAO,MAAM,WAAW,MAAM;AAAA,IAC7D;AACA,WAAO,UAAU;AAAA,EACnB,CAAC;AACH;AAEA,SAAS,wBAAwB,OAAwB;AACvD,QAAM,aAAa;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,EACd;AACA,aAAW,OAAO,YAAY;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,SAAS,wBAAwB,GAAG;AAC1C,QAAI,gBAAgB,OAAO,MAAM,EAAG,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAA8B;AACzD,MAAI,CAAC,wBAAwB,KAAK,GAAG;AACnC,WAAO,EAAE,SAAS,OAAO,MAAM,MAAM;AAAA,IAAC,GAAG,KAAK,MAAM;AAAA,IAAC,EAAE;AAAA,EACzD;AACA,QAAM,QAA6B,CAAC,EAAE,OAAO,SAAS,MAAM,QAAQ,OAAO,OAAO,EAAE,CAAC;AACrF,QAAM,QAAQ,CAAC,UAAkB,KAAK,MAAM,QAAQ,GAAI,IAAI;AAC5D,SAAO;AAAA,IACL,SAAS;AAAA,IACT,KAAK,OAAO,OAAO;AACjB,YAAM,KAAK,EAAE,OAAO,MAAM,QAAQ,OAAO,OAAO,GAAG,MAAM,CAAC;AAAA,IAC5D;AAAA,IACA,IAAI,OAAO;AACT,YAAM,KAAK,EAAE,OAAO,OAAO,MAAM,QAAQ,OAAO,OAAO,GAAG,MAAM,CAAC;AACjE,YAAM,WAAyF,CAAC;AAChG,eAAS,MAAM,GAAG,MAAM,MAAM,QAAQ,OAAO,GAAG;AAC9C,cAAM,UAAU,MAAM,GAAG;AACzB,cAAM,WAAW,MAAM,MAAM,CAAC;AAC9B,YAAI,QAAQ,UAAU,MAAO;AAC7B,cAAM,aAAa,OAAO,QAAQ,OAAO,SAAS,IAAI,IAAI;AAC1D,iBAAS,KAAK;AAAA,UACZ,MAAM,QAAQ;AAAA,UACd,YAAY,MAAM,UAAU;AAAA,UAC5B,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,QAClD,CAAC;AAAA,MACH;AACA,YAAM,UAAU,MAAM,MAAM,SAAS,CAAC,EAAE,OAAO,MAAM,CAAC,EAAE;AACxD,YAAM,UAAmC;AAAA,QACvC;AAAA,QACA,SAAS,MAAM,OAAO,OAAO,IAAI,GAAS;AAAA,QAC1C,OAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,UAAI,KAAK,SAAS,OAAO,KAAK,KAAK,KAAK,EAAE,SAAS,EAAG,SAAQ,OAAO,KAAK;AAAA,eACjE,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,EAAG,SAAQ,OAAO;AAChE,UAAI;AACF,gBAAQ,KAAK,mBAAmB,OAAO;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,mBACb,aACA,OACA,UACA,iBACA,UACkC;AAClC,QAAM,UAAU,oBAAI,IAAwB;AAC5C,MAAI,CAAC,MAAM,UAAU,CAAC,SAAU,QAAO;AAEvC,QAAM,eAAe,gBAClB,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC;AAE1F,QAAM,cAAc,oBAAI,IAAyB;AACjD,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,OAAO,KAAK,eAAe,YAAY,KAAK,WAAW,KAAK,EAAE,SAAS,IAAI,KAAK,aAAa;AAC5G,UAAM,KAAK,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,IAAI,KAAK,SAAS,OAAO,KAAK,UAAU,EAAE;AACpH,QAAI,CAAC,GAAI;AACT,QAAI,CAAC,YAAY,IAAI,MAAM,EAAG,aAAY,IAAI,QAAQ,oBAAI,IAAY,CAAC;AACvE,gBAAY,IAAI,MAAM,EAAG,IAAI,EAAE;AAAA,EACjC;AAEA,YAAU,KAAK,sBAAsB,EAAE,aAAa,YAAY,MAAM,WAAW,MAAM,OAAO,CAAC;AAE/F,aAAW,CAAC,QAAQ,KAAK,KAAK,YAAY,QAAQ,GAAG;AACnD,UAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,QAAI,CAAC,IAAI,OAAQ;AACjB,cAAU,KAAK,oBAAoB,EAAE,QAAQ,OAAO,IAAI,OAAO,CAAC;AAChE,QAAI;AACF,YAAM,SAAS,MAAM,YAAY,MAA+B,QAAoB;AAAA,QAClF;AAAA,QACA,iBAAiB,aAAa,SAAS,IAAI,eAAe;AAAA,QAC1D,SAAS,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;AAAA,QAC5B,qBAAqB,CAAC,YAAY,UAAU,YAAY,aAAa;AAAA,QACrE,MAAM,EAAE,MAAM,GAAG,UAAU,KAAK,IAAI,IAAI,QAAQ,CAAC,EAAE;AAAA,MACrD,CAAC;AACD,YAAM,YAAY,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,MAAM,SAAS;AACtE,gBAAU,KAAK,uBAAuB,EAAE,QAAQ,UAAU,CAAC;AAC3D,UAAI,WAAW;AACf,iBAAW,QAAQ,OAAO,SAAS,CAAC,GAAG;AACrC,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,cAAM,SAAS;AACf,cAAM,QAAQ,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,KAAK,EAAE,SAAS,IAAI,OAAO,KAAK,OAAO,OAAO,MAAM,EAAE;AAC/G,YAAI,CAAC,MAAO;AACZ,cAAM,QAAQ,iBAAiB,MAAM;AACrC,cAAM,UAAU,MAAM;AACpB,gBAAM,SAAS,wBAAwB,OAAO,OAAO;AACrD,cAAI,WAAW,KAAM,QAAO;AAC5B,gBAAM,SAAS,wBAAwB,gBAAgB,QAAQ,SAAS,CAAC;AACzE,cAAI,WAAW,KAAM,QAAO;AAC5B,gBAAM,UAAU,wBAAwB,OAAO,MAAM;AACrD,cAAI,YAAY,KAAM,QAAO;AAC7B,iBAAO,wBAAwB,gBAAgB,QAAQ,QAAQ,CAAC;AAAA,QAClE,GAAG;AACH,cAAM,YAAY,MAAM;AACtB,gBAAM,aAAa;AAAA,YACjB,OAAO,aAAa;AAAA,YACpB,OAAO,aAAa;AAAA,YACpB,OAAO;AAAA,YACP,gBAAgB,QAAQ,UAAU;AAAA,UACpC;AACA,qBAAW,aAAa,YAAY;AAClC,kBAAM,SAAS,YAAY,SAAS;AACpC,gBAAI,WAAW,KAAM,QAAO;AAAA,UAC9B;AACA,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,YAAY,MAAM;AACtB,gBAAM,aAAa;AAAA,YACjB,OAAO,aAAa;AAAA,YACpB,OAAO,aAAa;AAAA,YACpB,gBAAgB,QAAQ,UAAU;AAAA,UACpC;AACA,qBAAW,aAAa,YAAY;AAClC,gBAAI,OAAO,cAAc,UAAU;AACjC,oBAAM,UAAU,UAAU,KAAK;AAC/B,kBAAI,QAAQ,OAAQ,QAAO;AAAA,YAC7B;AAAA,UACF;AACA,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,oBAAoB,MAAM;AAC9B,gBAAM,aAAa;AAAA,YACjB,OAAO;AAAA,YACP,OAAO,gBAAgB;AAAA,YACvB,OAAO,gBAAgB;AAAA,YACvB,gBAAgB,QAAQ,aAAa;AAAA,UACvC;AACA,qBAAW,aAAa,YAAY;AAClC,gBAAI,OAAO,cAAc,UAAU;AACjC,oBAAM,UAAU,UAAU,KAAK;AAC/B,kBAAI,QAAQ,OAAQ,QAAO;AAAA,YAC7B;AAAA,UACF;AACA,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,SAAS,MAAM;AACnB,gBAAM,aAAa;AAAA,YACjB,OAAO;AAAA,YACP,OAAO;AAAA,YACP,OAAO,WAAW;AAAA,YAClB,OAAO,WAAW;AAAA,YAClB,gBAAgB,QAAQ,QAAQ;AAAA,YAChC,gBAAgB,QAAQ,OAAO;AAAA,UACjC;AACA,qBAAW,aAAa,YAAY;AAClC,kBAAM,SAAS,eAAe,SAAS;AACvC,gBAAI,OAAQ,QAAO;AAAA,UACrB;AACA,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,iBAAiB,OAAO,OAAO,oBAAoB,YAAY,OAAO,gBAAgB,KAAK,EAAE,SAAS,IACxG,OAAO,kBACN,OAAO,OAAO,mBAAmB,YAAY,OAAO,eAAe,KAAK,EAAE,SAAS,IAAI,OAAO,iBAAiB;AACpH,cAAM,eAAwC,CAAC;AAC/C,cAAM,oBAAoB,CAAC,KAAc,UAAmB;AAC1D,cAAI,OAAO,QAAQ,SAAU;AAC7B,gBAAM,aAAa,IAAI,KAAK;AAC5B,cAAI,CAAC,WAAW,OAAQ;AACxB,uBAAa,UAAU,IAAI,UAAU,SAAY,OAAO;AAAA,QAC1D;AACA,mBAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,cAAI,OAAO,WAAW,KAAK,GAAG;AAC5B,8BAAkB,OAAO,MAAM,CAAC,GAAG,QAAQ;AAAA,UAC7C,WAAW,OAAO,WAAW,KAAK,GAAG;AACnC,8BAAkB,OAAO,MAAM,CAAC,GAAG,QAAQ;AAAA,UAC7C;AAAA,QACF;AACA,cAAM,eAAe,OAAO,UAAU,OAAO,gBAAgB,OAAO;AACpE,YAAI,gBAAgB,OAAO,iBAAiB,UAAU;AACpD,qBAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,YAAuC,GAAG;AAC9F,8BAAkB,WAAW,WAAW;AAAA,UAC1C;AAAA,QACF;AAEA,gBAAQ,IAAI,GAAG,MAAM,IAAI,KAAK,IAAI;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb;AAAA,UACA;AAAA,UACA,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,eAAe;AAAA,QAClE,CAAC;AACD,oBAAY;AAAA,MACd;AACA,gBAAU,KAAK,wBAAwB,EAAE,QAAQ,SAAS,CAAC;AAAA,IAC7D,SAAS,KAAK;AACZ,gBAAU,KAAK,qBAAqB,EAAE,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AACvG,cAAQ,KAAK,+DAA+D,MAAM,IAAI,GAAG;AAAA,IAC3F;AAAA,EACF;AAEA,YAAU,KAAK,sBAAsB,EAAE,eAAe,QAAQ,KAAK,CAAC;AACpE,SAAO;AACT;AAEA,eAAsB,IAAI,MAAe,KAAmC;AAC1E,QAAM,WAAW,oBAAoB,yBAAyB;AAC9D,WAAS,KAAK,kBAAkB;AAEhC,QAAM,gBAAgB,mBAAmB,IAAI;AAC7C,WAAS,KAAK,mBAAmB,EAAE,SAAS,MAAM,KAAK,aAAa,EAAE,CAAC;AAEvE,QAAM,oBAAoB,cAAc,IAAI,YAAY;AACxD,QAAM,mBAAmB,cAAc,IAAI,WAAW;AACtD,QAAM,kBAAkB,cAAc,IAAI,UAAU,KAAK,cAAc,IAAI,OAAO;AAClF,QAAM,eAAe,cAAc,IAAI,OAAO;AAC9C,QAAM,sBAAsB,cAAc,IAAI,cAAc;AAC5D,QAAM,eAAe,cAAc,IAAI,OAAO,KAAK,cAAc,IAAI,OAAO;AAC5E,QAAM,sBAAsB;AAE5B,MAAI,aAAa;AACjB,MAAI;AACJ,MAAI,YAA+B,CAAC;AACpC,MAAI,WAA8B,CAAC;AACnC,MAAI,aAAiC,CAAC;AACtC,MAAI,YAAgC,CAAC;AACrC,MAAI,cAAc,oBAAI,IAAwB;AAC9C,MAAI,QAAwB,CAAC;AAC7B,MAAI,iBAA0C,CAAC;AAC/C,MAAI,UAAU,oBAAI,IAA2D;AAC7E,MAAI,eAAwC,CAAC;AAC7C,MAAI,eAA8B;AAClC,MAAI,UAAwC;AAC5C,MAAI,YAA+H,CAAC;AAEpI,MAAI;AACF,UAAM,QAAQ,aAAa,UAAU,EAAE,IAAI,IAAI,QAAQ,GAAG,CAAC;AAC3D,QAAI,CAAC,MAAM,SAAS;AAClB,mBAAa;AACb,oBAAc,EAAE,QAAQ,oBAAoB;AAC5C,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AACA,aAAS,KAAK,mBAAmB,EAAE,IAAI,MAAM,KAAK,GAAG,CAAC;AAEtD,UAAM,OAAO,MAAM,mBAAmB,IAAI;AAC1C,QAAI,CAAC,MAAM;AACT,eAAS,KAAK,cAAc;AAC5B,mBAAa;AACb,oBAAc,EAAE,QAAQ,eAAe;AACvC,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,aAAS,KAAK,iBAAiB;AAAA,MAC7B,UAAU,KAAK,YAAY;AAAA,MAC3B,gBAAgB,KAAK,SAAS;AAAA,MAC9B,QAAQ,KAAK,OAAO;AAAA,MACpB,UAAU,CAAC,CAAC,KAAK;AAAA,IACnB,CAAC;AACD,mBAAe,KAAK,WAAW,OAAO,KAAK,OAAO;AAElD,UAAM,YAAY,MAAM,uBAAuB;AAC/C,aAAS,KAAK,oBAAoB;AAElC,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AACzF,UAAM,mBAAmB,MAAM,uCAAuC,WAAW,OAAO,YAAY,KAAK,QAAQ;AACjH,UAAM,kBAAkB,iBAAiB,UAAU,cAAc;AACjE,aAAS,KAAK,kBAAkB;AAAA,MAC9B,qBAAqB,MAAM,QAAQ,OAAO,SAAS,IAAI,MAAM,UAAU,SAAS;AAAA,IAClF,CAAC;AACD,UAAM,KAAM,UAAU,QAAQ,IAAI;AAElC,UAAM,cAAc,iCAAiC,MAAM,KAAK,IAAI,UAAU,IAAI;AAClF,QAAI,CAAC,YAAY,SAAS;AACxB,mBAAa;AACb,oBAAc,EAAE,QAAQ,yBAAyB;AACjD,aAAO,SAAS,kBAAkB;AAAA,IACpC;AAEA,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,CAAC;AAAA,MACD;AAAA,QACE,UAAU,OAAO,YAAY,KAAK,YAAY;AAAA,QAC9C,gBAAgB,OAAO,cAAc,KAAK,SAAS;AAAA,MACrD;AAAA,IACF;AACA,aAAS,KAAK,iBAAiB,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC;AAClD,QAAI,CAAC,QAAQ;AACX,mBAAa;AACb,oBAAc,EAAE,QAAQ,mBAAmB;AAC3C,aAAO,SAAS,kBAAkB;AAAA,IACpC;AAEA,QAAI,CAAC,gCAAgC,EAAE,OAAO,MAAM,gBAAgB,OAAO,eAAe,CAAC,GAAG;AAC5F,mBAAa;AACb,oBAAc,EAAE,QAAQ,yBAAyB;AACjD,aAAO,UAAU,eAAe;AAAA,IAClC;AAEA,UAAM,CAAC,eAAe,kBAAkB,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC5D,sBAAsB,IAAI,uBAAuB,EAAE,QAAQ,OAAO,GAAG,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK,CAAC;AAAA,MACnN,uBAAuB,IAAI,MAAM;AAAA,IACnC,CAAC;AACD,cAAU;AACV,aAAS,KAAK,kBAAkB,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;AACpD,gBAAY,yBAAyB,SAAS,kBAAkB;AAQhE,UAAM,wBAAwB,gCAAgC;AAAA,MAC5D,eAAe;AAAA,MACf,cAAc;AAAA,IAChB,CAAC;AAED,UAAM,cAAc,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK;AACtI,UAAM,kCAAkC,uBAAuB,qBAAqB;AAYpF,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI,MAAM,QAAQ,IAAI;AAAA,MACpB,mBACI,mBAAmB,IAAI,iBAAiB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,WAAW,QAAQ,WAAW,OAAO,EAAE,GAAG,WAAW,IACjI,QAAQ,QAA2B,CAAC,CAAC;AAAA,MACzC,mBAAmB,IAAI,uBAAuB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,UAAU,CAAC,KAAK,EAAE,GAAG,WAAW;AAAA,MACvG,mBAAmB,IAAI,yBAAyB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,WAAW;AAAA,MAC3G,kBACI,mBAAmB,IAAI,iBAAiB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,WAAW,OAAO,GAAG,OAAO,GAAG,GAAG,WAAW,IACzH,QAAQ,QAA2B,CAAC,CAAC;AAAA,MACzC,kCACI;AAAA,QACE;AAAA,QACA;AAAA,QACA,iBAAiB,UACb,EAAE,QAAQ,OAAO,IAAI,WAAW,MAAM,GAAG,sBAAsB,IAC/D,EAAE,QAAQ,OAAO,IAAI,GAAG,sBAAsB;AAAA,QAClD,EAAE,SAAS,EAAE,aAAa,OAAO,WAAW,OAAO,GAAG,OAAO,IAAI;AAAA,QACjE;AAAA,MACF,IACA,QAAQ,QAA+B,CAAC,CAAC;AAAA,IAC/C,CAAC;AACD,gBAAY;AACZ,qBAAiB;AACjB,eAAW;AACX,aAAS,KAAK,oBAAoB,EAAE,OAAO,UAAU,OAAO,CAAC;AAC7D,aAAS,KAAK,eAAe,EAAE,OAAO,eAAe,OAAO,CAAC;AAC7D,aAAS,KAAK,iBAAiB,EAAE,OAAO,iBAAiB,OAAO,CAAC;AACjE,aAAS,KAAK,mBAAmB,EAAE,OAAO,SAAS,OAAO,CAAC;AAC3D,aAAS,KAAK,iCAAiC,EAAE,OAAO,yBAAyB,OAAO,CAAC;AACzF,UAAM,8BAA8B,yBAAyB,OAAO,CAAC,gBAAgB,CAAC,YAAY,SAAS;AAC3G,UAAM,wBAAwB,kCAC1B,MAAM,6BAA6B;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA,wBAAwB,OAAO,cAAc,KAAK,SAAS;AAAA,MAC3D,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC,IACD,CAAC;AACL,aAAS,KAAK,mCAAmC,EAAE,OAAO,sBAAsB,OAAO,CAAC;AAExF,UAAM,6BAA6B,mCAC9B,MAAM;AACL,YAAM,SAAS,CAAC,GAAG,qBAAqB,EACrC,OAAO,CAAC,gBAAgB,YAAY,WAAW,aAAa,YAAY,oBAAoB,MAAM,EAClG,KAAK,CAAC,MAAM,UAAU;AACrB,cAAM,WAAW,IAAI,KAAK,KAAK,eAAe,KAAK,SAAS,EAAE,QAAQ;AACtE,cAAM,YAAY,IAAI,KAAK,MAAM,eAAe,MAAM,SAAS,EAAE,QAAQ;AACzE,YAAI,aAAa,UAAW,QAAO,KAAK,GAAG,cAAc,MAAM,EAAE;AACjE,eAAO,WAAW;AAAA,MACpB,CAAC;AACH,aAAO,OAAO,MAAM,GAAG,mBAAmB;AAAA,IAC5C,GAAG,IACH,MAAM,6BAA6B;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA,wBAAwB,OAAO,cAAc,KAAK,SAAS;AAAA,MAC3D,cAAc,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,UACE,QAAQ,OAAO;AAAA,UACf,gBAAgB,OAAO;AAAA,UACvB,UAAU,OAAO;AAAA,UACjB,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,iBAAiB,EAAE,KAAK,OAAO;AAAA,UAC/B,GAAG;AAAA,QACL;AAAA,QACA,EAAE,SAAS,EAAE,aAAa,OAAO,WAAW,MAAM,GAAG,OAAO,oBAAoB;AAAA,QAChF,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK;AAAA,MACpH;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAEL,QAAI,qBAAqB,CAAC,iBAAiB,SAAS;AAClD,mBAAa,MAAM,mBAAmB,IAAI,kBAAkB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,YAAY,QAAQ,WAAW,OAAO,GAAG,OAAO,GAAG,GAAG,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK,CAAC;AACzQ,eAAS,KAAK,qBAAqB,EAAE,OAAO,WAAW,OAAO,CAAC;AAAA,IACjE;AAEA,QAAI,qBAAqB;AACvB,eAAS,KAAK,uBAAuB,EAAE,OAAO,sBAAsB,OAAO,CAAC;AAAA,IAC9E;AAEA,QAAI,gBAAgB,CAAC,iBAAiB,SAAS;AAC7C,kBAAY,MAAM,mBAAmB,IAAI,kBAAkB,EAAE,QAAQ,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,WAAW,OAAO,GAAG,OAAO,GAAG,GAAG,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK,CAAC;AACpP,eAAS,KAAK,qBAAqB,EAAE,OAAO,UAAU,OAAO,CAAC;AAC9D,UAAI,UAAU,QAAQ;AACpB,cAAM,cAAe,UAAU,QAAQ,aAAa;AACpD,YAAI;AACF,wBAAc,MAAM;AAAA,YAClB;AAAA,YACA;AAAA,YACA,OAAO,YAAY,KAAK,YAAY;AAAA,YACpC,CAAC,OAAO,kBAAkB,MAAM,GAAI,OAAO,aAAa,CAAC,CAAE;AAAA,YAC3D;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,kBAAQ,KAAK,wDAAwD,GAAG;AAAA,QAC1E;AACA,iBAAS,KAAK,yBAAyB,EAAE,OAAO,YAAY,MAAM,OAAO,UAAU,OAAO,CAAC;AAAA,MAC7F;AAAA,IACF;AAEA,UAAM,6BAA6B,IAAI;AAAA,MACrC,yBACG,OAAO,CAAC,gBAAgB,YAAY,WAAW,4CAA4C,EAC3F,IAAI,CAAC,gBAAgB,YAAY,EAAE;AAAA,IACxC;AACA,UAAM,yBAAyB,IAAI;AAAA,MACjC,yBACG,OAAO,CAAC,gBAAgB,YAAY,WAAW,wCAAwC,EACvF,IAAI,CAAC,gBAAgB,YAAY,EAAE;AAAA,IACxC;AACA,UAAM,yBAAyB,sBAC5B,OAAO,CAAC,gBAAgB,YAAY,oBAAoB,MAAM,EAC9D,IAAI,CAAC,gBAAgB,sCAAsC,WAAW,CAAC;AAC1E,UAAM,qBAAqB,sBACxB,OAAO,CAAC,gBAAgB,YAAY,oBAAoB,MAAM,EAC9D,IAAI,CAAC,gBAAgB,kCAAkC,WAAW,CAAC;AAEtE,UAAM,YAAY,oBAAI,IAAY;AAClC,QAAI,mBAAmB;AACrB,iBAAW,YAAY,YAAY;AACjC,YAAI,SAAS,aAAc,WAAU,IAAI,SAAS,YAAY;AAAA,MAChE;AAAA,IACF;AACA,QAAI,iBAAiB;AACnB,iBAAW,WAAW,UAAU;AAC9B,YAAI,QAAQ,aAAc,WAAU,IAAI,QAAQ,YAAY;AAAA,MAC9D;AAAA,IACF;AACA,QAAI,aAAc,WAAU,IAAI,YAAY;AAE5C,QAAI,UAAU,MAAM;AAClB,YAAM,QAAQ,MAAM,mBAAmB,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,MAAM,KAAK,SAAS,EAAE,EAAE,GAAG,CAAC,GAAG,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK,CAAC;AAChN,gBAAU,IAAI;AAAA,QACZ,MAAM,IAAI,CAAC,SAAS;AAAA,UAClB,KAAK;AAAA,UACL;AAAA,YACE,MAAM,KAAK,QAAQ;AAAA,YACnB,OAAO,KAAK,SAAS;AAAA,UACvB;AAAA,QACF,CAAC;AAAA,MACH;AACA,eAAS,KAAK,kBAAkB,EAAE,OAAO,QAAQ,KAAK,CAAC;AAAA,IACzD;AAEA,QAAI,cAAc;AAChB,YAAM,YAAY,MAAM;AAAA,QACtB;AAAA,QACA;AAAA,QACA,EAAE,QAAQ,OAAO,GAAG;AAAA,QACpB,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,QACrB,EAAE,UAAU,OAAO,YAAY,KAAK,YAAY,MAAM,gBAAgB,OAAO,kBAAkB,KAAK,SAAS,KAAK;AAAA,MACpH;AACA,cAAQ,UACL,IAAI,CAAC,SAAU,KAAK,QAAyC,IAAI,EACjE,OAAO,CAAC,SAA+B,CAAC,CAAC,QAAQ,OAAO,SAAS,QAAQ;AAC5E,eAAS,KAAK,gBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;AAAA,IACvD;AAKA,UAAM,YAAY,SAAS,MAAM;AACjC,UAAM,CAAC,yBAAyB,0BAA0B,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACrF,sBAAsB;AAAA,QACpB;AAAA,QACA,UAAU,EAAE,UAAU;AAAA,QACtB,WAAW,CAAC,OAAO,EAAE;AAAA,QACrB,kBAAkB,EAAE,CAAC,OAAO,EAAE,GAAG,OAAO,YAAY,KAAK;AAAA,QACzD,wBAAwB,EAAE,CAAC,OAAO,EAAE,GAAG,OAAO,kBAAkB,KAAK;AAAA,QACrE,iBAAiB;AAAA,UACf,OAAO,YAAY,KAAK,YAAY;AAAA,QACtC,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAClC,CAAC;AAAA,MACD,YACI,sBAAsB;AAAA,QACpB;AAAA,QACA,UAAU,EAAE,UAAU;AAAA,QACtB,WAAW,CAAC,SAAS;AAAA,QACrB,kBAAkB,EAAE,CAAC,SAAS,GAAG,SAAS,YAAY,KAAK;AAAA,QAC3D,wBAAwB,EAAE,CAAC,SAAS,GAAG,SAAS,kBAAkB,KAAK;AAAA,QACvE,iBAAiB;AAAA,UACf,SAAS,YAAY,OAAO,YAAY,KAAK,YAAY;AAAA,QAC3D,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAClC,CAAC,IACD,QAAQ,QAAiD,CAAC,CAAC;AAAA,MAC/D,gCAAgC,IAAI,OAAO,YAAY,MAAM,OAAO,kBAAkB,IAAI;AAAA,IAC5F,CAAC;AACD,aAAS,KAAK,+BAA+B,EAAE,MAAM,OAAO,KAAK,0BAA0B,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC;AACrH,QAAI,WAAW;AACb,eAAS,KAAK,gCAAgC,EAAE,MAAM,OAAO,KAAK,2BAA2B,SAAS,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC;AAAA,IACzH;AACA,aAAS,KAAK,iCAAiC,EAAE,MAAM,QAAQ,KAAK,CAAC;AACrE,mBAAe;AAAA,MACb;AAAA,QACE;AAAA,QACA,0BAA0B,OAAO,EAAE,KAAK,CAAC;AAAA,QACzC,YAAY,2BAA2B,SAAS,KAAK,CAAC,IAAI,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,aAAS,KAAK,wBAAwB,EAAE,MAAM,OAAO,KAAK,YAAY,EAAE,OAAO,CAAC;AAEhF,UAAM,oBAAoB;AAI1B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI,MAAM,QAAQ,IAAI;AAAA,MACpB,kBACI,QAAQ,QAAQ,SAAS,MAAM,IAC/B,GAAG,MAAM,iBAAiB;AAAA,QACxB,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,MACL,GAAG,MAAM,qBAAqB;AAAA,QAC5B,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,WAAW;AAAA,QACX,iBAAiB,EAAE,KAAK,OAAO;AAAA,QAC/B,GAAG;AAAA,MACL,CAAC;AAAA,MACD,GAAG,MAAM,qBAAqB;AAAA,QAC5B,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,WAAW;AAAA,QACX,GAAG;AAAA,MACL,CAAC;AAAA,MACD,iBAAiB,UACb,GAAG,MAAM,qBAAqB;AAAA,QAC5B,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,WAAW;AAAA,QACX,iBAAiB;AAAA,MACnB,CAAC,IACD,GAAG,MAAM,kBAAkB;AAAA,QACzB,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,MACL,mBACI,QAAQ,QAAQ,UAAU,MAAM,IAChC,GAAG,MAAM,iBAAiB;AAAA,QACxB,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,MACL,eACI,QAAQ,QAAQ,MAAM,MAAM,IAC5B,GAAG,MAAM,wBAAwB;AAAA,QAC/B,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACP,CAAC;AACD,UAAM,SAAS;AAAA,MACb,MAAM,eAAe,SAAS,iBAAiB;AAAA,MAC/C,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,OAAO;AAAA,MACP,WAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW,UAAU;AAAA,IACvB;AAEA,UAAM,WAAW,aAAa,KAAK;AAAA,MACjC;AAAA,MACA,QAAQ;AAAA,QACN,IAAI,OAAO;AAAA,QACX,aAAa,OAAO;AAAA,QACpB,aAAa,OAAO;AAAA,QACpB,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,QACrB,cAAc,OAAO;AAAA,QACrB,QAAQ,OAAO;AAAA,QACf,gBAAgB,OAAO;AAAA,QACvB,QAAQ,OAAO;AAAA,QACf,aAAa,OAAO;AAAA,QACpB,gBAAgB,OAAO;AAAA,QACvB,mBAAmB,OAAO,oBAAoB,OAAO,kBAAkB,YAAY,IAAI;AAAA,QACvF,qBAAqB,OAAO;AAAA,QAC5B,sBAAsB,OAAO;AAAA,QAC7B,qBAAqB,OAAO;AAAA,QAC5B,sBAAsB,OAAO;AAAA,QAC7B,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,WAAW,OAAO,UAAU,YAAY;AAAA,QACxC,WAAW,OAAO,UAAU,YAAY;AAAA,MAC1C;AAAA,MACA,SAAS,UACL;AAAA,QACE,IAAI,QAAQ;AAAA,QACZ,WAAW,QAAQ;AAAA,QACnB,UAAU,QAAQ;AAAA,QAClB,eAAe,QAAQ;AAAA,QACvB,UAAU,QAAQ;AAAA,QAClB,YAAY,QAAQ;AAAA,QACpB,WAAW,QAAQ;AAAA,QACnB,UAAU,QAAQ;AAAA,QAClB,aAAa,QAAQ;AAAA,QACrB,YAAY,QAAQ;AAAA,QACpB,iBAAiB,QAAQ,UAAW,OAAO,QAAQ,YAAY,WAAW,QAAQ,UAAU,QAAQ,QAAQ,KAAM;AAAA,QAClH,WAAW,QAAQ,UAAU,YAAY;AAAA,MAC3C,IACA;AAAA,MACJ;AAAA,MACA,MAAM;AAAA,QACJ,GAAG,cAAc,cAAc;AAAA,QAC/B,GAAG,iBACA,IAAI,CAAC,MAAM;AACV,gBAAM,QAAQ,EAAE;AAChB,cAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,iBAAO,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,OAAO,KAAK;AAAA,QACzD,CAAC,EACA,OAAO,CAAC,MAAuD,MAAM,IAAI;AAAA,MAC9E;AAAA,MACA,WAAW,mBACP,UAAU,IAAI,CAAC,aAAa;AAAA,QAC1B,IAAI,QAAQ;AAAA,QACZ,MAAM,QAAQ;AAAA,QACd,SAAS,QAAQ;AAAA,QACjB,cAAc,QAAQ;AAAA,QACtB,cAAc,QAAQ;AAAA,QACtB,gBAAgB,QAAQ;AAAA,QACxB,YAAY,QAAQ;AAAA,QACpB,MAAM,QAAQ;AAAA,QACd,QAAQ,QAAQ;AAAA,QAChB,YAAY,QAAQ;AAAA,QACpB,SAAS,QAAQ;AAAA,QACjB,UAAU,QAAQ;AAAA,QAClB,WAAW,QAAQ;AAAA,QACnB,WAAW,QAAQ;AAAA,QACnB,WAAW,QAAQ,UAAU,YAAY;AAAA,MAC3C,EAAE,IACF,CAAC;AAAA,MACL,UAAU,kBACN,SAAS,IAAI,CAAC,YAAY;AACxB,cAAM,aAAa,QAAQ,eAAe,QAAQ,IAAI,QAAQ,YAAY,IAAI;AAC9E,eAAO;AAAA,UACL,IAAI,QAAQ;AAAA,UACZ,MAAM,QAAQ;AAAA,UACd,cAAc,QAAQ;AAAA,UACtB,YAAY,YAAY,QAAQ;AAAA,UAChC,aAAa,YAAY,SAAS;AAAA,UAClC,QAAQ,QAAQ,OAAQ,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO,QAAQ,KAAK,KAAM;AAAA,UAC7F,WAAW,QAAQ,UAAU,YAAY;AAAA,UACzC,gBAAgB,QAAQ,kBAAkB;AAAA,UAC1C,iBAAiB,QAAQ,mBAAmB;AAAA,QAC9C;AAAA,MACF,CAAC,IACD,CAAC;AAAA,MACL,YAAY,oBAEN,iBAAiB,UACb,yBACA;AAAA,QACE,GAAG,WACA,OAAO,CAAC,aAAa,CAAC,2BAA2B,IAAI,SAAS,EAAE,CAAC,EACjE,IAAI,CAAC,cAAc;AAAA,UAClB,IAAI,SAAS;AAAA,UACb,cAAc,SAAS;AAAA,UACvB,SAAS,SAAS;AAAA,UAClB,MAAM,SAAS;AAAA,UACf,YAAY,SAAS,aAAa,SAAS,WAAW,YAAY,IAAI;AAAA,UACtE,QAAQ,SAAS,OAAQ,OAAO,SAAS,SAAS,WAAW,SAAS,OAAO,SAAS,KAAK,KAAM;AAAA,UACjG,cAAc,SAAS;AAAA,UACvB,YAAY,SAAS,eAAe,QAAQ,IAAI,SAAS,YAAY,GAAG,QAAQ,OAAO;AAAA,UACvF,aAAa,SAAS,eAAe,QAAQ,IAAI,SAAS,YAAY,GAAG,SAAS,OAAO;AAAA,UACzF,WAAW,SAAS,UAAU,YAAY;AAAA,UAC1C,gBAAgB,SAAS,kBAAkB;AAAA,UAC3C,iBAAiB,SAAS,mBAAmB;AAAA,QAC/C,EAAE;AAAA,QACJ,GAAG,uBAAuB;AAAA,UACxB,CAAC,aACC,sBAAsB;AAAA,YACpB,CAAC,gBACC,YAAY,OAAO,SAAS,MAC5B,YAAY,WAAW;AAAA,UAC3B;AAAA,QACJ;AAAA,MACF,EAAE,KAAK,CAAC,MAAM,UAAU;AACtB,cAAM,WAAW,IAAI,KAAK,KAAK,cAAc,KAAK,SAAS,EAAE,QAAQ;AACrE,cAAM,YAAY,IAAI,KAAK,MAAM,cAAc,MAAM,SAAS,EAAE,QAAQ;AACxE,YAAI,aAAa,UAAW,QAAO,MAAM,GAAG,cAAc,KAAK,EAAE;AACjE,eAAO,YAAY;AAAA,MACrB,CAAC,EAAE,MAAM,GAAG,EAAE,IAEpB,CAAC;AAAA,MACL,cAAc,sBACV,wBACA,CAAC;AAAA,MACL,OAAO,eACH,MAAM,IAAI,CAAC,UAAU;AAAA,QACnB,IAAI,KAAK;AAAA,QACT,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,eAAe,KAAK;AAAA,QACpB,YAAY,KAAK,cAAc;AAAA,QAC/B,iBAAiB,KAAK,mBAAmB;AAAA,QACzC,aAAa,KAAK;AAAA,QAClB,eAAe,KAAK;AAAA,QACpB,aAAa,KAAK;AAAA,QAClB,iBAAiB,KAAK,kBAAkB,KAAK,gBAAgB,YAAY,IAAI;AAAA,QAC7E,aAAa,KAAK;AAAA,QAClB,QAAQ,KAAK;AAAA,QACb,gBAAgB,KAAK,kBAAkB;AAAA,QACvC,cAAc,KAAK,gBAAgB;AAAA,QACnC,WAAW,KAAK,aAAa;AAAA,QAC7B,WAAW,KAAK,UAAU,YAAY;AAAA,QACtC,WAAW,KAAK,UAAU,YAAY;AAAA,MACxC,EAAE,IACF,CAAC;AAAA,MACL,OAAO,eAED,iBAAiB,UACb,qBACA;AAAA,QACE,GAAG,UACA,OAAO,CAAC,SAAS,CAAC,uBAAuB,IAAI,KAAK,MAAM,CAAC,EACzD,IAAI,CAAC,SAAS;AACb,gBAAM,SAAS,OAAO,KAAK,eAAe,YAAY,KAAK,WAAW,KAAK,EAAE,SAAS,IAAI,KAAK,aAAa;AAC5G,gBAAM,MAAM,GAAG,MAAM,IAAI,KAAK,MAAM;AACpC,gBAAM,SAAS,YAAY,IAAI,GAAG;AAClC,iBAAO;AAAA,YACL,IAAI,KAAK;AAAA,YACT,QAAQ,KAAK;AAAA,YACb,YAAY;AAAA,YACZ,WAAW,KAAK,UAAU,YAAY;AAAA,YACtC,iBAAiB,KAAK;AAAA,YACtB,OAAO,QAAQ,SAAS;AAAA,YACxB,QAAQ,QAAQ,UAAU;AAAA,YAC1B,UAAU,QAAQ,YAAY;AAAA,YAC9B,UAAU,QAAQ,YAAY;AAAA,YAC9B,aAAa,QAAQ,eAAe;AAAA,YACpC,OAAO,QAAQ,SAAS;AAAA,YACxB,oBAAoB,QAAQ,kBAAkB;AAAA,YAC9C,cAAc,QAAQ,gBAAgB;AAAA,UACxC;AAAA,QACF,CAAC;AAAA,QACH,GAAG,mBAAmB;AAAA,UACpB,CAAC,SACC,sBAAsB;AAAA,YACpB,CAAC,gBACC,YAAY,OAAO,KAAK,UACxB,YAAY,WAAW;AAAA,UAC3B;AAAA,QACJ;AAAA,MACF,EAAE,KAAK,CAAC,MAAM,UAAU;AACtB,cAAM,WAAW,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ;AAClD,cAAM,YAAY,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AACpD,YAAI,aAAa,UAAW,QAAO,MAAM,GAAG,cAAc,KAAK,EAAE;AACjE,eAAO,YAAY;AAAA,MACrB,CAAC,EAAE,MAAM,GAAG,EAAE,IAEpB,CAAC;AAAA,MACL,WAAW,UAAU,KAAK,CAAC,UAAU,MAAM,SAAS;AAAA,MACpD,WAAW,UAAU,IAAI,CAAC,WAAW;AAAA,QACnC,IAAI,MAAM;AAAA,QACV,aAAa,MAAM;AAAA,QACnB,WAAW,MAAM;AAAA,MACnB,EAAE;AAAA,MACF,UAAU,MAAM;AACd,cAAM,iBAAiB,UAAU,KAAK,CAAC,UAAU,MAAM,SAAS,KAAK,UAAU,CAAC,KAAK;AACrF,eAAO,iBACH,EAAE,IAAI,eAAe,WAAW,aAAa,eAAe,YAAY,IACxE;AAAA,MACN,GAAG;AAAA,MACH,0BAA0B;AAAA,MAC1B;AAAA,MACA,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,MAAM,oBAAoB,QAAQ,IAAI,iBAAiB,GAAG,QAAQ,OAAO;AAAA,QACzE,OAAO,oBAAoB,QAAQ,IAAI,iBAAiB,GAAG,SAAS,KAAK,SAAS,OAAO,KAAK,SAAS;AAAA,MACzG;AAAA,IACF,CAAC;AACD,iBAAa;AACb,kBAAc;AAAA,MACZ,SAAS,MAAM,KAAK,aAAa;AAAA,MACjC;AAAA,MACA;AAAA,IACF;AACA,aAAS,KAAK,kBAAkB,EAAE,QAAQ,WAAW,CAAC;AACtD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,aAAa,IAAK,cAAa;AACnC,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,kBAAc,EAAE,GAAI,eAAe,CAAC,GAAI,OAAO,QAAQ;AACvD,UAAM;AAAA,EACR,UAAE;AACA,aAAS,IAAI,EAAE,QAAQ,YAAY,GAAI,eAAe,CAAC,EAAG,CAAC;AAAA,EAC7D;AACF;AACA,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,SAAS,EACN,OAAO,EACP,SAAS,EACT,SAAS,6GAA6G;AAC3H,CAAC,EAAE,YAAY;AAEf,MAAM,6BAA6B,EAAE,OAAO;AAAA,EAC1C,iBAAiB,EAAE,KAAK,CAAC,aAAa,QAAQ,CAAC;AAAA,EAC/C,QAAQ,EAAE,OAAO;AAAA,IACf,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC7C,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC7C,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACvC,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC/C,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACvC,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACrD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACrD,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACtD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IAChD,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC/B,WAAW,EAAE,OAAO;AAAA,IACpB,WAAW,EAAE,OAAO;AAAA,EACtB,CAAC;AAAA,EACD,SAAS,EACN,OAAO;AAAA,IACN,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACvD,WAAW,EAAE,OAAO;AAAA,EACtB,CAAC,EACA,SAAS;AAAA,EACZ,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC9C,MAAM,EAAE;AAAA,IACN,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,OAAO,EAAE,OAAO;AAAA,MAChB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,CAAC;AAAA,EACH;AAAA,EACA,WAAW,EAAE;AAAA,IACX,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACrC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACxC,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC7C,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC7C,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC/C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACrC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACvC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACxC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC1C,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,WAAW,EAAE,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAAA,EACA,UAAU,EAAE;AAAA,IACV,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACrC,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACpD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAC9C,WAAW,EAAE,OAAO;AAAA,MACpB,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC/C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,CAAC;AAAA,EACH;AAAA,EACA,YAAY,EAAE;AAAA,IACZ,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,cAAc,EAAE,OAAO;AAAA,MACvB,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACxC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACrC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAC9C,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACpD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,WAAW,EAAE,OAAO;AAAA,MACpB,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC/C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,CAAC;AAAA,EACH;AAAA,EACA,cAAc,EAAE;AAAA,IACd,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAChD,iBAAiB,EAAE,OAAO;AAAA,MAC1B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACtC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACrC,QAAQ,EAAE,OAAO;AAAA,MACjB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACpD,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACnD,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAC9C,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACtD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAChD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC1C,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,MACpE,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC/C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAChD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACvC,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,MAC1D,WAAW,EAAE,OAAO;AAAA,MACpB,WAAW,EAAE,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAAA,EACA,OAAO,EAAE;AAAA,IACP,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACtC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACvC,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC9C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC9C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAChD,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACnD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACvC,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC/C,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACpD,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC1C,WAAW,EAAE,OAAO;AAAA,MACpB,WAAW,EAAE,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAAA,EACA,OAAO,EAAE;AAAA,IACP,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,MACxB,YAAY,EAAE,OAAO;AAAA,MACrB,WAAW,EAAE,OAAO;AAAA,MACpB,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MACvD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACtC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,MACxC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC5C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACtC,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,MAC1D,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IACtE,CAAC;AAAA,EACH;AAAA,EACA,WAAW,EAAE,QAAQ;AAAA,EACrB,WAAW,EAAE;AAAA,IACX,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,aAAa,EAAE,OAAO;AAAA,MACtB,WAAW,EAAE,QAAQ;AAAA,IACvB,CAAC;AAAA,EACH;AAAA,EACA,QAAQ,EAAE,OAAO;AAAA,IACf,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACnC,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,IAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,CAAC;AACH,CAAC;AAED,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,2BAA2B;AAAA,MAC1F;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,sBAAsB,QAAQ,wBAAwB;AAAA,QAClF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,wBAAwB;AAAA,QAC5E,EAAE,QAAQ,KAAK,aAAa,2CAA2C,QAAQ,wBAAwB;AAAA,QACvG,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,wBAAwB;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
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 CustomerEntity,\n CustomerPersonProfile,\n CustomerAddress,\n CustomerComment,\n CustomerActivity,\n CustomerTagAssignment,\n CustomerTag,\n CustomerLabelAssignment,\n CustomerLabel,\n CustomerDealPersonLink,\n CustomerDeal,\n CustomerTodoLink,\n CustomerInteraction,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport { mergePersonCustomFieldValues, resolvePersonCustomFieldRouting } from '../../../lib/customFieldRouting'\nimport {\n CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n EXAMPLE_TODO_SOURCE,\n CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n mapInteractionRecordToActivitySummary,\n mapInteractionRecordToTodoSummary,\n} from '../../../lib/interactionCompatibility'\nimport { resolveCustomerInteractionFeatureFlags } from '../../../lib/interactionFeatureFlags'\nimport { hydrateCanonicalInteractions } from '../../../lib/interactionReadModel'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanFromUnknown, parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/personCompanies'\nimport { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'\nimport { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'\nimport { resolveCustomerDetailTenantScope } from '../../../lib/detailTenantScope'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { buildCollectionTags, canonicalizeResourceTag, isCrudCacheEnabled, resolveCrudCache } from '@open-mercato/shared/lib/crud/cache'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\n// Person detail is one of the heaviest custom GETs (decryption sweeps across\n// addresses, tags, labels, interactions, custom fields, plus per-link query\n// engine enrichment). Mirror the gated get-then-set cache from the roles list\n// (#3143) so a hit skips every sweep below. Writes flow through the customers\n// command bus, which already flushes the matching crud:<resourceKind> collection\n// tags post-commit \u2014 these set tags reuse the exact same shapes (see\n// invalidateCrudCache), so no new invalidation wiring is needed (#3663).\n// The resource ids below are canonicalized with canonicalizeResourceTag (just\n// like invalidateCrudCache does via expandResourceAliases) before building the\n// collection tags, so camelCase ids (tagAssignment, labelAssignment,\n// personCompanyLink) produce the SAME tag the command bus deletes on\n// write/undo/redo \u2014 otherwise the cache would never be invalidated for them.\nconst PERSON_DETAIL_TTL_MS = 60_000\nconst PERSON_DETAIL_TAG_RESOURCES = [\n 'customers.person',\n 'customers.address',\n 'customers.tagAssignment',\n 'customers.labelAssignment',\n 'customers.personCompanyLink',\n 'customers.interaction',\n 'customers.activity',\n] as const\n\nfunction buildPersonDetailCacheKey(params: {\n personId: string\n tenantId: string | null\n organizationId: string | null\n callerId: string | null\n selectedOrganizationId: string | null\n scopedOrganizationIds: string[]\n interactionMode: 'canonical' | 'legacy'\n includeTokens: string[]\n}): string {\n const include = [...params.includeTokens].sort((a, b) => a.localeCompare(b)).join('|') || 'none'\n const scoped = [...params.scopedOrganizationIds].sort((a, b) => a.localeCompare(b)).join('|') || 'null'\n return [\n 'customers:person-detail',\n params.personId,\n `tenant=${params.tenantId ?? 'null'}`,\n `org=${params.organizationId ?? 'null'}`,\n `caller=${params.callerId ?? 'null'}`,\n `selOrg=${params.selectedOrganizationId ?? 'null'}`,\n `scope=${scoped}`,\n `mode=${params.interactionMode}`,\n `include=${include}`,\n ].join(':')\n}\n\nfunction buildPersonDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {\n const tags: string[] = []\n for (const resource of PERSON_DETAIL_TAG_RESOURCES) {\n const canonical = canonicalizeResourceTag(resource) ?? resource\n tags.push(...buildCollectionTags(canonical, tenantId, [organizationId]))\n }\n return tags\n}\n\nfunction parseIncludeParams(request: Request): Set<string> {\n const url = new URL(request.url)\n const raw = url.searchParams.getAll('include')\n const tokens = new Set<string>()\n raw.forEach((entry) => {\n if (!entry) return\n entry\n .split(',')\n .map((part) => part.trim().toLowerCase())\n .filter((part) => part.length > 0)\n .forEach((part) => tokens.add(part))\n })\n return tokens\n}\n\nfunction forbidden(message: string) {\n return NextResponse.json({ error: message }, { status: 403 })\n}\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction serializeTags(assignments: CustomerTagAssignment[]): Array<{ id: string; label: string; color: string | null }> {\n return assignments\n .map((assignment) => {\n const tag = assignment.tag as CustomerTag | string | null\n if (!tag || typeof tag === 'string') return null\n return {\n id: tag.id,\n label: tag.label,\n color: tag.color ?? null,\n }\n })\n .filter(\n (tag: { id: string; label: string; color: string | null } | null): tag is {\n id: string\n label: string\n color: string | null\n } => tag !== null,\n )\n}\n\ntype TodoDetail = {\n title: string | null\n isDone: boolean | null\n priority: number | null\n severity: string | null\n description: string | null\n dueAt: string | null\n organizationId: string | null\n customValues: Record<string, unknown> | null\n}\n\nfunction extractTodoTitle(record: Record<string, unknown>): string | null {\n const candidates = ['title', 'subject', 'name', 'summary', 'text', 'description']\n for (const key of candidates) {\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 parseNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return null\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) return parsed\n }\n return null\n}\n\nfunction parseDateValue(value: unknown): string | null {\n if (value instanceof Date) {\n const ts = value.getTime()\n return Number.isNaN(ts) ? null : value.toISOString()\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return null\n const candidate = new Date(trimmed)\n if (!Number.isNaN(candidate.getTime())) return candidate.toISOString()\n }\n return null\n}\n\nfunction readCustomField(record: Record<string, unknown>, key: string): unknown {\n const custom = record.custom ?? record.customFields ?? record.cf\n if (custom && typeof custom === 'object') {\n const bucket = custom as Record<string, unknown>\n if (key in bucket) return bucket[key]\n }\n return undefined\n}\n\ntype RouteProfilerMark = { label: string; time: bigint; extra?: Record<string, unknown> }\ntype RouteProfiler = { enabled: boolean; mark: (label: string, extra?: Record<string, unknown>) => void; end: (extra?: Record<string, unknown>) => void }\n\nfunction normalizeProfilerTokens(input: string | null | undefined): string[] {\n if (!input) return []\n return input\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter((token) => token.length > 0)\n}\n\nfunction profilerMatches(scope: string, tokens: string[]): boolean {\n if (!tokens.length) return false\n const lower = scope.toLowerCase()\n return tokens.some((token) => {\n if (token === '*' || token === 'all' || parseBooleanToken(token) === true) return true\n if (token.endsWith('*')) {\n const prefix = token.slice(0, -1)\n return prefix.length === 0 ? true : lower.startsWith(prefix)\n }\n return token === lower\n })\n}\n\nfunction isRouteProfilingEnabled(scope: string): boolean {\n const candidates = [\n process.env.OM_CRUD_PROFILE,\n process.env.NEXT_PUBLIC_OM_CRUD_PROFILE,\n process.env.OM_ROUTE_PROFILE,\n process.env.NEXT_PUBLIC_OM_ROUTE_PROFILE,\n ]\n for (const raw of candidates) {\n if (!raw) continue\n const tokens = normalizeProfilerTokens(raw)\n if (profilerMatches(scope, tokens)) return true\n }\n return false\n}\n\nfunction createRouteProfiler(scope: string): RouteProfiler {\n if (!isRouteProfilingEnabled(scope)) {\n return { enabled: false, mark: () => {}, end: () => {} }\n }\n const marks: RouteProfilerMark[] = [{ label: 'start', time: process.hrtime.bigint() }]\n const round = (value: number) => Math.round(value * 1000) / 1000\n return {\n enabled: true,\n mark(label, extra) {\n marks.push({ label, time: process.hrtime.bigint(), extra })\n },\n end(extra) {\n marks.push({ label: 'end', time: process.hrtime.bigint(), extra })\n const segments: Array<{ step: string; durationMs: number; extra?: Record<string, unknown> }> = []\n for (let idx = 1; idx < marks.length; idx += 1) {\n const current = marks[idx]\n const previous = marks[idx - 1]\n if (current.label === 'end') continue\n const durationMs = Number(current.time - previous.time) / 1_000_000\n segments.push({\n step: current.label,\n durationMs: round(durationMs),\n ...(current.extra ? { extra: current.extra } : {}),\n })\n }\n const totalNs = marks[marks.length - 1].time - marks[0].time\n const payload: Record<string, unknown> = {\n scope,\n totalMs: round(Number(totalNs) / 1_000_000),\n steps: segments,\n }\n const tail = marks[marks.length - 1]\n if (tail.extra && Object.keys(tail.extra).length > 0) payload.meta = tail.extra\n else if (extra && Object.keys(extra).length > 0) payload.meta = extra\n try {\n console.info('[route:profile]', payload)\n } catch {\n // ignore logging failures\n }\n },\n }\n}\n\nasync function resolveTodoDetails(\n queryEngine: QueryEngine,\n links: CustomerTodoLink[],\n tenantId: string | null,\n organizationIds: Array<string | null>,\n profiler?: RouteProfiler,\n): Promise<Map<string, TodoDetail>> {\n const details = new Map<string, TodoDetail>()\n if (!links.length || !tenantId) return details\n\n const scopedOrgIds = organizationIds\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n\n const idsBySource = new Map<string, Set<string>>()\n for (const link of links) {\n const source = typeof link.todoSource === 'string' && link.todoSource.trim().length > 0 ? link.todoSource : EXAMPLE_TODO_SOURCE\n const id = typeof link.todoId === 'string' && link.todoId.trim().length > 0 ? link.todoId : String(link.todoId ?? '')\n if (!id) continue\n if (!idsBySource.has(source)) idsBySource.set(source, new Set<string>())\n idsBySource.get(source)!.add(id)\n }\n\n profiler?.mark('todo_links_grouped', { sourceCount: idsBySource.size, linkCount: links.length })\n\n for (const [source, idSet] of idsBySource.entries()) {\n const ids = Array.from(idSet)\n if (!ids.length) continue\n profiler?.mark('todo_query_start', { source, count: ids.length })\n try {\n const result = await queryEngine.query<Record<string, unknown>>(source as EntityId, {\n tenantId,\n organizationIds: scopedOrgIds.length > 0 ? scopedOrgIds : undefined,\n filters: { id: { $in: ids } },\n includeCustomFields: ['priority', 'due_at', 'severity', 'description'],\n page: { page: 1, pageSize: Math.max(ids.length, 1) },\n })\n const itemCount = Array.isArray(result.items) ? result.items.length : 0\n profiler?.mark('todo_query_complete', { source, itemCount })\n let enriched = 0\n for (const item of result.items ?? []) {\n if (!item || typeof item !== 'object') continue\n const record = item as Record<string, unknown>\n const rawId = typeof record.id === 'string' && record.id.trim().length > 0 ? record.id : String(record.id ?? '')\n if (!rawId) continue\n const title = extractTodoTitle(record)\n const isDone = (() => {\n const direct = parseBooleanFromUnknown(record.is_done)\n if (direct !== null) return direct\n const custom = parseBooleanFromUnknown(readCustomField(record, 'is_done'))\n if (custom !== null) return custom\n const generic = parseBooleanFromUnknown(record.isDone)\n if (generic !== null) return generic\n return parseBooleanFromUnknown(readCustomField(record, 'isDone'))\n })()\n const priority = (() => {\n const candidates = [\n record['cf:priority'],\n record['cf_priority'],\n record.priority,\n readCustomField(record, 'priority'),\n ]\n for (const candidate of candidates) {\n const parsed = parseNumber(candidate)\n if (parsed !== null) return parsed\n }\n return null\n })()\n const severity = (() => {\n const candidates = [\n record['cf:severity'],\n record['cf_severity'],\n readCustomField(record, 'severity'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n })()\n const descriptionValue = (() => {\n const candidates = [\n record.description,\n record['cf:description'],\n record['cf_description'],\n readCustomField(record, 'description'),\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n })()\n const dueAt = (() => {\n const candidates = [\n record.due_at,\n record.dueAt,\n record['cf:due_at'],\n record['cf_due_at'],\n readCustomField(record, 'due_at'),\n readCustomField(record, 'dueAt'),\n ]\n for (const candidate of candidates) {\n const parsed = parseDateValue(candidate)\n if (parsed) return parsed\n }\n return null\n })()\n const organizationId = typeof record.organization_id === 'string' && record.organization_id.trim().length > 0\n ? record.organization_id\n : (typeof record.organizationId === 'string' && record.organizationId.trim().length > 0 ? record.organizationId : null)\n const customValues: Record<string, unknown> = {}\n const assignCustomValue = (key: unknown, value: unknown) => {\n if (typeof key !== 'string') return\n const trimmedKey = key.trim()\n if (!trimmedKey.length) return\n customValues[trimmedKey] = value === undefined ? null : value\n }\n for (const [rawKey, rawValue] of Object.entries(record)) {\n if (rawKey.startsWith('cf:')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n } else if (rawKey.startsWith('cf_')) {\n assignCustomValue(rawKey.slice(3), rawValue)\n }\n }\n const nestedCustom = record.custom ?? record.customFields ?? record.cf\n if (nestedCustom && typeof nestedCustom === 'object') {\n for (const [nestedKey, nestedValue] of Object.entries(nestedCustom as Record<string, unknown>)) {\n assignCustomValue(nestedKey, nestedValue)\n }\n }\n\n details.set(`${source}:${rawId}`, {\n title,\n isDone,\n priority,\n severity,\n description: descriptionValue,\n dueAt,\n organizationId,\n customValues: Object.keys(customValues).length ? customValues : null,\n })\n enriched += 1\n }\n profiler?.mark('todo_items_processed', { source, enriched })\n } catch (err) {\n profiler?.mark('todo_query_failed', { source, error: err instanceof Error ? err.message : String(err) })\n console.warn(`customers.people.detail: failed to resolve todos for source ${source}`, err)\n }\n }\n\n profiler?.mark('todo_details_ready', { enrichedCount: details.size })\n return details\n}\n\nexport async function GET(_req: Request, ctx: { params?: { id?: string } }) {\n const profiler = createRouteProfiler('customers.people.detail')\n profiler.mark('request_received')\n\n const includeTokens = parseIncludeParams(_req)\n profiler.mark('includes_parsed', { include: Array.from(includeTokens) })\n\n const includeActivities = includeTokens.has('activities')\n const includeAddresses = includeTokens.has('addresses')\n const includeComments = includeTokens.has('comments') || includeTokens.has('notes')\n const includeDeals = includeTokens.has('deals')\n const includeInteractions = includeTokens.has('interactions')\n const includeTodos = includeTokens.has('todos') || includeTokens.has('tasks')\n const plannedPreviewLimit = 5\n\n let statusCode = 500\n let profileMeta: Record<string, unknown> | undefined\n let addresses: CustomerAddress[] = []\n let comments: CustomerComment[] = []\n let activities: CustomerActivity[] = []\n let todoLinks: CustomerTodoLink[] = []\n let todoDetails = new Map<string, TodoDetail>()\n let deals: CustomerDeal[] = []\n let tagAssignments: CustomerTagAssignment[] = []\n let userMap = new Map<string, { name: string | null; email: string | null }>()\n let customFields: Record<string, unknown> = {}\n let viewerUserId: string | null = null\n let profile: CustomerPersonProfile | null = null\n let companies: Array<{ linkId: string | null; companyId: string; displayName: string; isPrimary: boolean; synthetic?: boolean }> = []\n\n try {\n const parse = paramsSchema.safeParse({ id: ctx.params?.id })\n if (!parse.success) {\n statusCode = 400\n profileMeta = { reason: 'invalid_person_id' }\n return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })\n }\n profiler.mark('params_resolved', { id: parse.data.id })\n\n const auth = await getAuthFromRequest(_req)\n if (!auth) {\n profiler.mark('auth_missing')\n statusCode = 401\n profileMeta = { reason: 'unauthorized' }\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n profiler.mark('auth_resolved', {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n userId: auth.sub ?? null,\n isApiKey: !!auth.isApiKey,\n })\n viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n\n const container = await createRequestContainer()\n profiler.mark('container_resolved')\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: _req })\n const interactionFlags = await resolveCustomerInteractionFeatureFlags(container, scope?.tenantId ?? auth.tenantId)\n const interactionMode = interactionFlags.unified ? 'canonical' : 'legacy'\n profiler.mark('scope_resolved', {\n scopedOrganizations: Array.isArray(scope?.filterIds) ? scope.filterIds.length : 0,\n })\n const em = (container.resolve('em') as EntityManager)\n\n const tenantScope = resolveCustomerDetailTenantScope(parse.data.id, 'person', auth)\n if (!tenantScope.allowed) {\n statusCode = 404\n profileMeta = { reason: 'person_tenant_mismatch' }\n return notFound('Person not found')\n }\n\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n tenantScope.where,\n {},\n {\n tenantId: scope?.tenantId ?? auth.tenantId ?? null,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n },\n )\n profiler.mark('person_loaded', { found: !!person })\n if (!person) {\n statusCode = 404\n profileMeta = { reason: 'person_not_found' }\n return notFound('Person not found')\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: person.organizationId })) {\n statusCode = 403\n profileMeta = { reason: 'organization_forbidden' }\n return forbidden('Access denied')\n }\n\n // Gated get-then-set cache. Key on the resolved person plus the effective\n // RBAC scope (caller identity, selected/visible orgs) and the payload-shaping\n // inputs (interaction mode + include set). The lookup sits before the\n // expensive sweeps below so a hit returns without touching the DB.\n const personDetailTenantId = person.tenantId ?? auth.tenantId ?? null\n const personDetailOrganizationId = person.organizationId ?? null\n const personDetailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null\n const personDetailCacheKey = personDetailCache\n ? buildPersonDetailCacheKey({\n personId: person.id,\n tenantId: personDetailTenantId,\n organizationId: personDetailOrganizationId,\n callerId: auth.sub ?? null,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n scopedOrganizationIds: Array.isArray(scope?.filterIds) ? scope.filterIds : [],\n interactionMode,\n includeTokens: Array.from(includeTokens),\n })\n : null\n const personDetailCacheTags = personDetailCache\n ? buildPersonDetailCacheTags(personDetailTenantId, personDetailOrganizationId)\n : []\n\n if (personDetailCache && personDetailCacheKey) {\n const cached = await runWithCacheTenant(personDetailTenantId, () => personDetailCache.get(personDetailCacheKey))\n if (cached) {\n statusCode = 200\n profileMeta = { cacheHit: true, include: Array.from(includeTokens), interactionMode }\n profiler.mark('cache_hit')\n return NextResponse.json(cached)\n }\n }\n\n const [profileResult, personCompanyLinks] = await Promise.all([\n findOneWithDecryption(em, CustomerPersonProfile, { entity: person }, { populate: ['company'] }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }),\n loadPersonCompanyLinks(em, person),\n ])\n profile = profileResult\n profiler.mark('profile_loaded', { found: !!profile })\n companies = summarizePersonCompanies(profile, personCompanyLinks)\n\n // Per-user email privacy (CRM email integration spec, \"Layer 1 \u2014 DB filter\"):\n // exclude private email interactions owned by other users from every read\n // path that returns customer_interactions. Non-email rows, shared emails, and\n // legacy null-visibility rows pass through. v1 is strict owner-only: there is\n // NO admin bypass \u2014 the filter ignores caller features, and\n // `customers.email.view_private` is reserved (inert) for v2 oversight.\n const emailVisibilityFilter = buildEmailVisibilityMikroFilter({\n currentUserId: viewerUserId,\n userFeatures: undefined,\n })\n\n const personScope = { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }\n const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos\n\n // Audited for #3386 rollout (P3): every findWithDecryption call in this\n // block and in the activities/todoLinks fetches below sorts only on\n // non-encrypted system columns (isPrimary, createdAt, occurredAt,\n // scheduledAt). Each fetch is bounded by entity scope (one person) plus\n // an explicit limit, so neither the paginated-list hazard (#3278) nor the\n // two-phase encrypted-sort migration apply here.\n //\n // These reads only depend on the resolved person + scope + email visibility\n // filter, so dispatch them together to avoid a server-side request waterfall\n // before the detail surface can render (issue #3203).\n const [\n addressesResult,\n tagAssignmentsResult,\n labelAssignments,\n commentsResult,\n canonicalInteractionRows,\n ] = await Promise.all([\n includeAddresses\n ? findWithDecryption(em, CustomerAddress, { entity: person.id }, { orderBy: { isPrimary: 'desc', createdAt: 'desc' } }, personScope)\n : Promise.resolve<CustomerAddress[]>([]),\n findWithDecryption(em, CustomerTagAssignment, { entity: person.id }, { populate: ['tag'] }, personScope),\n findWithDecryption(em, CustomerLabelAssignment, { entity: person.id }, { populate: ['label'] }, personScope),\n includeComments\n ? findWithDecryption(em, CustomerComment, { entity: person.id }, { orderBy: { createdAt: 'desc' }, limit: 50 }, personScope)\n : Promise.resolve<CustomerComment[]>([]),\n shouldLoadCanonicalInteractions\n ? findWithDecryption(\n em,\n CustomerInteraction,\n interactionFlags.unified\n ? { entity: person.id, deletedAt: null, ...emailVisibilityFilter }\n : { entity: person.id, ...emailVisibilityFilter },\n { orderBy: { scheduledAt: 'asc', createdAt: 'desc' }, limit: 100 },\n personScope,\n )\n : Promise.resolve<CustomerInteraction[]>([]),\n ])\n addresses = addressesResult\n tagAssignments = tagAssignmentsResult\n comments = commentsResult\n profiler.mark('addresses_loaded', { count: addresses.length })\n profiler.mark('tags_loaded', { count: tagAssignments.length })\n profiler.mark('labels_loaded', { count: labelAssignments.length })\n profiler.mark('comments_loaded', { count: comments.length })\n profiler.mark('canonical_interactions_loaded', { count: canonicalInteractionRows.length })\n const canonicalActiveInteractions = canonicalInteractionRows.filter((interaction) => !interaction.deletedAt)\n const canonicalInteractions = shouldLoadCanonicalInteractions\n ? await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n interactions: canonicalActiveInteractions,\n enrich: includeInteractions,\n })\n : []\n profiler.mark('canonical_interactions_hydrated', { count: canonicalInteractions.length })\n\n const plannedPreviewInteractions = shouldLoadCanonicalInteractions\n ? (() => {\n const sorted = [...canonicalInteractions]\n .filter((interaction) => interaction.status === 'planned' && interaction.interactionType !== 'task')\n .sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.createdAt).getTime()\n if (leftTime === rightTime) return left.id.localeCompare(right.id)\n return leftTime - rightTime\n })\n return sorted.slice(0, plannedPreviewLimit)\n })()\n : await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n interactions: await findWithDecryption(\n em,\n CustomerInteraction,\n {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n status: 'planned',\n interactionType: { $ne: 'task' },\n ...emailVisibilityFilter,\n },\n { orderBy: { scheduledAt: 'ASC', createdAt: 'ASC' }, limit: plannedPreviewLimit },\n { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },\n ),\n enrich: true,\n })\n\n if (includeActivities && !interactionFlags.unified) {\n activities = await findWithDecryption(em, CustomerActivity, { entity: person.id }, { orderBy: { occurredAt: 'desc', createdAt: 'desc' }, limit: 50 }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n profiler.mark('activities_loaded', { count: activities.length })\n }\n\n if (includeInteractions) {\n profiler.mark('interactions_loaded', { count: canonicalInteractions.length })\n }\n\n if (includeTodos && !interactionFlags.unified) {\n todoLinks = await findWithDecryption(em, CustomerTodoLink, { entity: person.id }, { orderBy: { createdAt: 'desc' }, limit: 50 }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n profiler.mark('todo_links_loaded', { count: todoLinks.length })\n if (todoLinks.length) {\n const queryEngine = (container.resolve('queryEngine') as QueryEngine)\n try {\n todoDetails = await resolveTodoDetails(\n queryEngine,\n todoLinks,\n person.tenantId ?? auth.tenantId ?? null,\n [person.organizationId ?? null, ...(scope?.filterIds ?? [])],\n profiler,\n )\n } catch (err) {\n console.warn('customers.people.detail: failed to enrich todo links', err)\n }\n profiler.mark('todo_details_enriched', { count: todoDetails.size, links: todoLinks.length })\n }\n }\n\n const canonicalActivityBridgeIds = new Set(\n canonicalInteractionRows\n .filter((interaction) => interaction.source === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE)\n .map((interaction) => interaction.id),\n )\n const canonicalTodoBridgeIds = new Set(\n canonicalInteractionRows\n .filter((interaction) => interaction.source === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE)\n .map((interaction) => interaction.id),\n )\n const canonicalActivityItems = canonicalInteractions\n .filter((interaction) => interaction.interactionType !== 'task')\n .map((interaction) => mapInteractionRecordToActivitySummary(interaction))\n const canonicalTodoItems = canonicalInteractions\n .filter((interaction) => interaction.interactionType === 'task')\n .map((interaction) => mapInteractionRecordToTodoSummary(interaction))\n\n const authorIds = new Set<string>()\n if (includeActivities) {\n for (const activity of activities) {\n if (activity.authorUserId) authorIds.add(activity.authorUserId)\n }\n }\n if (includeComments) {\n for (const comment of comments) {\n if (comment.authorUserId) authorIds.add(comment.authorUserId)\n }\n }\n if (viewerUserId) authorIds.add(viewerUserId)\n\n if (authorIds.size) {\n const users = await findWithDecryption(em, User, { id: { $in: Array.from(authorIds) } }, {}, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null })\n userMap = new Map(\n users.map((user) => [\n user.id,\n {\n name: user.name ?? null,\n email: user.email ?? null,\n },\n ])\n )\n profiler.mark('authors_loaded', { count: userMap.size })\n }\n\n if (includeDeals) {\n const dealLinks = await findWithDecryption(\n em,\n CustomerDealPersonLink,\n { person: person.id },\n { populate: ['deal'] },\n { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },\n )\n deals = dealLinks\n .map((link) => (link.deal as CustomerDeal | string | null) ?? null)\n .filter((deal): deal is CustomerDeal => !!deal && typeof deal !== 'string')\n profiler.mark('deals_loaded', { count: deals.length })\n }\n\n // Entity custom fields, profile custom fields, and the routing lookup do not\n // depend on each other (profileId is already resolved above), so load them in\n // parallel instead of three sequential awaits (issue #3203).\n const profileId = profile?.id ?? null\n const [entityCustomFieldValues, profileCustomFieldValues, routing] = await Promise.all([\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_entity,\n recordIds: [person.id],\n tenantIdByRecord: { [person.id]: person.tenantId ?? null },\n organizationIdByRecord: { [person.id]: person.organizationId ?? null },\n tenantFallbacks: [\n person.tenantId ?? auth.tenantId ?? null,\n ].filter((v): v is string => !!v),\n }),\n profileId\n ? loadCustomFieldValues({\n em,\n entityId: E.customers.customer_person_profile,\n recordIds: [profileId],\n tenantIdByRecord: { [profileId]: profile?.tenantId ?? null },\n organizationIdByRecord: { [profileId]: profile?.organizationId ?? null },\n tenantFallbacks: [\n profile?.tenantId ?? person.tenantId ?? auth.tenantId ?? null,\n ].filter((v): v is string => !!v),\n })\n : Promise.resolve<Record<string, Record<string, unknown>>>({}),\n resolvePersonCustomFieldRouting(em, person.tenantId ?? null, person.organizationId ?? null),\n ])\n profiler.mark('entity_custom_fields_loaded', { keys: Object.keys(entityCustomFieldValues?.[person.id] ?? {}).length })\n if (profileId) {\n profiler.mark('profile_custom_fields_loaded', { keys: Object.keys(profileCustomFieldValues?.[profileId] ?? {}).length })\n }\n profiler.mark('custom_field_routing_resolved', { keys: routing.size })\n customFields = normalizeCustomerDetailCustomFields(\n mergePersonCustomFieldValues(\n routing,\n entityCustomFieldValues?.[person.id] ?? {},\n profileId ? profileCustomFieldValues?.[profileId] ?? {} : {},\n ),\n )\n profiler.mark('custom_fields_merged', { keys: Object.keys(customFields).length })\n\n const viewerUserIdFinal = viewerUserId\n // The count fields are independent of each other; dispatch them together so\n // the response counts object is assembled in one round of parallel queries\n // instead of an inline await waterfall (issue #3203).\n const [\n commentsCount,\n activitiesCount,\n interactionsCount,\n todosCount,\n addressesCount,\n dealsCount,\n ] = await Promise.all([\n includeComments\n ? Promise.resolve(comments.length)\n : em.count(CustomerComment, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n interactionType: { $ne: 'task' },\n ...emailVisibilityFilter,\n }),\n em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n ...emailVisibilityFilter,\n }),\n interactionFlags.unified\n ? em.count(CustomerInteraction, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n deletedAt: null,\n interactionType: 'task',\n })\n : em.count(CustomerTodoLink, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n includeAddresses\n ? Promise.resolve(addresses.length)\n : em.count(CustomerAddress, {\n entity: person.id,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n }),\n includeDeals\n ? Promise.resolve(deals.length)\n : em.count(CustomerDealPersonLink, {\n person: person.id,\n }),\n ])\n const counts = {\n tags: tagAssignments.length + labelAssignments.length,\n comments: commentsCount,\n activities: activitiesCount,\n interactions: interactionsCount,\n todos: todosCount,\n addresses: addressesCount,\n deals: dealsCount,\n companies: companies.length,\n }\n\n const payload = {\n interactionMode,\n person: {\n id: person.id,\n displayName: person.displayName,\n description: person.description,\n ownerUserId: person.ownerUserId,\n primaryEmail: person.primaryEmail,\n primaryPhone: person.primaryPhone,\n status: person.status,\n lifecycleStage: person.lifecycleStage,\n source: person.source,\n temperature: person.temperature,\n renewalQuarter: person.renewalQuarter,\n nextInteractionAt: person.nextInteractionAt ? person.nextInteractionAt.toISOString() : null,\n nextInteractionName: person.nextInteractionName,\n nextInteractionRefId: person.nextInteractionRefId,\n nextInteractionIcon: person.nextInteractionIcon,\n nextInteractionColor: person.nextInteractionColor,\n organizationId: person.organizationId,\n tenantId: person.tenantId,\n isActive: person.isActive,\n createdAt: person.createdAt.toISOString(),\n updatedAt: person.updatedAt.toISOString(),\n },\n profile: profile\n ? {\n id: profile.id,\n firstName: profile.firstName,\n lastName: profile.lastName,\n preferredName: profile.preferredName,\n jobTitle: profile.jobTitle,\n department: profile.department,\n seniority: profile.seniority,\n timezone: profile.timezone,\n linkedInUrl: profile.linkedInUrl,\n twitterUrl: profile.twitterUrl,\n companyEntityId: profile.company ? (typeof profile.company === 'string' ? profile.company : profile.company.id) : null,\n updatedAt: profile.updatedAt.toISOString(),\n }\n : null,\n customFields,\n tags: [\n ...serializeTags(tagAssignments),\n ...labelAssignments\n .map((a) => {\n const label = a.label as CustomerLabel | string | null\n if (!label || typeof label === 'string') return null\n return { id: label.id, label: label.label, color: null }\n })\n .filter((t): t is { id: string; label: string; color: null } => t !== null),\n ],\n addresses: includeAddresses\n ? addresses.map((address) => ({\n id: address.id,\n name: address.name,\n purpose: address.purpose,\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2,\n buildingNumber: address.buildingNumber,\n flatNumber: address.flatNumber,\n city: address.city,\n region: address.region,\n postalCode: address.postalCode,\n country: address.country,\n latitude: address.latitude,\n longitude: address.longitude,\n isPrimary: address.isPrimary,\n createdAt: address.createdAt.toISOString(),\n }))\n : [],\n comments: includeComments\n ? comments.map((comment) => {\n const authorInfo = comment.authorUserId ? userMap.get(comment.authorUserId) : null\n return {\n id: comment.id,\n body: comment.body,\n authorUserId: comment.authorUserId,\n authorName: authorInfo?.name ?? null,\n authorEmail: authorInfo?.email ?? null,\n dealId: comment.deal ? (typeof comment.deal === 'string' ? comment.deal : comment.deal.id) : null,\n createdAt: comment.createdAt.toISOString(),\n appearanceIcon: comment.appearanceIcon ?? null,\n appearanceColor: comment.appearanceColor ?? null,\n }\n })\n : [],\n activities: includeActivities\n ? (\n interactionFlags.unified\n ? canonicalActivityItems\n : [\n ...activities\n .filter((activity) => !canonicalActivityBridgeIds.has(activity.id))\n .map((activity) => ({\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject,\n body: activity.body,\n occurredAt: activity.occurredAt ? activity.occurredAt.toISOString() : null,\n dealId: activity.deal ? (typeof activity.deal === 'string' ? activity.deal : activity.deal.id) : null,\n authorUserId: activity.authorUserId,\n authorName: activity.authorUserId ? userMap.get(activity.authorUserId)?.name ?? null : null,\n authorEmail: activity.authorUserId ? userMap.get(activity.authorUserId)?.email ?? null : null,\n createdAt: activity.createdAt.toISOString(),\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n })),\n ...canonicalActivityItems.filter(\n (activity) =>\n canonicalInteractions.some(\n (interaction) =>\n interaction.id === activity.id &&\n interaction.source === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n ),\n ),\n ].sort((left, right) => {\n const leftTime = new Date(left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.occurredAt ?? right.createdAt).getTime()\n if (leftTime === rightTime) return right.id.localeCompare(left.id)\n return rightTime - leftTime\n }).slice(0, 50)\n )\n : [],\n interactions: includeInteractions\n ? canonicalInteractions\n : [],\n deals: includeDeals\n ? deals.map((deal) => ({\n id: deal.id,\n title: deal.title,\n status: deal.status,\n pipelineStage: deal.pipelineStage,\n pipelineId: deal.pipelineId ?? null,\n pipelineStageId: deal.pipelineStageId ?? null,\n valueAmount: deal.valueAmount,\n valueCurrency: deal.valueCurrency,\n probability: deal.probability,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId,\n source: deal.source,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n }))\n : [],\n todos: includeTodos\n ? (\n interactionFlags.unified\n ? canonicalTodoItems\n : [\n ...todoLinks\n .filter((link) => !canonicalTodoBridgeIds.has(link.todoId))\n .map((link) => {\n const source = typeof link.todoSource === 'string' && link.todoSource.trim().length > 0 ? link.todoSource : EXAMPLE_TODO_SOURCE\n const key = `${source}:${link.todoId}`\n const detail = todoDetails.get(key)\n return {\n id: link.id,\n todoId: link.todoId,\n todoSource: source,\n createdAt: link.createdAt.toISOString(),\n createdByUserId: link.createdByUserId,\n title: detail?.title ?? null,\n isDone: detail?.isDone ?? null,\n priority: detail?.priority ?? null,\n severity: detail?.severity ?? null,\n description: detail?.description ?? null,\n dueAt: detail?.dueAt ?? null,\n todoOrganizationId: detail?.organizationId ?? null,\n customValues: detail?.customValues ?? null,\n }\n }),\n ...canonicalTodoItems.filter(\n (todo) =>\n canonicalInteractions.some(\n (interaction) =>\n interaction.id === todo.todoId &&\n interaction.source === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,\n ),\n ),\n ].sort((left, right) => {\n const leftTime = new Date(left.createdAt).getTime()\n const rightTime = new Date(right.createdAt).getTime()\n if (leftTime === rightTime) return right.id.localeCompare(left.id)\n return rightTime - leftTime\n }).slice(0, 50)\n )\n : [],\n isPrimary: companies.some((entry) => entry.isPrimary),\n companies: companies.map((entry) => ({\n id: entry.companyId,\n displayName: entry.displayName,\n isPrimary: entry.isPrimary,\n })),\n company: (() => {\n const primaryCompany = companies.find((entry) => entry.isPrimary) ?? companies[0] ?? null\n return primaryCompany\n ? { id: primaryCompany.companyId, displayName: primaryCompany.displayName }\n : null\n })(),\n plannedActivitiesPreview: plannedPreviewInteractions,\n counts,\n viewer: {\n userId: viewerUserIdFinal,\n name: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.name ?? null : null,\n email: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.email ?? auth.email ?? null : auth.email ?? null,\n },\n }\n\n if (personDetailCache && personDetailCacheKey) {\n try {\n await runWithCacheTenant(personDetailTenantId, () =>\n personDetailCache.set(personDetailCacheKey, payload, {\n ttl: PERSON_DETAIL_TTL_MS,\n tags: personDetailCacheTags,\n }),\n )\n } catch {}\n }\n\n const response = NextResponse.json(payload)\n statusCode = 200\n profileMeta = {\n include: Array.from(includeTokens),\n interactionMode,\n counts,\n }\n profiler.mark('response_ready', { status: statusCode })\n return response\n } catch (err) {\n if (statusCode < 400) statusCode = 500\n const message = err instanceof Error ? err.message : String(err)\n profileMeta = { ...(profileMeta ?? {}), error: message }\n throw err\n } finally {\n profiler.end({ status: statusCode, ...(profileMeta ?? {}) })\n }\n}\nconst personDetailQuerySchema = z.object({\n include: z\n .string()\n .optional()\n .describe('Comma-separated list of relations to include (addresses, comments, activities, interactions, deals, todos).'),\n}).passthrough()\n\nconst personDetailResponseSchema = z.object({\n interactionMode: z.enum(['canonical', 'legacy']),\n person: z.object({\n id: z.string().uuid(),\n displayName: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n primaryEmail: z.string().nullable().optional(),\n primaryPhone: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n lifecycleStage: z.string().nullable().optional(),\n source: z.string().nullable().optional(),\n nextInteractionAt: z.string().nullable().optional(),\n nextInteractionName: z.string().nullable().optional(),\n nextInteractionRefId: z.string().nullable().optional(),\n nextInteractionIcon: z.string().nullable().optional(),\n nextInteractionColor: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n isActive: z.boolean().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n profile: z\n .object({\n id: z.string().uuid(),\n firstName: z.string().nullable().optional(),\n lastName: z.string().nullable().optional(),\n preferredName: z.string().nullable().optional(),\n jobTitle: z.string().nullable().optional(),\n department: z.string().nullable().optional(),\n seniority: z.string().nullable().optional(),\n timezone: z.string().nullable().optional(),\n linkedInUrl: z.string().nullable().optional(),\n twitterUrl: z.string().nullable().optional(),\n companyEntityId: z.string().uuid().nullable().optional(),\n updatedAt: z.string(),\n })\n .nullable(),\n customFields: z.record(z.string(), z.unknown()),\n tags: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n color: z.string().nullable().optional(),\n }),\n ),\n addresses: z.array(\n z.object({\n id: z.string().uuid(),\n name: z.string().nullable().optional(),\n purpose: z.string().nullable().optional(),\n addressLine1: z.string().nullable().optional(),\n addressLine2: z.string().nullable().optional(),\n buildingNumber: z.string().nullable().optional(),\n flatNumber: z.string().nullable().optional(),\n city: z.string().nullable().optional(),\n region: z.string().nullable().optional(),\n postalCode: z.string().nullable().optional(),\n country: z.string().nullable().optional(),\n latitude: z.number().nullable().optional(),\n longitude: z.number().nullable().optional(),\n isPrimary: z.boolean().nullable().optional(),\n createdAt: z.string(),\n }),\n ),\n comments: z.array(\n z.object({\n id: z.string().uuid(),\n body: z.string().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n }),\n ),\n activities: z.array(\n z.object({\n id: z.string().uuid(),\n activityType: z.string(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n }),\n ),\n interactions: z.array(\n z.object({\n id: z.string().uuid(),\n entityId: z.string().uuid().nullable().optional(),\n interactionType: z.string(),\n title: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n status: z.string(),\n scheduledAt: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n priority: z.number().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealTitle: z.string().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n source: z.string().nullable().optional(),\n _integrations: z.record(z.string(), z.unknown()).optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n ),\n deals: z.array(\n z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n valueAmount: z.number().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.string().nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n ),\n todos: z.array(\n z.object({\n id: z.string().uuid(),\n todoId: z.string().uuid(),\n todoSource: z.string(),\n createdAt: z.string(),\n createdByUserId: z.string().uuid().nullable().optional(),\n title: z.string().nullable().optional(),\n isDone: z.boolean().nullable().optional(),\n priority: z.number().nullable().optional(),\n severity: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n dueAt: z.string().nullable().optional(),\n todoOrganizationId: z.string().uuid().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n }),\n ),\n isPrimary: z.boolean(),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n displayName: z.string(),\n isPrimary: z.boolean(),\n }),\n ),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n})\n\nconst personDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch person detail',\n methods: {\n GET: {\n summary: 'Fetch person with related data',\n description: 'Returns a person customer record with optional related resources such as addresses, comments, activities, interactions, deals, and todos.',\n query: personDetailQuerySchema,\n responses: [\n { status: 200, description: 'Person detail payload', schema: personDetailResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: personDetailErrorSchema },\n { status: 401, description: 'Unauthorized', schema: personDetailErrorSchema },\n { status: 403, description: 'Forbidden for tenant/organization scope', schema: personDetailErrorSchema },\n { status: 404, description: 'Person not found', schema: personDetailErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6431.1.g1037269810",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6431.1.g1037269810",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6431.1.g1037269810",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6431.1.g1037269810",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
264
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
265
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6431.1.g1037269810",
|
|
264
|
+
"@open-mercato/shared": "0.6.6-develop.6431.1.g1037269810",
|
|
265
|
+
"@open-mercato/ui": "0.6.6-develop.6431.1.g1037269810",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^6.9.1",
|
|
268
268
|
"@testing-library/react": "^16.3.1",
|
|
@@ -42,6 +42,8 @@ import { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/p
|
|
|
42
42
|
import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
|
|
43
43
|
import { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'
|
|
44
44
|
import { resolveCustomerDetailTenantScope } from '../../../lib/detailTenantScope'
|
|
45
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
46
|
+
import { buildCollectionTags, canonicalizeResourceTag, isCrudCacheEnabled, resolveCrudCache } from '@open-mercato/shared/lib/crud/cache'
|
|
45
47
|
|
|
46
48
|
export const metadata = {
|
|
47
49
|
GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },
|
|
@@ -51,6 +53,63 @@ const paramsSchema = z.object({
|
|
|
51
53
|
id: z.string().uuid(),
|
|
52
54
|
})
|
|
53
55
|
|
|
56
|
+
// Person detail is one of the heaviest custom GETs (decryption sweeps across
|
|
57
|
+
// addresses, tags, labels, interactions, custom fields, plus per-link query
|
|
58
|
+
// engine enrichment). Mirror the gated get-then-set cache from the roles list
|
|
59
|
+
// (#3143) so a hit skips every sweep below. Writes flow through the customers
|
|
60
|
+
// command bus, which already flushes the matching crud:<resourceKind> collection
|
|
61
|
+
// tags post-commit — these set tags reuse the exact same shapes (see
|
|
62
|
+
// invalidateCrudCache), so no new invalidation wiring is needed (#3663).
|
|
63
|
+
// The resource ids below are canonicalized with canonicalizeResourceTag (just
|
|
64
|
+
// like invalidateCrudCache does via expandResourceAliases) before building the
|
|
65
|
+
// collection tags, so camelCase ids (tagAssignment, labelAssignment,
|
|
66
|
+
// personCompanyLink) produce the SAME tag the command bus deletes on
|
|
67
|
+
// write/undo/redo — otherwise the cache would never be invalidated for them.
|
|
68
|
+
const PERSON_DETAIL_TTL_MS = 60_000
|
|
69
|
+
const PERSON_DETAIL_TAG_RESOURCES = [
|
|
70
|
+
'customers.person',
|
|
71
|
+
'customers.address',
|
|
72
|
+
'customers.tagAssignment',
|
|
73
|
+
'customers.labelAssignment',
|
|
74
|
+
'customers.personCompanyLink',
|
|
75
|
+
'customers.interaction',
|
|
76
|
+
'customers.activity',
|
|
77
|
+
] as const
|
|
78
|
+
|
|
79
|
+
function buildPersonDetailCacheKey(params: {
|
|
80
|
+
personId: string
|
|
81
|
+
tenantId: string | null
|
|
82
|
+
organizationId: string | null
|
|
83
|
+
callerId: string | null
|
|
84
|
+
selectedOrganizationId: string | null
|
|
85
|
+
scopedOrganizationIds: string[]
|
|
86
|
+
interactionMode: 'canonical' | 'legacy'
|
|
87
|
+
includeTokens: string[]
|
|
88
|
+
}): string {
|
|
89
|
+
const include = [...params.includeTokens].sort((a, b) => a.localeCompare(b)).join('|') || 'none'
|
|
90
|
+
const scoped = [...params.scopedOrganizationIds].sort((a, b) => a.localeCompare(b)).join('|') || 'null'
|
|
91
|
+
return [
|
|
92
|
+
'customers:person-detail',
|
|
93
|
+
params.personId,
|
|
94
|
+
`tenant=${params.tenantId ?? 'null'}`,
|
|
95
|
+
`org=${params.organizationId ?? 'null'}`,
|
|
96
|
+
`caller=${params.callerId ?? 'null'}`,
|
|
97
|
+
`selOrg=${params.selectedOrganizationId ?? 'null'}`,
|
|
98
|
+
`scope=${scoped}`,
|
|
99
|
+
`mode=${params.interactionMode}`,
|
|
100
|
+
`include=${include}`,
|
|
101
|
+
].join(':')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildPersonDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {
|
|
105
|
+
const tags: string[] = []
|
|
106
|
+
for (const resource of PERSON_DETAIL_TAG_RESOURCES) {
|
|
107
|
+
const canonical = canonicalizeResourceTag(resource) ?? resource
|
|
108
|
+
tags.push(...buildCollectionTags(canonical, tenantId, [organizationId]))
|
|
109
|
+
}
|
|
110
|
+
return tags
|
|
111
|
+
}
|
|
112
|
+
|
|
54
113
|
function parseIncludeParams(request: Request): Set<string> {
|
|
55
114
|
const url = new URL(request.url)
|
|
56
115
|
const raw = url.searchParams.getAll('include')
|
|
@@ -485,6 +544,39 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
485
544
|
return forbidden('Access denied')
|
|
486
545
|
}
|
|
487
546
|
|
|
547
|
+
// Gated get-then-set cache. Key on the resolved person plus the effective
|
|
548
|
+
// RBAC scope (caller identity, selected/visible orgs) and the payload-shaping
|
|
549
|
+
// inputs (interaction mode + include set). The lookup sits before the
|
|
550
|
+
// expensive sweeps below so a hit returns without touching the DB.
|
|
551
|
+
const personDetailTenantId = person.tenantId ?? auth.tenantId ?? null
|
|
552
|
+
const personDetailOrganizationId = person.organizationId ?? null
|
|
553
|
+
const personDetailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null
|
|
554
|
+
const personDetailCacheKey = personDetailCache
|
|
555
|
+
? buildPersonDetailCacheKey({
|
|
556
|
+
personId: person.id,
|
|
557
|
+
tenantId: personDetailTenantId,
|
|
558
|
+
organizationId: personDetailOrganizationId,
|
|
559
|
+
callerId: auth.sub ?? null,
|
|
560
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
561
|
+
scopedOrganizationIds: Array.isArray(scope?.filterIds) ? scope.filterIds : [],
|
|
562
|
+
interactionMode,
|
|
563
|
+
includeTokens: Array.from(includeTokens),
|
|
564
|
+
})
|
|
565
|
+
: null
|
|
566
|
+
const personDetailCacheTags = personDetailCache
|
|
567
|
+
? buildPersonDetailCacheTags(personDetailTenantId, personDetailOrganizationId)
|
|
568
|
+
: []
|
|
569
|
+
|
|
570
|
+
if (personDetailCache && personDetailCacheKey) {
|
|
571
|
+
const cached = await runWithCacheTenant(personDetailTenantId, () => personDetailCache.get(personDetailCacheKey))
|
|
572
|
+
if (cached) {
|
|
573
|
+
statusCode = 200
|
|
574
|
+
profileMeta = { cacheHit: true, include: Array.from(includeTokens), interactionMode }
|
|
575
|
+
profiler.mark('cache_hit')
|
|
576
|
+
return NextResponse.json(cached)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
488
580
|
const [profileResult, personCompanyLinks] = await Promise.all([
|
|
489
581
|
findOneWithDecryption(em, CustomerPersonProfile, { entity: person }, { populate: ['company'] }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }),
|
|
490
582
|
loadPersonCompanyLinks(em, person),
|
|
@@ -801,7 +893,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
801
893
|
companies: companies.length,
|
|
802
894
|
}
|
|
803
895
|
|
|
804
|
-
const
|
|
896
|
+
const payload = {
|
|
805
897
|
interactionMode,
|
|
806
898
|
person: {
|
|
807
899
|
id: person.id,
|
|
@@ -1011,7 +1103,20 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
1011
1103
|
name: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.name ?? null : null,
|
|
1012
1104
|
email: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.email ?? auth.email ?? null : auth.email ?? null,
|
|
1013
1105
|
},
|
|
1014
|
-
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (personDetailCache && personDetailCacheKey) {
|
|
1109
|
+
try {
|
|
1110
|
+
await runWithCacheTenant(personDetailTenantId, () =>
|
|
1111
|
+
personDetailCache.set(personDetailCacheKey, payload, {
|
|
1112
|
+
ttl: PERSON_DETAIL_TTL_MS,
|
|
1113
|
+
tags: personDetailCacheTags,
|
|
1114
|
+
}),
|
|
1115
|
+
)
|
|
1116
|
+
} catch {}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const response = NextResponse.json(payload)
|
|
1015
1120
|
statusCode = 200
|
|
1016
1121
|
profileMeta = {
|
|
1017
1122
|
include: Array.from(includeTokens),
|