@open-mercato/core 0.4.5-develop-7f44fcf045 → 0.4.5-develop-8f98466993
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/currencies/api/currencies/route.js +6 -0
- package/dist/modules/currencies/api/currencies/route.js.map +2 -2
- package/dist/modules/currencies/api/exchange-rates/route.js +5 -1
- package/dist/modules/currencies/api/exchange-rates/route.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +5 -4
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/commands/exchange-rates.js +5 -4
- package/dist/modules/currencies/commands/exchange-rates.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/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/currencies/api/currencies/route.ts +6 -0
- package/src/modules/currencies/api/exchange-rates/route.ts +5 -1
- package/src/modules/currencies/commands/currencies.ts +5 -4
- package/src/modules/currencies/commands/exchange-rates.ts +5 -4
- 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/workflows/migrations/Migration20260222205305.ts +13 -0
|
@@ -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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/api/pipeline-stages/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 type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CustomerPipelineStage, CustomerDictionaryEntry } from '../../data/entities'\nimport {\n pipelineStageCreateSchema,\n pipelineStageUpdateSchema,\n pipelineStageDeleteSchema,\n type PipelineStageCreateInput,\n type PipelineStageUpdateInput,\n type PipelineStageDeleteInput,\n} from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { ensureDictionaryEntry } from '../../commands/shared'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n return { ctx, organizationId, tenantId }\n}\n\nexport async function GET(req: Request) {\n try {\n const { ctx, organizationId, tenantId } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: 'Organization and tenant context required' }, { status: 400 })\n }\n const url = new URL(req.url)\n const pipelineId = url.searchParams.get('pipelineId')\n\n const em = (ctx.container.resolve('em') as EntityManager)\n const where: Record<string, unknown> = { organizationId, tenantId }\n if (pipelineId) where.pipelineId = pipelineId\n\n const stages = await em.find(CustomerPipelineStage, where, { orderBy: { order: 'ASC' } })\n\n const stageLabels = stages.map((s) => s.label.trim().toLowerCase())\n const dictEntries = stageLabels.length\n ? await em.find(CustomerDictionaryEntry, {\n organizationId,\n tenantId,\n kind: 'pipeline_stage',\n normalizedValue: { $in: stageLabels },\n })\n : []\n const dictByNormalized = new Map<string, CustomerDictionaryEntry>()\n dictEntries.forEach((entry) => dictByNormalized.set(entry.normalizedValue, entry))\n\n const missingStages = stages.filter((s) => !dictByNormalized.has(s.label.trim().toLowerCase()))\n if (missingStages.length) {\n for (const stage of missingStages) {\n const created = await ensureDictionaryEntry(em, {\n tenantId,\n organizationId,\n kind: 'pipeline_stage',\n value: stage.label,\n })\n if (created) dictByNormalized.set(created.normalizedValue, created)\n }\n }\n\n const items = stages.map((stage) => {\n const dictEntry = dictByNormalized.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n pipelineId: stage.pipelineId,\n label: stage.label,\n order: stage.order,\n color: dictEntry?.color ?? null,\n icon: dictEntry?.icon ?? null,\n organizationId: stage.organizationId,\n tenantId: stage.tenantId,\n createdAt: stage.createdAt,\n updatedAt: stage.updatedAt,\n }\n })\n return NextResponse.json({ items, total: items.length })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('customers.pipeline-stages GET failed', err)\n return NextResponse.json({ error: 'Failed to load pipeline stages' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(body, ctx, translate)\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(\n 'customers.pipeline-stages.create',\n { input: pipelineStageCreateSchema.parse(scoped), ctx },\n )\n const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.pipelineStage',\n resourceId: logEntry.resourceId ?? result?.stageId ?? null,\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 console.error('customers.pipeline-stages POST failed', err)\n return NextResponse.json({ error: 'Failed to create pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(body, ctx, translate)\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(\n 'customers.pipeline-stages.update',\n { input: pipelineStageUpdateSchema.parse(scoped), ctx },\n )\n const response = NextResponse.json({ ok: true })\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.pipelineStage',\n resourceId: logEntry.resourceId ?? null,\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 console.error('customers.pipeline-stages PUT failed', err)\n return NextResponse.json({ error: 'Failed to update pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(body, ctx, translate)\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageDeleteInput, void>(\n 'customers.pipeline-stages.delete',\n { input: pipelineStageDeleteSchema.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 DELETE failed', err)\n return NextResponse.json({ error: 'Failed to delete pipeline stage' }, { status: 400 })\n }\n}\n\nconst stageItemSchema = z.object({\n id: z.string().uuid(),\n pipelineId: z.string().uuid(),\n label: z.string(),\n order: z.number(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n createdAt: z.date(),\n updatedAt: z.date(),\n})\n\nconst stageListResponseSchema = z.object({\n items: z.array(stageItemSchema),\n total: z.number(),\n})\n\nconst stageCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nconst stageOkResponseSchema = z.object({\n ok: z.boolean(),\n})\n\nconst stageErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Manage pipeline stages',\n methods: {\n GET: {\n summary: 'List pipeline stages',\n description: 'Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.',\n query: z.object({ pipelineId: z.string().uuid().optional() }),\n responses: [\n { status: 200, description: 'Stage list', schema: stageListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n { status: 400, description: 'Invalid request', schema: stageErrorSchema },\n ],\n },\n POST: {\n summary: 'Create pipeline stage',\n description: 'Creates a new pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageCreateSchema },\n responses: [\n { status: 201, description: 'Stage created', schema: stageCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update pipeline stage',\n description: 'Updates an existing pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageUpdateSchema },\n responses: [\n { status: 200, description: 'Stage updated', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete pipeline stage',\n description: 'Deletes a pipeline stage. Returns 409 if active deals use this stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageDeleteSchema },\n responses: [\n { status: 200, description: 'Stage deleted', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 409, description: 'Stage has active deals', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|