@open-mercato/core 0.4.5-develop-0f0e676c72 → 0.4.5-develop-e694581d9f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/customer_deal/index.js +4 -0
- package/dist/generated/entities/customer_deal/index.js.map +2 -2
- package/dist/generated/entities/customer_pipeline/index.js +17 -0
- package/dist/generated/entities/customer_pipeline/index.js.map +7 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js +19 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +4 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/acl.js +2 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +4 -0
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +12 -0
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -1
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +69 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +7 -0
- package/dist/modules/customers/api/pipeline-stages/route.js +275 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +7 -0
- package/dist/modules/customers/api/pipelines/route.js +245 -0
- package/dist/modules/customers/api/pipelines/route.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/page.js +2 -0
- package/dist/modules/customers/backend/config/customers/page.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +439 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +17 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +35 -1
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +102 -74
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/cli.js +28 -2
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +34 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/index.js +2 -0
- package/dist/modules/customers/commands/index.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +126 -0
- package/dist/modules/customers/commands/pipeline-stages.js.map +7 -0
- package/dist/modules/customers/commands/pipelines.js +87 -0
- package/dist/modules/customers/commands/pipelines.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +0 -5
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +474 -0
- package/dist/modules/customers/components/PipelineSettings.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +84 -12
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/data/entities.js +78 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +44 -0
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260218191730.js +77 -0
- package/dist/modules/customers/migrations/Migration20260218191730.js.map +7 -0
- package/dist/modules/customers/setup.js +7 -3
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +46 -44
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/api/context.js +10 -1
- package/dist/modules/translations/api/context.js.map +2 -2
- package/dist/modules/translations/commands/index.js +2 -0
- package/dist/modules/translations/commands/index.js.map +7 -0
- package/dist/modules/translations/commands/translations.js +160 -0
- package/dist/modules/translations/commands/translations.js.map +7 -0
- package/dist/modules/translations/index.js +1 -0
- package/dist/modules/translations/index.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260222205305.js +14 -0
- package/dist/modules/workflows/migrations/Migration20260222205305.js.map +7 -0
- package/generated/entities/customer_deal/index.ts +2 -0
- package/generated/entities/customer_pipeline/index.ts +7 -0
- package/generated/entities/customer_pipeline_stage/index.ts +8 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +4 -0
- package/package.json +2 -2
- package/src/modules/customers/acl.ts +2 -0
- package/src/modules/customers/api/deals/[id]/route.ts +4 -0
- package/src/modules/customers/api/deals/route.ts +12 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +21 -1
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +71 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +296 -0
- package/src/modules/customers/api/pipelines/route.ts +261 -0
- package/src/modules/customers/backend/config/customers/page.tsx +2 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +13 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +512 -0
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/deals/page.tsx +33 -1
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +119 -79
- package/src/modules/customers/cli.ts +29 -1
- package/src/modules/customers/commands/deals.ts +44 -1
- package/src/modules/customers/commands/index.ts +2 -0
- package/src/modules/customers/commands/pipeline-stages.ts +156 -0
- package/src/modules/customers/commands/pipelines.ts +105 -0
- package/src/modules/customers/components/DictionarySettings.tsx +0 -5
- package/src/modules/customers/components/PipelineSettings.tsx +570 -0
- package/src/modules/customers/components/detail/DealForm.tsx +89 -11
- package/src/modules/customers/data/entities.ts +64 -0
- package/src/modules/customers/data/validators.ts +57 -0
- package/src/modules/customers/i18n/de.json +4 -0
- package/src/modules/customers/i18n/en.json +4 -0
- package/src/modules/customers/i18n/es.json +4 -0
- package/src/modules/customers/i18n/pl.json +5 -1
- package/src/modules/customers/migrations/Migration20260218191730.ts +84 -0
- package/src/modules/customers/setup.ts +5 -1
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -60
- package/src/modules/translations/api/context.ts +12 -0
- package/src/modules/translations/commands/index.ts +1 -0
- package/src/modules/translations/commands/translations.ts +253 -0
- package/src/modules/translations/index.ts +1 -0
- package/src/modules/workflows/migrations/Migration20260222205305.ts +13 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/api/deals/route.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CustomerDeal, CustomerDealPersonLink, CustomerDealCompanyLink } from '../../data/entities'\nimport { dealCreateSchema, dealUpdateSchema } from '../../data/validators'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '../utils'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n createCustomersCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n status: z.string().optional(),\n pipelineStage: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n personEntityId: z.string().uuid().optional(),\n companyEntityId: z.string().uuid().optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n}\n\nexport const metadata = routeMetadata\n\ntype DealListQuery = z.infer<typeof listSchema>\n\nfunction parseUuid(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const result = z.string().uuid().safeParse(trimmed)\n return result.success ? trimmed : null\n}\n\nfunction normalizeUuidList(values: Array<unknown>): string[] {\n const set = new Set<string>()\n values.forEach((candidate) => {\n if (Array.isArray(candidate)) {\n candidate.forEach((entry) => {\n const parsed = parseUuid(entry)\n if (parsed) set.add(parsed)\n })\n return\n }\n if (typeof candidate === 'string' && candidate.includes(',')) {\n candidate\n .split(',')\n .map((entry) => entry.trim())\n .forEach((entry) => {\n const parsed = parseUuid(entry)\n if (parsed) set.add(parsed)\n })\n return\n }\n const parsed = parseUuid(candidate)\n if (parsed) set.add(parsed)\n })\n return Array.from(set)\n}\n\nconst crud = makeCrudRoute<unknown, unknown, DealListQuery>({\n metadata: routeMetadata,\n orm: {\n entity: CustomerDeal,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.customers.customer_deal,\n },\n list: {\n schema: listSchema,\n entityId: E.customers.customer_deal,\n fields: [\n 'id',\n 'title',\n 'description',\n 'status',\n 'pipeline_stage',\n 'value_amount',\n 'value_currency',\n 'probability',\n 'expected_close_at',\n 'owner_user_id',\n 'source',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n decorateCustomFields: {\n entityIds: E.customers.customer_deal,\n },\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n title: 'title',\n value: 'value_amount',\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.search) {\n filters.title = { $ilike: `%${escapeLikePattern(query.search)}%` }\n }\n if (query.status) {\n filters.status = { $eq: query.status }\n }\n if (query.pipelineStage) {\n filters.pipeline_stage = { $eq: query.pipelineStage }\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'customers.deals.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(dealCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.dealId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'customers.deals.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(dealUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'customers.deals.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id =\n parsed?.body?.id ??\n parsed?.id ??\n parsed?.query?.id ??\n (ctx.request ? new URL(ctx.request.url).searchParams.get('id') : null)\n if (!id) throw new CrudHttpError(400, { error: translate('customers.errors.deal_required', 'Deal id is required') })\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n beforeList: (query, ctx) => {\n const url = ctx.request ? new URL(ctx.request.url) : null\n const legacyPersonId = query.personEntityId ?? null\n const legacyCompanyId = query.companyEntityId ?? null\n const allPersonCandidates: unknown[] = []\n const allCompanyCandidates: unknown[] = []\n if (legacyPersonId) allPersonCandidates.push(legacyPersonId)\n if (legacyCompanyId) allCompanyCandidates.push(legacyCompanyId)\n if (url) {\n const personParams = url.searchParams.getAll('personId')\n const companyParams = url.searchParams.getAll('companyId')\n if (personParams.length) allPersonCandidates.push(...personParams)\n if (companyParams.length) allCompanyCandidates.push(...companyParams)\n const legacyRepeatPerson = url.searchParams.getAll('personEntityId')\n const legacyRepeatCompany = url.searchParams.getAll('companyEntityId')\n if (legacyRepeatPerson.length) allPersonCandidates.push(...legacyRepeatPerson)\n if (legacyRepeatCompany.length) allCompanyCandidates.push(...legacyRepeatCompany)\n }\n const personIds = normalizeUuidList(allPersonCandidates)\n const companyIds = normalizeUuidList(allCompanyCandidates)\n ;(ctx as any).__dealsFilters = {\n personIds,\n companyIds,\n }\n },\n afterList: async (payload, ctx) => {\n const filters = ((ctx as any).__dealsFilters || {}) as {\n personIds?: string[]\n companyIds?: string[]\n }\n const selectedPersonIds = Array.isArray(filters.personIds) ? filters.personIds : []\n const selectedCompanyIds = Array.isArray(filters.companyIds) ? filters.companyIds : []\n\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const scopeSource = (items[0] ?? {}) as Record<string, unknown>\n const fallbackTenantId =\n (typeof scopeSource.tenantId === 'string' && scopeSource.tenantId.trim().length\n ? scopeSource.tenantId\n : typeof (scopeSource as any).tenant_id === 'string' && (scopeSource as any).tenant_id.trim().length\n ? (scopeSource as any).tenant_id\n : null) ?? (ctx as any)?.auth?.tenantId ?? null\n const fallbackOrganizationId =\n (typeof scopeSource.organizationId === 'string' && scopeSource.organizationId.trim().length\n ? scopeSource.organizationId\n : typeof (scopeSource as any).organization_id === 'string' &&\n (scopeSource as any).organization_id.trim().length\n ? (scopeSource as any).organization_id\n : null) ?? (ctx as any)?.auth?.orgId ?? null\n const ids = items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const candidate = (item as Record<string, unknown>).id\n return typeof candidate === 'string' && candidate.trim().length ? candidate : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n if (!ids.length) {\n payload.items = []\n payload.total = 0\n return\n }\n try {\n const em = (ctx.container.resolve('em') as EntityManager)\n const [allPersonLinks, allCompanyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: { $in: ids } },\n { populate: ['person'] },\n { tenantId: fallbackTenantId, organizationId: fallbackOrganizationId },\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: { $in: ids } },\n { populate: ['company'] },\n { tenantId: fallbackTenantId, organizationId: fallbackOrganizationId },\n ),\n ])\n\n const personAssignments = new Map<string, { id: string; label: string }[]>()\n const personMemberships = new Map<string, Set<string>>()\n allPersonLinks.forEach((link) => {\n const deal = link.deal\n const dealId =\n typeof deal === 'string'\n ? deal\n : deal && typeof deal === 'object' && 'id' in deal && typeof (deal as any).id === 'string'\n ? (deal as any).id\n : null\n if (!dealId) return\n const personRef = link.person\n const personId =\n typeof personRef === 'string'\n ? personRef\n : personRef && typeof personRef === 'object' && 'id' in personRef && typeof (personRef as any).id === 'string'\n ? (personRef as any).id\n : null\n if (!personId) return\n const label =\n personRef && typeof personRef === 'object' && 'displayName' in personRef && typeof (personRef as any).displayName === 'string'\n ? (personRef as any).displayName\n : ''\n const bucket = personAssignments.get(dealId) ?? []\n if (!bucket.some((entry) => entry.id === personId)) {\n bucket.push({ id: personId, label })\n personAssignments.set(dealId, bucket)\n }\n const membership = personMemberships.get(dealId) ?? new Set<string>()\n membership.add(personId)\n personMemberships.set(dealId, membership)\n })\n\n const companyAssignments = new Map<string, { id: string; label: string }[]>()\n const companyMemberships = new Map<string, Set<string>>()\n allCompanyLinks.forEach((link) => {\n const deal = link.deal\n const dealId =\n typeof deal === 'string'\n ? deal\n : deal && typeof deal === 'object' && 'id' in deal && typeof (deal as any).id === 'string'\n ? (deal as any).id\n : null\n if (!dealId) return\n const companyRef = link.company\n const companyId =\n typeof companyRef === 'string'\n ? companyRef\n : companyRef && typeof companyRef === 'object' && 'id' in companyRef && typeof (companyRef as any).id === 'string'\n ? (companyRef as any).id\n : null\n if (!companyId) return\n const label =\n companyRef && typeof companyRef === 'object' && 'displayName' in companyRef && typeof (companyRef as any).displayName === 'string'\n ? (companyRef as any).displayName\n : ''\n const bucket = companyAssignments.get(dealId) ?? []\n if (!bucket.some((entry) => entry.id === companyId)) {\n bucket.push({ id: companyId, label })\n companyAssignments.set(dealId, bucket)\n }\n const membership = companyMemberships.get(dealId) ?? new Set<string>()\n membership.add(companyId)\n companyMemberships.set(dealId, membership)\n })\n\n const hasPersonFilter = selectedPersonIds.length > 0\n const hasCompanyFilter = selectedCompanyIds.length > 0\n const matchesAll = (selected: string[], memberships: Map<string, Set<string>>, dealId: string) => {\n if (!selected.length) return true\n const membership = memberships.get(dealId)\n if (!membership || membership.size === 0) return false\n return selected.every((id) => membership.has(id))\n }\n\n const enhancedItems = items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const data = item as Record<string, unknown>\n const candidate = typeof data.id === 'string' ? data.id : null\n if (!candidate || !candidate.trim().length) return null\n const people = personAssignments.get(candidate) ?? []\n const companies = companyAssignments.get(candidate) ?? []\n const matchesPerson = matchesAll(selectedPersonIds, personMemberships, candidate)\n const matchesCompany = matchesAll(selectedCompanyIds, companyMemberships, candidate)\n if (!matchesPerson || !matchesCompany) return null\n const tenantIdRaw =\n typeof data.tenantId === 'string'\n ? data.tenantId\n : typeof data.tenant_id === 'string'\n ? data.tenant_id\n : null\n const organizationIdRaw =\n typeof data.organizationId === 'string'\n ? data.organizationId\n : typeof data.organization_id === 'string'\n ? data.organization_id\n : null\n const tenantId = tenantIdRaw && tenantIdRaw.trim().length ? tenantIdRaw.trim() : null\n const organizationId = organizationIdRaw && organizationIdRaw.trim().length ? organizationIdRaw.trim() : null\n return {\n ...data,\n personIds: people.map((entry) => entry.id),\n people,\n companyIds: companies.map((entry) => entry.id),\n companies,\n tenantId,\n organizationId,\n }\n })\n .filter(\n (item: Record<string, unknown> | null): item is Record<string, unknown> => item !== null,\n )\n\n payload.items = enhancedItems\n if (hasPersonFilter || hasCompanyFilter) {\n payload.total = enhancedItems.length\n payload.totalPages = 1\n payload.page = 1\n }\n } catch (err) {\n console.warn('[customers.deals] failed to filter by person/company link', err)\n // fall back to unfiltered list to avoid breaking the endpoint\n }\n },\n },\n})\n\nconst { POST, PUT, DELETE } = crud\n\nexport { POST, PUT, DELETE }\nexport const GET = crud.GET\n\nconst dealAssociationSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n})\n\nconst dealListItemSchema = z\n .object({\n id: z.string().uuid(),\n title: z.string().nullable(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipeline_stage: z.string().nullable().optional(),\n value_amount: z.number().nullable().optional(),\n value_currency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expected_close_at: z.string().nullable().optional(),\n owner_user_id: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n personIds: z.array(z.string().uuid()).optional(),\n people: z.array(dealAssociationSchema).optional(),\n companyIds: z.array(z.string().uuid()).optional(),\n companies: z.array(dealAssociationSchema).optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n })\n .passthrough()\n\nconst dealCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nexport const openApi = createCustomersCrudOpenApi({\n resourceName: 'Deal',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(dealListItemSchema),\n create: {\n schema: dealCreateSchema,\n responseSchema: dealCreateResponseSchema,\n description: 'Creates a sales deal, optionally associating people and companies.',\n },\n update: {\n schema: dealUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates pipeline position, metadata, or associations for an existing deal.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a deal by `id`. The identifier may be provided in the body or query parameters.',\n },\n})\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CustomerDeal, CustomerDealPersonLink, CustomerDealCompanyLink } from '../../data/entities'\nimport { dealCreateSchema, dealUpdateSchema } from '../../data/validators'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '../utils'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n createCustomersCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n status: z.string().optional(),\n pipelineStage: z.string().optional(),\n pipelineId: z.string().uuid().optional(),\n pipelineStageId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n personEntityId: z.string().uuid().optional(),\n companyEntityId: z.string().uuid().optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n}\n\nexport const metadata = routeMetadata\n\ntype DealListQuery = z.infer<typeof listSchema>\n\nfunction parseUuid(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const result = z.string().uuid().safeParse(trimmed)\n return result.success ? trimmed : null\n}\n\nfunction normalizeUuidList(values: Array<unknown>): string[] {\n const set = new Set<string>()\n values.forEach((candidate) => {\n if (Array.isArray(candidate)) {\n candidate.forEach((entry) => {\n const parsed = parseUuid(entry)\n if (parsed) set.add(parsed)\n })\n return\n }\n if (typeof candidate === 'string' && candidate.includes(',')) {\n candidate\n .split(',')\n .map((entry) => entry.trim())\n .forEach((entry) => {\n const parsed = parseUuid(entry)\n if (parsed) set.add(parsed)\n })\n return\n }\n const parsed = parseUuid(candidate)\n if (parsed) set.add(parsed)\n })\n return Array.from(set)\n}\n\nconst crud = makeCrudRoute<unknown, unknown, DealListQuery>({\n metadata: routeMetadata,\n orm: {\n entity: CustomerDeal,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.customers.customer_deal,\n },\n list: {\n schema: listSchema,\n entityId: E.customers.customer_deal,\n fields: [\n 'id',\n 'title',\n 'description',\n 'status',\n 'pipeline_stage',\n 'pipeline_id',\n 'pipeline_stage_id',\n 'value_amount',\n 'value_currency',\n 'probability',\n 'expected_close_at',\n 'owner_user_id',\n 'source',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n decorateCustomFields: {\n entityIds: E.customers.customer_deal,\n },\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n title: 'title',\n value: 'value_amount',\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.search) {\n filters.title = { $ilike: `%${escapeLikePattern(query.search)}%` }\n }\n if (query.status) {\n filters.status = { $eq: query.status }\n }\n if (query.pipelineStage) {\n filters.pipeline_stage = { $eq: query.pipelineStage }\n }\n if (query.pipelineId) {\n filters.pipeline_id = { $eq: query.pipelineId }\n }\n if (query.pipelineStageId) {\n filters.pipeline_stage_id = { $eq: query.pipelineStageId }\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'customers.deals.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(dealCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.dealId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'customers.deals.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(dealUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'customers.deals.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id =\n parsed?.body?.id ??\n parsed?.id ??\n parsed?.query?.id ??\n (ctx.request ? new URL(ctx.request.url).searchParams.get('id') : null)\n if (!id) throw new CrudHttpError(400, { error: translate('customers.errors.deal_required', 'Deal id is required') })\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n beforeList: (query, ctx) => {\n const url = ctx.request ? new URL(ctx.request.url) : null\n const legacyPersonId = query.personEntityId ?? null\n const legacyCompanyId = query.companyEntityId ?? null\n const allPersonCandidates: unknown[] = []\n const allCompanyCandidates: unknown[] = []\n if (legacyPersonId) allPersonCandidates.push(legacyPersonId)\n if (legacyCompanyId) allCompanyCandidates.push(legacyCompanyId)\n if (url) {\n const personParams = url.searchParams.getAll('personId')\n const companyParams = url.searchParams.getAll('companyId')\n if (personParams.length) allPersonCandidates.push(...personParams)\n if (companyParams.length) allCompanyCandidates.push(...companyParams)\n const legacyRepeatPerson = url.searchParams.getAll('personEntityId')\n const legacyRepeatCompany = url.searchParams.getAll('companyEntityId')\n if (legacyRepeatPerson.length) allPersonCandidates.push(...legacyRepeatPerson)\n if (legacyRepeatCompany.length) allCompanyCandidates.push(...legacyRepeatCompany)\n }\n const personIds = normalizeUuidList(allPersonCandidates)\n const companyIds = normalizeUuidList(allCompanyCandidates)\n ;(ctx as any).__dealsFilters = {\n personIds,\n companyIds,\n }\n },\n afterList: async (payload, ctx) => {\n const filters = ((ctx as any).__dealsFilters || {}) as {\n personIds?: string[]\n companyIds?: string[]\n }\n const selectedPersonIds = Array.isArray(filters.personIds) ? filters.personIds : []\n const selectedCompanyIds = Array.isArray(filters.companyIds) ? filters.companyIds : []\n\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const scopeSource = (items[0] ?? {}) as Record<string, unknown>\n const fallbackTenantId =\n (typeof scopeSource.tenantId === 'string' && scopeSource.tenantId.trim().length\n ? scopeSource.tenantId\n : typeof (scopeSource as any).tenant_id === 'string' && (scopeSource as any).tenant_id.trim().length\n ? (scopeSource as any).tenant_id\n : null) ?? (ctx as any)?.auth?.tenantId ?? null\n const fallbackOrganizationId =\n (typeof scopeSource.organizationId === 'string' && scopeSource.organizationId.trim().length\n ? scopeSource.organizationId\n : typeof (scopeSource as any).organization_id === 'string' &&\n (scopeSource as any).organization_id.trim().length\n ? (scopeSource as any).organization_id\n : null) ?? (ctx as any)?.auth?.orgId ?? null\n const ids = items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const candidate = (item as Record<string, unknown>).id\n return typeof candidate === 'string' && candidate.trim().length ? candidate : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n if (!ids.length) {\n payload.items = []\n payload.total = 0\n return\n }\n try {\n const em = (ctx.container.resolve('em') as EntityManager)\n const [allPersonLinks, allCompanyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: { $in: ids } },\n { populate: ['person'] },\n { tenantId: fallbackTenantId, organizationId: fallbackOrganizationId },\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: { $in: ids } },\n { populate: ['company'] },\n { tenantId: fallbackTenantId, organizationId: fallbackOrganizationId },\n ),\n ])\n\n const personAssignments = new Map<string, { id: string; label: string }[]>()\n const personMemberships = new Map<string, Set<string>>()\n allPersonLinks.forEach((link) => {\n const deal = link.deal\n const dealId =\n typeof deal === 'string'\n ? deal\n : deal && typeof deal === 'object' && 'id' in deal && typeof (deal as any).id === 'string'\n ? (deal as any).id\n : null\n if (!dealId) return\n const personRef = link.person\n const personId =\n typeof personRef === 'string'\n ? personRef\n : personRef && typeof personRef === 'object' && 'id' in personRef && typeof (personRef as any).id === 'string'\n ? (personRef as any).id\n : null\n if (!personId) return\n const label =\n personRef && typeof personRef === 'object' && 'displayName' in personRef && typeof (personRef as any).displayName === 'string'\n ? (personRef as any).displayName\n : ''\n const bucket = personAssignments.get(dealId) ?? []\n if (!bucket.some((entry) => entry.id === personId)) {\n bucket.push({ id: personId, label })\n personAssignments.set(dealId, bucket)\n }\n const membership = personMemberships.get(dealId) ?? new Set<string>()\n membership.add(personId)\n personMemberships.set(dealId, membership)\n })\n\n const companyAssignments = new Map<string, { id: string; label: string }[]>()\n const companyMemberships = new Map<string, Set<string>>()\n allCompanyLinks.forEach((link) => {\n const deal = link.deal\n const dealId =\n typeof deal === 'string'\n ? deal\n : deal && typeof deal === 'object' && 'id' in deal && typeof (deal as any).id === 'string'\n ? (deal as any).id\n : null\n if (!dealId) return\n const companyRef = link.company\n const companyId =\n typeof companyRef === 'string'\n ? companyRef\n : companyRef && typeof companyRef === 'object' && 'id' in companyRef && typeof (companyRef as any).id === 'string'\n ? (companyRef as any).id\n : null\n if (!companyId) return\n const label =\n companyRef && typeof companyRef === 'object' && 'displayName' in companyRef && typeof (companyRef as any).displayName === 'string'\n ? (companyRef as any).displayName\n : ''\n const bucket = companyAssignments.get(dealId) ?? []\n if (!bucket.some((entry) => entry.id === companyId)) {\n bucket.push({ id: companyId, label })\n companyAssignments.set(dealId, bucket)\n }\n const membership = companyMemberships.get(dealId) ?? new Set<string>()\n membership.add(companyId)\n companyMemberships.set(dealId, membership)\n })\n\n const hasPersonFilter = selectedPersonIds.length > 0\n const hasCompanyFilter = selectedCompanyIds.length > 0\n const matchesAll = (selected: string[], memberships: Map<string, Set<string>>, dealId: string) => {\n if (!selected.length) return true\n const membership = memberships.get(dealId)\n if (!membership || membership.size === 0) return false\n return selected.every((id) => membership.has(id))\n }\n\n const enhancedItems = items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const data = item as Record<string, unknown>\n const candidate = typeof data.id === 'string' ? data.id : null\n if (!candidate || !candidate.trim().length) return null\n const people = personAssignments.get(candidate) ?? []\n const companies = companyAssignments.get(candidate) ?? []\n const matchesPerson = matchesAll(selectedPersonIds, personMemberships, candidate)\n const matchesCompany = matchesAll(selectedCompanyIds, companyMemberships, candidate)\n if (!matchesPerson || !matchesCompany) return null\n const tenantIdRaw =\n typeof data.tenantId === 'string'\n ? data.tenantId\n : typeof data.tenant_id === 'string'\n ? data.tenant_id\n : null\n const organizationIdRaw =\n typeof data.organizationId === 'string'\n ? data.organizationId\n : typeof data.organization_id === 'string'\n ? data.organization_id\n : null\n const tenantId = tenantIdRaw && tenantIdRaw.trim().length ? tenantIdRaw.trim() : null\n const organizationId = organizationIdRaw && organizationIdRaw.trim().length ? organizationIdRaw.trim() : null\n return {\n ...data,\n personIds: people.map((entry) => entry.id),\n people,\n companyIds: companies.map((entry) => entry.id),\n companies,\n tenantId,\n organizationId,\n }\n })\n .filter(\n (item: Record<string, unknown> | null): item is Record<string, unknown> => item !== null,\n )\n\n payload.items = enhancedItems\n if (hasPersonFilter || hasCompanyFilter) {\n payload.total = enhancedItems.length\n payload.totalPages = 1\n payload.page = 1\n }\n } catch (err) {\n console.warn('[customers.deals] failed to filter by person/company link', err)\n // fall back to unfiltered list to avoid breaking the endpoint\n }\n },\n },\n})\n\nconst { POST, PUT, DELETE } = crud\n\nexport { POST, PUT, DELETE }\nexport const GET = crud.GET\n\nconst dealAssociationSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n})\n\nconst dealListItemSchema = z\n .object({\n id: z.string().uuid(),\n title: z.string().nullable(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipeline_stage: z.string().nullable().optional(),\n pipeline_id: z.string().uuid().nullable().optional(),\n pipeline_stage_id: z.string().uuid().nullable().optional(),\n value_amount: z.number().nullable().optional(),\n value_currency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expected_close_at: z.string().nullable().optional(),\n owner_user_id: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n personIds: z.array(z.string().uuid()).optional(),\n people: z.array(dealAssociationSchema).optional(),\n companyIds: z.array(z.string().uuid()).optional(),\n companies: z.array(dealAssociationSchema).optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n })\n .passthrough()\n\nconst dealCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nexport const openApi = createCustomersCrudOpenApi({\n resourceName: 'Deal',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(dealListItemSchema),\n create: {\n schema: dealCreateSchema,\n responseSchema: dealCreateResponseSchema,\n description: 'Creates a sales deal, optionally associating people and companies.',\n },\n update: {\n schema: dealUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates pipeline position, metadata, or associations for an existing deal.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a deal by `id`. The identifier may be provided in the body or query parameters.',\n },\n})\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,cAAc,wBAAwB,+BAA+B;AAC9E,SAAS,kBAAkB,wBAAwB;AACnD,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAElC,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,aAAa,EAChB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC5C,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC1C,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAC9C,CAAC,EACA,YAAY;AAEf,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AAAA,EACpE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAAA,EACvE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAAA,EACtE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAC3E;AAEO,MAAM,WAAW;AAIxB,SAAS,UAAU,OAA+B;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,QAAM,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,OAAO;AAClD,SAAO,OAAO,UAAU,UAAU;AACpC;AAEA,SAAS,kBAAkB,QAAkC;AAC3D,QAAM,MAAM,oBAAI,IAAY;AAC5B,SAAO,QAAQ,CAAC,cAAc;AAC5B,QAAI,MAAM,QAAQ,SAAS,GAAG;AAC5B,gBAAU,QAAQ,CAAC,UAAU;AAC3B,cAAMA,UAAS,UAAU,KAAK;AAC9B,YAAIA,QAAQ,KAAI,IAAIA,OAAM;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AACA,QAAI,OAAO,cAAc,YAAY,UAAU,SAAS,GAAG,GAAG;AAC5D,gBACG,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,QAAQ,CAAC,UAAU;AAClB,cAAMA,UAAS,UAAU,KAAK;AAC9B,YAAIA,QAAQ,KAAI,IAAIA,OAAM;AAAA,MAC5B,CAAC;AACH;AAAA,IACF;AACA,UAAM,SAAS,UAAU,SAAS;AAClC,QAAI,OAAQ,KAAI,IAAI,MAAM;AAAA,EAC5B,CAAC;AACD,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,MAAM,OAAO,cAA+C;AAAA,EAC1D,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,IACP,YAAY,EAAE,UAAU;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,UAAU,EAAE,UAAU;AAAA,IACtB,QAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,sBAAsB;AAAA,MACpB,WAAW,EAAE,UAAU;AAAA,IACzB;AAAA,IACA,cAAc;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,MACX,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,IACA,cAAc,OAAO,UAAe;AAClC,YAAM,UAA+B,CAAC;AACtC,UAAI,MAAM,QAAQ;AAChB,gBAAQ,QAAQ,EAAE,QAAQ,IAAI,kBAAkB,MAAM,MAAM,CAAC,IAAI;AAAA,MACnE;AACA,UAAI,MAAM,QAAQ;AAChB,gBAAQ,SAAS,EAAE,KAAK,MAAM,OAAO;AAAA,MACvC;AACA,UAAI,MAAM,eAAe;AACvB,gBAAQ,iBAAiB,EAAE,KAAK,MAAM,cAAc;AAAA,MACtD;AACA,UAAI,MAAM,YAAY;AACpB,gBAAQ,cAAc,EAAE,KAAK,MAAM,WAAW;AAAA,MAChD;AACA,UAAI,MAAM,iBAAiB;AACzB,gBAAQ,oBAAoB,EAAE,KAAK,MAAM,gBAAgB;AAAA,MAC3D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,eAAO,wBAAwB,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAAA,MAC5E;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO,EAAE,IAAI,QAAQ,UAAU,QAAQ,MAAM,KAAK;AAAA,MACtE,QAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,eAAO,wBAAwB,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAAA,MAC5E;AAAA,MACA,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,KACJ,QAAQ,MAAM,MACd,QAAQ,MACR,QAAQ,OAAO,OACd,IAAI,UAAU,IAAI,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,IAAI,IAAI;AACnE,YAAI,CAAC,GAAI,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,kCAAkC,qBAAqB,EAAE,CAAC;AACnH,eAAO,EAAE,GAAG;AAAA,MACd;AAAA,MACA,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,YAAY,CAAC,OAAO,QAAQ;AAC1B,YAAM,MAAM,IAAI,UAAU,IAAI,IAAI,IAAI,QAAQ,GAAG,IAAI;AACrD,YAAM,iBAAiB,MAAM,kBAAkB;AAC/C,YAAM,kBAAkB,MAAM,mBAAmB;AACjD,YAAM,sBAAiC,CAAC;AACxC,YAAM,uBAAkC,CAAC;AACzC,UAAI,eAAgB,qBAAoB,KAAK,cAAc;AAC3D,UAAI,gBAAiB,sBAAqB,KAAK,eAAe;AAC9D,UAAI,KAAK;AACP,cAAM,eAAe,IAAI,aAAa,OAAO,UAAU;AACvD,cAAM,gBAAgB,IAAI,aAAa,OAAO,WAAW;AACzD,YAAI,aAAa,OAAQ,qBAAoB,KAAK,GAAG,YAAY;AACjE,YAAI,cAAc,OAAQ,sBAAqB,KAAK,GAAG,aAAa;AACpE,cAAM,qBAAqB,IAAI,aAAa,OAAO,gBAAgB;AACnE,cAAM,sBAAsB,IAAI,aAAa,OAAO,iBAAiB;AACrE,YAAI,mBAAmB,OAAQ,qBAAoB,KAAK,GAAG,kBAAkB;AAC7E,YAAI,oBAAoB,OAAQ,sBAAqB,KAAK,GAAG,mBAAmB;AAAA,MAClF;AACA,YAAM,YAAY,kBAAkB,mBAAmB;AACvD,YAAM,aAAa,kBAAkB,oBAAoB;AACxD,MAAC,IAAY,iBAAiB;AAAA,QAC7B;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW,OAAO,SAAS,QAAQ;AACjC,YAAM,UAAY,IAAY,kBAAkB,CAAC;AAIjD,YAAM,oBAAoB,MAAM,QAAQ,QAAQ,SAAS,IAAI,QAAQ,YAAY,CAAC;AAClF,YAAM,qBAAqB,MAAM,QAAQ,QAAQ,UAAU,IAAI,QAAQ,aAAa,CAAC;AAErF,YAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC9D,UAAI,CAAC,MAAM,OAAQ;AACnB,YAAM,cAAe,MAAM,CAAC,KAAK,CAAC;AAClC,YAAM,oBACH,OAAO,YAAY,aAAa,YAAY,YAAY,SAAS,KAAK,EAAE,SACrE,YAAY,WACZ,OAAQ,YAAoB,cAAc,YAAa,YAAoB,UAAU,KAAK,EAAE,SACzF,YAAoB,YACrB,SAAU,KAAa,MAAM,YAAY;AACjD,YAAM,0BACH,OAAO,YAAY,mBAAmB,YAAY,YAAY,eAAe,KAAK,EAAE,SACjF,YAAY,iBACZ,OAAQ,YAAoB,oBAAoB,YAC7C,YAAoB,gBAAgB,KAAK,EAAE,SAC3C,YAAoB,kBACrB,SAAU,KAAa,MAAM,SAAS;AAC9C,YAAM,MAAM,MACT,IAAI,CAAC,SAAkB;AACtB,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,cAAM,YAAa,KAAiC;AACpD,eAAO,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,YAAY;AAAA,MAChF,CAAC,EACA,OAAO,CAAC,UAA0C,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAClG,UAAI,CAAC,IAAI,QAAQ;AACf,gBAAQ,QAAQ,CAAC;AACjB,gBAAQ,QAAQ;AAChB;AAAA,MACF;AACA,UAAI;AACF,cAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,cAAM,CAAC,gBAAgB,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC1D;AAAA,YACE;AAAA,YACA;AAAA,YACA,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE;AAAA,YACrB,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,YACvB,EAAE,UAAU,kBAAkB,gBAAgB,uBAAuB;AAAA,UACvE;AAAA,UACA;AAAA,YACE;AAAA,YACA;AAAA,YACA,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE;AAAA,YACrB,EAAE,UAAU,CAAC,SAAS,EAAE;AAAA,YACxB,EAAE,UAAU,kBAAkB,gBAAgB,uBAAuB;AAAA,UACvE;AAAA,QACF,CAAC;AAED,cAAM,oBAAoB,oBAAI,IAA6C;AAC3E,cAAM,oBAAoB,oBAAI,IAAyB;AACvD,uBAAe,QAAQ,CAAC,SAAS;AAC/B,gBAAM,OAAO,KAAK;AAClB,gBAAM,SACJ,OAAO,SAAS,WACZ,OACA,QAAQ,OAAO,SAAS,YAAY,QAAQ,QAAQ,OAAQ,KAAa,OAAO,WAC7E,KAAa,KACd;AACR,cAAI,CAAC,OAAQ;AACb,gBAAM,YAAY,KAAK;AACvB,gBAAM,WACJ,OAAO,cAAc,WACjB,YACA,aAAa,OAAO,cAAc,YAAY,QAAQ,aAAa,OAAQ,UAAkB,OAAO,WACjG,UAAkB,KACnB;AACR,cAAI,CAAC,SAAU;AACf,gBAAM,QACJ,aAAa,OAAO,cAAc,YAAY,iBAAiB,aAAa,OAAQ,UAAkB,gBAAgB,WACjH,UAAkB,cACnB;AACN,gBAAM,SAAS,kBAAkB,IAAI,MAAM,KAAK,CAAC;AACjD,cAAI,CAAC,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,QAAQ,GAAG;AAClD,mBAAO,KAAK,EAAE,IAAI,UAAU,MAAM,CAAC;AACnC,8BAAkB,IAAI,QAAQ,MAAM;AAAA,UACtC;AACA,gBAAM,aAAa,kBAAkB,IAAI,MAAM,KAAK,oBAAI,IAAY;AACpE,qBAAW,IAAI,QAAQ;AACvB,4BAAkB,IAAI,QAAQ,UAAU;AAAA,QAC1C,CAAC;AAED,cAAM,qBAAqB,oBAAI,IAA6C;AAC5E,cAAM,qBAAqB,oBAAI,IAAyB;AACxD,wBAAgB,QAAQ,CAAC,SAAS;AAChC,gBAAM,OAAO,KAAK;AAClB,gBAAM,SACJ,OAAO,SAAS,WACZ,OACA,QAAQ,OAAO,SAAS,YAAY,QAAQ,QAAQ,OAAQ,KAAa,OAAO,WAC7E,KAAa,KACd;AACR,cAAI,CAAC,OAAQ;AACb,gBAAM,aAAa,KAAK;AACxB,gBAAM,YACJ,OAAO,eAAe,WAClB,aACA,cAAc,OAAO,eAAe,YAAY,QAAQ,cAAc,OAAQ,WAAmB,OAAO,WACrG,WAAmB,KACpB;AACR,cAAI,CAAC,UAAW;AAChB,gBAAM,QACJ,cAAc,OAAO,eAAe,YAAY,iBAAiB,cAAc,OAAQ,WAAmB,gBAAgB,WACrH,WAAmB,cACpB;AACN,gBAAM,SAAS,mBAAmB,IAAI,MAAM,KAAK,CAAC;AAClD,cAAI,CAAC,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,SAAS,GAAG;AACnD,mBAAO,KAAK,EAAE,IAAI,WAAW,MAAM,CAAC;AACpC,+BAAmB,IAAI,QAAQ,MAAM;AAAA,UACvC;AACA,gBAAM,aAAa,mBAAmB,IAAI,MAAM,KAAK,oBAAI,IAAY;AACrE,qBAAW,IAAI,SAAS;AACxB,6BAAmB,IAAI,QAAQ,UAAU;AAAA,QAC3C,CAAC;AAED,cAAM,kBAAkB,kBAAkB,SAAS;AACnD,cAAM,mBAAmB,mBAAmB,SAAS;AACrD,cAAM,aAAa,CAAC,UAAoB,aAAuC,WAAmB;AAChG,cAAI,CAAC,SAAS,OAAQ,QAAO;AAC7B,gBAAM,aAAa,YAAY,IAAI,MAAM;AACzC,cAAI,CAAC,cAAc,WAAW,SAAS,EAAG,QAAO;AACjD,iBAAO,SAAS,MAAM,CAAC,OAAO,WAAW,IAAI,EAAE,CAAC;AAAA,QAClD;AAEA,cAAM,gBAAgB,MACnB,IAAI,CAAC,SAAkB;AACtB,cAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,gBAAM,OAAO;AACb,gBAAM,YAAY,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AAC1D,cAAI,CAAC,aAAa,CAAC,UAAU,KAAK,EAAE,OAAQ,QAAO;AACnD,gBAAM,SAAS,kBAAkB,IAAI,SAAS,KAAK,CAAC;AACpD,gBAAM,YAAY,mBAAmB,IAAI,SAAS,KAAK,CAAC;AACxD,gBAAM,gBAAgB,WAAW,mBAAmB,mBAAmB,SAAS;AAChF,gBAAM,iBAAiB,WAAW,oBAAoB,oBAAoB,SAAS;AACnF,cAAI,CAAC,iBAAiB,CAAC,eAAgB,QAAO;AAC9C,gBAAM,cACJ,OAAO,KAAK,aAAa,WACrB,KAAK,WACL,OAAO,KAAK,cAAc,WACxB,KAAK,YACL;AACR,gBAAM,oBACJ,OAAO,KAAK,mBAAmB,WAC3B,KAAK,iBACL,OAAO,KAAK,oBAAoB,WAC9B,KAAK,kBACL;AACR,gBAAM,WAAW,eAAe,YAAY,KAAK,EAAE,SAAS,YAAY,KAAK,IAAI;AACjF,gBAAM,iBAAiB,qBAAqB,kBAAkB,KAAK,EAAE,SAAS,kBAAkB,KAAK,IAAI;AACzG,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,WAAW,OAAO,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,YACzC;AAAA,YACA,YAAY,UAAU,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,YAC7C;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,CAAC,EACA;AAAA,UACC,CAAC,SAA0E,SAAS;AAAA,QACtF;AAEF,gBAAQ,QAAQ;AAChB,YAAI,mBAAmB,kBAAkB;AACvC,kBAAQ,QAAQ,cAAc;AAC9B,kBAAQ,aAAa;AACrB,kBAAQ,OAAO;AAAA,QACjB;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,KAAK,6DAA6D,GAAG;AAAA,MAE/E;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,MAAM,EAAE,MAAM,KAAK,OAAO,IAAI;AAGvB,MAAM,MAAM,KAAK;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAED,MAAM,qBAAqB,EACxB,OAAO;AAAA,EACN,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACvC,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC/C,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACnD,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACzD,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC7C,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC/C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC5C,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAClD,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACrD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACjD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS;AAAA,EAC/C,QAAQ,EAAE,MAAM,qBAAqB,EAAE,SAAS;AAAA,EAChD,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS;AAAA,EAChD,WAAW,EAAE,MAAM,qBAAqB,EAAE,SAAS;AAAA,EACnD,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACtD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAClD,CAAC,EACA,YAAY;AAEf,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACjC,CAAC;AAEM,MAAM,UAAU,2BAA2B;AAAA,EAChD,cAAc;AAAA,EACd,aAAa;AAAA,EACb,oBAAoB,8BAA8B,kBAAkB;AAAA,EACpE,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AAAA,EACA,KAAK;AAAA,IACH,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,IAC1C,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AACF,CAAC;",
|
|
6
6
|
"names": ["parsed"]
|
|
7
7
|
}
|
|
@@ -2,7 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
3
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
4
|
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
5
|
-
import { CustomerDictionaryEntry } from "../../../data/entities.js";
|
|
5
|
+
import { CustomerDictionaryEntry, CustomerPipelineStage } from "../../../data/entities.js";
|
|
6
|
+
import { ensureDictionaryEntry } from "../../../commands/shared.js";
|
|
6
7
|
import { mapDictionaryKind, resolveDictionaryRouteContext } from "../context.js";
|
|
7
8
|
import { createDictionaryCacheKey, createDictionaryCacheTags, invalidateDictionaryCache, DICTIONARY_CACHE_TTL_MS } from "../cache.js";
|
|
8
9
|
import { z } from "zod";
|
|
@@ -37,6 +38,24 @@ async function GET(req, ctx) {
|
|
|
37
38
|
{ tenantId, organizationId: { $in: readableOrganizationIds }, kind: mappedKind },
|
|
38
39
|
{ orderBy: { label: "asc" } }
|
|
39
40
|
);
|
|
41
|
+
if (mappedKind === "pipeline_stage" && organizationId) {
|
|
42
|
+
const existingNormalized = new Set(entries.map((e) => e.normalizedValue));
|
|
43
|
+
const pipelineStages = await em.find(CustomerPipelineStage, { organizationId, tenantId });
|
|
44
|
+
for (const stage of pipelineStages) {
|
|
45
|
+
if (!existingNormalized.has(stage.label.trim().toLowerCase())) {
|
|
46
|
+
const created = await ensureDictionaryEntry(em, {
|
|
47
|
+
tenantId,
|
|
48
|
+
organizationId,
|
|
49
|
+
kind: "pipeline_stage",
|
|
50
|
+
value: stage.label
|
|
51
|
+
});
|
|
52
|
+
if (created) {
|
|
53
|
+
entries.push(created);
|
|
54
|
+
existingNormalized.add(created.normalizedValue);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
40
59
|
const byValue = /* @__PURE__ */ new Map();
|
|
41
60
|
for (const entry of entries) {
|
|
42
61
|
const normalized = entry.normalizedValue;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/dictionaries/%5Bkind%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { CommandExecuteResult } from '@open-mercato/shared/lib/commands/types'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { CustomerDictionaryEntry } from '../../../data/entities'\nimport { mapDictionaryKind, resolveDictionaryRouteContext } from '../context'\nimport { createDictionaryCacheKey, createDictionaryCacheTags, invalidateDictionaryCache, DICTIONARY_CACHE_TTL_MS } from '../cache'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst colorSchema = z.string().trim().regex(/^#([0-9A-Fa-f]{6})$/, 'Invalid color hex')\nconst iconSchema = z.string().trim().min(1).max(48)\n\nconst postSchema = z.object({\n value: z.string().trim().min(1).max(150),\n label: z.string().trim().max(150).optional(),\n color: colorSchema.or(z.null()).optional(),\n icon: iconSchema.or(z.null()).optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { kind?: string } }) {\n try {\n const { translate, em, organizationId, tenantId, readableOrganizationIds, cache } = await resolveDictionaryRouteContext(req)\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n\n let cacheKey: string | null = null\n if (cache) {\n cacheKey = createDictionaryCacheKey({ tenantId, organizationId, mappedKind, readableOrganizationIds })\n const cached = await cache.get(cacheKey)\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const organizationOrder = new Map<string, number>()\n readableOrganizationIds.forEach((id, index) => organizationOrder.set(id, index))\n\n const entries = await em.find(\n CustomerDictionaryEntry,\n { tenantId, organizationId: { $in: readableOrganizationIds }, kind: mappedKind } as any,\n { orderBy: { label: 'asc' } }\n )\n\n const byValue = new Map<string, { entry: CustomerDictionaryEntry; isInherited: boolean; order: number }>()\n for (const entry of entries) {\n const normalized = entry.normalizedValue\n const order = organizationOrder.get(entry.organizationId) ?? Number.MAX_SAFE_INTEGER\n if (!byValue.has(normalized) || order < byValue.get(normalized)!.order) {\n byValue.set(normalized, {\n entry,\n isInherited: organizationId ? entry.organizationId !== organizationId : false,\n order,\n })\n }\n }\n\n const items = Array.from(byValue.values()).map(({ entry, isInherited, order }) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited,\n __order: order,\n }))\n\n items.sort((a, b) => {\n if (a.isInherited !== b.isInherited) return a.isInherited ? 1 : -1\n if (a.__order !== b.__order) return a.__order - b.__order\n return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })\n })\n\n const responseBody = {\n items: items.map(({ __order, ...item }) => item),\n }\n\n if (cache && cacheKey) {\n const tags = createDictionaryCacheTags({\n tenantId,\n mappedKind,\n organizationIds: readableOrganizationIds,\n })\n try {\n await cache.set(cacheKey, responseBody, {\n ttl: DICTIONARY_CACHE_TTL_MS,\n tags,\n })\n } catch (err) {\n console.warn('[customers.dictionaries.cache] Failed to set cache entry', err)\n }\n }\n\n return NextResponse.json(responseBody)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('customers.dictionaries.list failed', err)\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to load dictionary entries') }, { status: 400 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { kind?: string } }) {\n try {\n const context = await resolveDictionaryRouteContext(req)\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const body = postSchema.parse(await req.json().catch(() => ({})))\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } =\n (await commandBus.execute('customers.dictionaryEntries.create', {\n input: {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n kind: mappedKind,\n value: body.value,\n label: body.label,\n color: body.color,\n icon: body.icon,\n },\n ctx: context.ctx,\n })) as CommandExecuteResult<{ entryId: string; mode: 'created' | 'updated' | 'unchanged' }>\n const entry = await context.em.fork().findOne(CustomerDictionaryEntry, result.entryId)\n if (!entry) {\n throw new CrudHttpError(400, { error: context.translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') })\n }\n\n await invalidateDictionaryCache(context.cache, {\n tenantId: context.tenantId,\n mappedKind,\n organizationIds: [entry.organizationId],\n })\n\n const response = NextResponse.json(\n {\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited: false,\n },\n { status: result.mode === 'created' ? 201 : 200 }\n )\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('customers.dictionaries.create failed', err)\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') }, { status: 400 })\n }\n}\n\nconst dictionaryEntrySchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n icon: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n isInherited: z.boolean().optional(),\n})\n\nconst dictionaryListResponseSchema = z.object({\n items: z.array(dictionaryEntrySchema),\n})\n\nconst dictionaryErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entries',\n methods: {\n GET: {\n summary: 'List dictionary entries',\n description: 'Returns the merged dictionary entries for the requested kind, including inherited values.',\n responses: [\n { status: 200, description: 'Dictionary entries', schema: dictionaryListResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 400, description: 'Failed to resolve dictionary context', schema: dictionaryErrorSchema },\n ],\n },\n POST: {\n summary: 'Create or override dictionary entry',\n description: 'Creates a dictionary entry (or updates the existing entry for the same value) within the current organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: postSchema,\n },\n responses: [\n { status: 201, description: 'Dictionary entry created', schema: dictionaryEntrySchema },\n { status: 200, description: 'Dictionary entry updated', schema: dictionaryEntrySchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: dictionaryErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 409, description: 'Duplicate value conflict', schema: dictionaryErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAG9B,SAAS,kCAAkC;AAC3C,SAAS
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { CommandExecuteResult } from '@open-mercato/shared/lib/commands/types'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { CustomerDictionaryEntry, CustomerPipelineStage } from '../../../data/entities'\nimport { ensureDictionaryEntry } from '../../../commands/shared'\nimport { mapDictionaryKind, resolveDictionaryRouteContext } from '../context'\nimport { createDictionaryCacheKey, createDictionaryCacheTags, invalidateDictionaryCache, DICTIONARY_CACHE_TTL_MS } from '../cache'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst colorSchema = z.string().trim().regex(/^#([0-9A-Fa-f]{6})$/, 'Invalid color hex')\nconst iconSchema = z.string().trim().min(1).max(48)\n\nconst postSchema = z.object({\n value: z.string().trim().min(1).max(150),\n label: z.string().trim().max(150).optional(),\n color: colorSchema.or(z.null()).optional(),\n icon: iconSchema.or(z.null()).optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { kind?: string } }) {\n try {\n const { translate, em, organizationId, tenantId, readableOrganizationIds, cache } = await resolveDictionaryRouteContext(req)\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n\n let cacheKey: string | null = null\n if (cache) {\n cacheKey = createDictionaryCacheKey({ tenantId, organizationId, mappedKind, readableOrganizationIds })\n const cached = await cache.get(cacheKey)\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const organizationOrder = new Map<string, number>()\n readableOrganizationIds.forEach((id, index) => organizationOrder.set(id, index))\n\n const entries = await em.find(\n CustomerDictionaryEntry,\n { tenantId, organizationId: { $in: readableOrganizationIds }, kind: mappedKind } as any,\n { orderBy: { label: 'asc' } }\n )\n\n if (mappedKind === 'pipeline_stage' && organizationId) {\n const existingNormalized = new Set(entries.map((e) => e.normalizedValue))\n const pipelineStages = await em.find(CustomerPipelineStage, { organizationId, tenantId })\n for (const stage of pipelineStages) {\n if (!existingNormalized.has(stage.label.trim().toLowerCase())) {\n const created = await ensureDictionaryEntry(em, {\n tenantId,\n organizationId,\n kind: 'pipeline_stage',\n value: stage.label,\n })\n if (created) {\n entries.push(created)\n existingNormalized.add(created.normalizedValue)\n }\n }\n }\n }\n\n const byValue = new Map<string, { entry: CustomerDictionaryEntry; isInherited: boolean; order: number }>()\n for (const entry of entries) {\n const normalized = entry.normalizedValue\n const order = organizationOrder.get(entry.organizationId) ?? Number.MAX_SAFE_INTEGER\n if (!byValue.has(normalized) || order < byValue.get(normalized)!.order) {\n byValue.set(normalized, {\n entry,\n isInherited: organizationId ? entry.organizationId !== organizationId : false,\n order,\n })\n }\n }\n\n const items = Array.from(byValue.values()).map(({ entry, isInherited, order }) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited,\n __order: order,\n }))\n\n items.sort((a, b) => {\n if (a.isInherited !== b.isInherited) return a.isInherited ? 1 : -1\n if (a.__order !== b.__order) return a.__order - b.__order\n return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })\n })\n\n const responseBody = {\n items: items.map(({ __order, ...item }) => item),\n }\n\n if (cache && cacheKey) {\n const tags = createDictionaryCacheTags({\n tenantId,\n mappedKind,\n organizationIds: readableOrganizationIds,\n })\n try {\n await cache.set(cacheKey, responseBody, {\n ttl: DICTIONARY_CACHE_TTL_MS,\n tags,\n })\n } catch (err) {\n console.warn('[customers.dictionaries.cache] Failed to set cache entry', err)\n }\n }\n\n return NextResponse.json(responseBody)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('customers.dictionaries.list failed', err)\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to load dictionary entries') }, { status: 400 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { kind?: string } }) {\n try {\n const context = await resolveDictionaryRouteContext(req)\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const body = postSchema.parse(await req.json().catch(() => ({})))\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } =\n (await commandBus.execute('customers.dictionaryEntries.create', {\n input: {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n kind: mappedKind,\n value: body.value,\n label: body.label,\n color: body.color,\n icon: body.icon,\n },\n ctx: context.ctx,\n })) as CommandExecuteResult<{ entryId: string; mode: 'created' | 'updated' | 'unchanged' }>\n const entry = await context.em.fork().findOne(CustomerDictionaryEntry, result.entryId)\n if (!entry) {\n throw new CrudHttpError(400, { error: context.translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') })\n }\n\n await invalidateDictionaryCache(context.cache, {\n tenantId: context.tenantId,\n mappedKind,\n organizationIds: [entry.organizationId],\n })\n\n const response = NextResponse.json(\n {\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited: false,\n },\n { status: result.mode === 'created' ? 201 : 200 }\n )\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('customers.dictionaries.create failed', err)\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') }, { status: 400 })\n }\n}\n\nconst dictionaryEntrySchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n icon: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n isInherited: z.boolean().optional(),\n})\n\nconst dictionaryListResponseSchema = z.object({\n items: z.array(dictionaryEntrySchema),\n})\n\nconst dictionaryErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entries',\n methods: {\n GET: {\n summary: 'List dictionary entries',\n description: 'Returns the merged dictionary entries for the requested kind, including inherited values.',\n responses: [\n { status: 200, description: 'Dictionary entries', schema: dictionaryListResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 400, description: 'Failed to resolve dictionary context', schema: dictionaryErrorSchema },\n ],\n },\n POST: {\n summary: 'Create or override dictionary entry',\n description: 'Creates a dictionary entry (or updates the existing entry for the same value) within the current organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: postSchema,\n },\n responses: [\n { status: 201, description: 'Dictionary entry created', schema: dictionaryEntrySchema },\n { status: 200, description: 'Dictionary entry updated', schema: dictionaryEntrySchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: dictionaryErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 409, description: 'Duplicate value conflict', schema: dictionaryErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
+
import { pipelineStageReorderSchema } from "../../../data/validators.js";
|
|
7
|
+
import { withScopedPayload } from "../../utils.js";
|
|
8
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
9
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
10
|
+
const metadata = {
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ["customers.pipelines.manage"] }
|
|
12
|
+
};
|
|
13
|
+
async function POST(req) {
|
|
14
|
+
try {
|
|
15
|
+
const container = await createRequestContainer();
|
|
16
|
+
const auth = await getAuthFromRequest(req);
|
|
17
|
+
const { translate } = await resolveTranslations();
|
|
18
|
+
if (!auth) throw new CrudHttpError(401, { error: translate("customers.errors.unauthorized", "Unauthorized") });
|
|
19
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
20
|
+
const ctx = {
|
|
21
|
+
container,
|
|
22
|
+
auth,
|
|
23
|
+
organizationScope: scope,
|
|
24
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
25
|
+
organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
26
|
+
request: req
|
|
27
|
+
};
|
|
28
|
+
const body = await req.json().catch(() => ({}));
|
|
29
|
+
const scoped = withScopedPayload(body, ctx, translate);
|
|
30
|
+
const commandBus = ctx.container.resolve("commandBus");
|
|
31
|
+
await commandBus.execute(
|
|
32
|
+
"customers.pipeline-stages.reorder",
|
|
33
|
+
{ input: pipelineStageReorderSchema.parse(scoped), ctx }
|
|
34
|
+
);
|
|
35
|
+
return NextResponse.json({ ok: true });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err instanceof CrudHttpError) {
|
|
38
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
39
|
+
}
|
|
40
|
+
console.error("customers.pipeline-stages.reorder failed", err);
|
|
41
|
+
return NextResponse.json({ error: "Failed to reorder pipeline stages" }, { status: 400 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const reorderOkResponseSchema = z.object({ ok: z.boolean() });
|
|
45
|
+
const reorderErrorSchema = z.object({ error: z.string() });
|
|
46
|
+
const openApi = {
|
|
47
|
+
tag: "Customers",
|
|
48
|
+
summary: "Reorder pipeline stages",
|
|
49
|
+
methods: {
|
|
50
|
+
POST: {
|
|
51
|
+
summary: "Reorder pipeline stages",
|
|
52
|
+
description: "Updates the order of pipeline stages in bulk.",
|
|
53
|
+
requestBody: { contentType: "application/json", schema: pipelineStageReorderSchema },
|
|
54
|
+
responses: [
|
|
55
|
+
{ status: 200, description: "Stages reordered", schema: reorderOkResponseSchema }
|
|
56
|
+
],
|
|
57
|
+
errors: [
|
|
58
|
+
{ status: 400, description: "Validation failed", schema: reorderErrorSchema },
|
|
59
|
+
{ status: 401, description: "Unauthorized", schema: reorderErrorSchema }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export {
|
|
65
|
+
POST,
|
|
66
|
+
metadata,
|
|
67
|
+
openApi
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/customers/api/pipeline-stages/reorder/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../../../data/validators'\nimport { withScopedPayload } from '../../utils'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageReorderInput, void>(\n 'customers.pipeline-stages.reorder',\n { input: pipelineStageReorderSchema.parse(scoped), ctx },\n )\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('customers.pipeline-stages.reorder failed', err)\n return NextResponse.json({ error: 'Failed to reorder pipeline stages' }, { status: 400 })\n }\n}\n\nconst reorderOkResponseSchema = z.object({ ok: z.boolean() })\nconst reorderErrorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Reorder pipeline stages',\n methods: {\n POST: {\n summary: 'Reorder pipeline stages',\n description: 'Updates the order of pipeline stages in bulk.',\n requestBody: { contentType: 'application/json', schema: pipelineStageReorderSchema },\n responses: [\n { status: 200, description: 'Stages reordered', schema: reorderOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: reorderErrorSchema },\n { status: 401, description: 'Unauthorized', schema: reorderErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,kCAAkE;AAC3E,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAG7B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,iCAAiC,cAAc,EAAE,CAAC;AAC7G,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,UAAM,MAA6B;AAAA,MACjC;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,MACnB,wBAAwB,OAAO,cAAc,KAAK,SAAS;AAAA,MAC3D,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,MAClE,SAAS;AAAA,IACX;AAEA,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,SAAS,kBAAkB,MAAM,KAAK,SAAS;AAErD,UAAM,aAAc,IAAI,UAAU,QAAQ,YAAY;AACtD,UAAM,WAAW;AAAA,MACf;AAAA,MACA,EAAE,OAAO,2BAA2B,MAAM,MAAM,GAAG,IAAI;AAAA,IACzD;AACA,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC,SAAS,KAAK;AACZ,QAAI,eAAe,eAAe;AAChC,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,4CAA4C,GAAG;AAC7D,WAAO,aAAa,KAAK,EAAE,OAAO,oCAAoC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1F;AACF;AAEA,MAAM,0BAA0B,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5D,MAAM,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAElD,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa,EAAE,aAAa,oBAAoB,QAAQ,2BAA2B;AAAA,MACnF,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,wBAAwB;AAAA,MAClF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,mBAAmB;AAAA,QAC5E,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,mBAAmB;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
+
import { CustomerPipelineStage, CustomerDictionaryEntry } from "../../data/entities.js";
|
|
7
|
+
import {
|
|
8
|
+
pipelineStageCreateSchema,
|
|
9
|
+
pipelineStageUpdateSchema,
|
|
10
|
+
pipelineStageDeleteSchema
|
|
11
|
+
} from "../../data/validators.js";
|
|
12
|
+
import { withScopedPayload } from "../utils.js";
|
|
13
|
+
import { ensureDictionaryEntry } from "../../commands/shared.js";
|
|
14
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
15
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
16
|
+
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
17
|
+
const metadata = {
|
|
18
|
+
GET: { requireAuth: true, requireFeatures: ["customers.pipelines.view"] },
|
|
19
|
+
POST: { requireAuth: true, requireFeatures: ["customers.pipelines.manage"] },
|
|
20
|
+
PUT: { requireAuth: true, requireFeatures: ["customers.pipelines.manage"] },
|
|
21
|
+
DELETE: { requireAuth: true, requireFeatures: ["customers.pipelines.manage"] }
|
|
22
|
+
};
|
|
23
|
+
async function buildContext(req) {
|
|
24
|
+
const container = await createRequestContainer();
|
|
25
|
+
const auth = await getAuthFromRequest(req);
|
|
26
|
+
const { translate } = await resolveTranslations();
|
|
27
|
+
if (!auth) throw new CrudHttpError(401, { error: translate("customers.errors.unauthorized", "Unauthorized") });
|
|
28
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
29
|
+
const ctx = {
|
|
30
|
+
container,
|
|
31
|
+
auth,
|
|
32
|
+
organizationScope: scope,
|
|
33
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
34
|
+
organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
35
|
+
request: req
|
|
36
|
+
};
|
|
37
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null;
|
|
38
|
+
const tenantId = auth.tenantId ?? null;
|
|
39
|
+
return { ctx, organizationId, tenantId };
|
|
40
|
+
}
|
|
41
|
+
async function GET(req) {
|
|
42
|
+
try {
|
|
43
|
+
const { ctx, organizationId, tenantId } = await buildContext(req);
|
|
44
|
+
if (!organizationId || !tenantId) {
|
|
45
|
+
return NextResponse.json({ error: "Organization and tenant context required" }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
const url = new URL(req.url);
|
|
48
|
+
const pipelineId = url.searchParams.get("pipelineId");
|
|
49
|
+
const em = ctx.container.resolve("em");
|
|
50
|
+
const where = { organizationId, tenantId };
|
|
51
|
+
if (pipelineId) where.pipelineId = pipelineId;
|
|
52
|
+
const stages = await em.find(CustomerPipelineStage, where, { orderBy: { order: "ASC" } });
|
|
53
|
+
const stageLabels = stages.map((s) => s.label.trim().toLowerCase());
|
|
54
|
+
const dictEntries = stageLabels.length ? await em.find(CustomerDictionaryEntry, {
|
|
55
|
+
organizationId,
|
|
56
|
+
tenantId,
|
|
57
|
+
kind: "pipeline_stage",
|
|
58
|
+
normalizedValue: { $in: stageLabels }
|
|
59
|
+
}) : [];
|
|
60
|
+
const dictByNormalized = /* @__PURE__ */ new Map();
|
|
61
|
+
dictEntries.forEach((entry) => dictByNormalized.set(entry.normalizedValue, entry));
|
|
62
|
+
const missingStages = stages.filter((s) => !dictByNormalized.has(s.label.trim().toLowerCase()));
|
|
63
|
+
if (missingStages.length) {
|
|
64
|
+
for (const stage of missingStages) {
|
|
65
|
+
const created = await ensureDictionaryEntry(em, {
|
|
66
|
+
tenantId,
|
|
67
|
+
organizationId,
|
|
68
|
+
kind: "pipeline_stage",
|
|
69
|
+
value: stage.label
|
|
70
|
+
});
|
|
71
|
+
if (created) dictByNormalized.set(created.normalizedValue, created);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const items = stages.map((stage) => {
|
|
75
|
+
const dictEntry = dictByNormalized.get(stage.label.trim().toLowerCase());
|
|
76
|
+
return {
|
|
77
|
+
id: stage.id,
|
|
78
|
+
pipelineId: stage.pipelineId,
|
|
79
|
+
label: stage.label,
|
|
80
|
+
order: stage.order,
|
|
81
|
+
color: dictEntry?.color ?? null,
|
|
82
|
+
icon: dictEntry?.icon ?? null,
|
|
83
|
+
organizationId: stage.organizationId,
|
|
84
|
+
tenantId: stage.tenantId,
|
|
85
|
+
createdAt: stage.createdAt,
|
|
86
|
+
updatedAt: stage.updatedAt
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
return NextResponse.json({ items, total: items.length });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof CrudHttpError) {
|
|
92
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
93
|
+
}
|
|
94
|
+
console.error("customers.pipeline-stages GET failed", err);
|
|
95
|
+
return NextResponse.json({ error: "Failed to load pipeline stages" }, { status: 500 });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function POST(req) {
|
|
99
|
+
try {
|
|
100
|
+
const { ctx } = await buildContext(req);
|
|
101
|
+
const body = await req.json().catch(() => ({}));
|
|
102
|
+
const { translate } = await resolveTranslations();
|
|
103
|
+
const scoped = withScopedPayload(body, ctx, translate);
|
|
104
|
+
const commandBus = ctx.container.resolve("commandBus");
|
|
105
|
+
const { result, logEntry } = await commandBus.execute(
|
|
106
|
+
"customers.pipeline-stages.create",
|
|
107
|
+
{ input: pipelineStageCreateSchema.parse(scoped), ctx }
|
|
108
|
+
);
|
|
109
|
+
const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 });
|
|
110
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
111
|
+
response.headers.set(
|
|
112
|
+
"x-om-operation",
|
|
113
|
+
serializeOperationMetadata({
|
|
114
|
+
id: logEntry.id,
|
|
115
|
+
undoToken: logEntry.undoToken,
|
|
116
|
+
commandId: logEntry.commandId,
|
|
117
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
118
|
+
resourceKind: logEntry.resourceKind ?? "customers.pipelineStage",
|
|
119
|
+
resourceId: logEntry.resourceId ?? result?.stageId ?? null,
|
|
120
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : void 0
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return response;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err instanceof CrudHttpError) {
|
|
127
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
128
|
+
}
|
|
129
|
+
console.error("customers.pipeline-stages POST failed", err);
|
|
130
|
+
return NextResponse.json({ error: "Failed to create pipeline stage" }, { status: 400 });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function PUT(req) {
|
|
134
|
+
try {
|
|
135
|
+
const { ctx } = await buildContext(req);
|
|
136
|
+
const body = await req.json().catch(() => ({}));
|
|
137
|
+
const { translate } = await resolveTranslations();
|
|
138
|
+
const scoped = withScopedPayload(body, ctx, translate);
|
|
139
|
+
const commandBus = ctx.container.resolve("commandBus");
|
|
140
|
+
const { logEntry } = await commandBus.execute(
|
|
141
|
+
"customers.pipeline-stages.update",
|
|
142
|
+
{ input: pipelineStageUpdateSchema.parse(scoped), ctx }
|
|
143
|
+
);
|
|
144
|
+
const response = NextResponse.json({ ok: true });
|
|
145
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
146
|
+
response.headers.set(
|
|
147
|
+
"x-om-operation",
|
|
148
|
+
serializeOperationMetadata({
|
|
149
|
+
id: logEntry.id,
|
|
150
|
+
undoToken: logEntry.undoToken,
|
|
151
|
+
commandId: logEntry.commandId,
|
|
152
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
153
|
+
resourceKind: logEntry.resourceKind ?? "customers.pipelineStage",
|
|
154
|
+
resourceId: logEntry.resourceId ?? null,
|
|
155
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : void 0
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return response;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
if (err instanceof CrudHttpError) {
|
|
162
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
163
|
+
}
|
|
164
|
+
console.error("customers.pipeline-stages PUT failed", err);
|
|
165
|
+
return NextResponse.json({ error: "Failed to update pipeline stage" }, { status: 400 });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function DELETE(req) {
|
|
169
|
+
try {
|
|
170
|
+
const { ctx } = await buildContext(req);
|
|
171
|
+
const body = await req.json().catch(() => ({}));
|
|
172
|
+
const { translate } = await resolveTranslations();
|
|
173
|
+
const scoped = withScopedPayload(body, ctx, translate);
|
|
174
|
+
const commandBus = ctx.container.resolve("commandBus");
|
|
175
|
+
await commandBus.execute(
|
|
176
|
+
"customers.pipeline-stages.delete",
|
|
177
|
+
{ input: pipelineStageDeleteSchema.parse(scoped), ctx }
|
|
178
|
+
);
|
|
179
|
+
return NextResponse.json({ ok: true });
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof CrudHttpError) {
|
|
182
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
183
|
+
}
|
|
184
|
+
console.error("customers.pipeline-stages DELETE failed", err);
|
|
185
|
+
return NextResponse.json({ error: "Failed to delete pipeline stage" }, { status: 400 });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const stageItemSchema = z.object({
|
|
189
|
+
id: z.string().uuid(),
|
|
190
|
+
pipelineId: z.string().uuid(),
|
|
191
|
+
label: z.string(),
|
|
192
|
+
order: z.number(),
|
|
193
|
+
color: z.string().nullable(),
|
|
194
|
+
icon: z.string().nullable(),
|
|
195
|
+
organizationId: z.string().uuid(),
|
|
196
|
+
tenantId: z.string().uuid(),
|
|
197
|
+
createdAt: z.date(),
|
|
198
|
+
updatedAt: z.date()
|
|
199
|
+
});
|
|
200
|
+
const stageListResponseSchema = z.object({
|
|
201
|
+
items: z.array(stageItemSchema),
|
|
202
|
+
total: z.number()
|
|
203
|
+
});
|
|
204
|
+
const stageCreateResponseSchema = z.object({
|
|
205
|
+
id: z.string().uuid().nullable()
|
|
206
|
+
});
|
|
207
|
+
const stageOkResponseSchema = z.object({
|
|
208
|
+
ok: z.boolean()
|
|
209
|
+
});
|
|
210
|
+
const stageErrorSchema = z.object({
|
|
211
|
+
error: z.string()
|
|
212
|
+
});
|
|
213
|
+
const openApi = {
|
|
214
|
+
tag: "Customers",
|
|
215
|
+
summary: "Manage pipeline stages",
|
|
216
|
+
methods: {
|
|
217
|
+
GET: {
|
|
218
|
+
summary: "List pipeline stages",
|
|
219
|
+
description: "Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.",
|
|
220
|
+
query: z.object({ pipelineId: z.string().uuid().optional() }),
|
|
221
|
+
responses: [
|
|
222
|
+
{ status: 200, description: "Stage list", schema: stageListResponseSchema }
|
|
223
|
+
],
|
|
224
|
+
errors: [
|
|
225
|
+
{ status: 401, description: "Unauthorized", schema: stageErrorSchema },
|
|
226
|
+
{ status: 400, description: "Invalid request", schema: stageErrorSchema }
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
POST: {
|
|
230
|
+
summary: "Create pipeline stage",
|
|
231
|
+
description: "Creates a new pipeline stage.",
|
|
232
|
+
requestBody: { contentType: "application/json", schema: pipelineStageCreateSchema },
|
|
233
|
+
responses: [
|
|
234
|
+
{ status: 201, description: "Stage created", schema: stageCreateResponseSchema }
|
|
235
|
+
],
|
|
236
|
+
errors: [
|
|
237
|
+
{ status: 400, description: "Validation failed", schema: stageErrorSchema },
|
|
238
|
+
{ status: 401, description: "Unauthorized", schema: stageErrorSchema }
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
PUT: {
|
|
242
|
+
summary: "Update pipeline stage",
|
|
243
|
+
description: "Updates an existing pipeline stage.",
|
|
244
|
+
requestBody: { contentType: "application/json", schema: pipelineStageUpdateSchema },
|
|
245
|
+
responses: [
|
|
246
|
+
{ status: 200, description: "Stage updated", schema: stageOkResponseSchema }
|
|
247
|
+
],
|
|
248
|
+
errors: [
|
|
249
|
+
{ status: 400, description: "Validation failed", schema: stageErrorSchema },
|
|
250
|
+
{ status: 404, description: "Stage not found", schema: stageErrorSchema }
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
DELETE: {
|
|
254
|
+
summary: "Delete pipeline stage",
|
|
255
|
+
description: "Deletes a pipeline stage. Returns 409 if active deals use this stage.",
|
|
256
|
+
requestBody: { contentType: "application/json", schema: pipelineStageDeleteSchema },
|
|
257
|
+
responses: [
|
|
258
|
+
{ status: 200, description: "Stage deleted", schema: stageOkResponseSchema }
|
|
259
|
+
],
|
|
260
|
+
errors: [
|
|
261
|
+
{ status: 409, description: "Stage has active deals", schema: stageErrorSchema },
|
|
262
|
+
{ status: 404, description: "Stage not found", schema: stageErrorSchema }
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
export {
|
|
268
|
+
DELETE,
|
|
269
|
+
GET,
|
|
270
|
+
POST,
|
|
271
|
+
PUT,
|
|
272
|
+
metadata,
|
|
273
|
+
openApi
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=route.js.map
|