@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7131.1.20abf9e885
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +5 -0
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +13 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
- package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/complete/route.js +5 -0
- package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
- package/dist/modules/customers/api/labels/assign/route.js +5 -0
- package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
- package/dist/modules/customers/api/labels/route.js +5 -0
- package/dist/modules/customers/api/labels/route.js.map +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js +5 -0
- package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
- package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +13 -0
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +5 -0
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
- package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
- package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +5 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +5 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +13 -0
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +5 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/forward/route.js +5 -0
- package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/reply/route.js +5 -0
- package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +9 -0
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/token/[token]/route.js +5 -0
- package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +5 -0
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +5 -0
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +5 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +5 -0
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +5 -0
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +5 -0
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +5 -0
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +5 -0
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +5 -0
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +5 -0
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +5 -0
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +5 -0
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +5 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
- package/src/modules/auth/api/profile/route.ts +5 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
- package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
- package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
- package/src/modules/customers/api/interactions/complete/route.ts +5 -0
- package/src/modules/customers/api/labels/assign/route.ts +5 -0
- package/src/modules/customers/api/labels/route.ts +5 -0
- package/src/modules/customers/api/labels/unassign/route.ts +5 -0
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
- package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
- package/src/modules/customers/api/pipelines/route.ts +13 -0
- package/src/modules/customers/api/settings/address-format/route.ts +5 -0
- package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
- package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
- package/src/modules/customers/api/tags/assign/route.ts +5 -0
- package/src/modules/customers/api/tags/unassign/route.ts +5 -0
- package/src/modules/customers/api/todos/route.ts +13 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
- package/src/modules/directory/api/organization-branding/route.ts +5 -0
- package/src/modules/eudr/api/plots/import/route.ts +7 -0
- package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
- package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
- package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
- package/src/modules/messages/api/[id]/forward/route.ts +5 -0
- package/src/modules/messages/api/[id]/reply/route.ts +5 -0
- package/src/modules/messages/api/[id]/route.ts +9 -0
- package/src/modules/messages/api/token/[token]/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
- package/src/modules/sales/api/quotes/accept/route.ts +5 -0
- package/src/modules/sales/api/quotes/convert/route.ts +5 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
- package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
- package/src/modules/staff/api/team-members/self/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
- package/src/modules/warranty_claims/api/ai/assess/route.ts +5 -0
- package/src/modules/warranty_claims/api/assign/route.ts +5 -0
- package/src/modules/warranty_claims/api/credit-memo/route.ts +5 -0
- package/src/modules/warranty_claims/api/events/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/events/route.ts +5 -0
- package/src/modules/warranty_claims/api/receiving/route.ts +5 -0
- package/src/modules/warranty_claims/api/replacement-order/route.ts +5 -0
- package/src/modules/warranty_claims/api/return-label/route.ts +5 -0
- package/src/modules/warranty_claims/api/sales-return/route.ts +5 -0
- package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
- package/src/modules/warranty_claims/api/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/transition/route.ts +5 -0
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
- package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/sales/api/quotes/convert/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { withScopedPayload } from '../../utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\nconst convertSchema = z.object({\n quoteId: z.string().uuid(),\n orderId: z.string().uuid().optional(),\n orderNumber: z.string().trim().max(191).optional(),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['sales.quotes.manage', 'sales.orders.manage'] },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nfunction resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nasync function runGuards(\n ctx: CommandRuntimeContext,\n input: MutationGuardInput,\n): Promise<{\n ok: boolean\n errorBody?: Record<string, unknown>\n errorStatus?: number\n afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>\n}> {\n const legacyGuard = bridgeLegacyGuard(ctx.container)\n if (!legacyGuard) {\n return { ok: true, afterSuccessCallbacks: [] }\n }\n\n return runMutationGuards([legacyGuard], input, {\n userFeatures: resolveUserFeatures(ctx.auth),\n })\n}\n\nasync function runGuardAfterSuccessCallbacks(\n callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,\n input: {\n tenantId: string\n organizationId: string | null\n userId: string\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete'\n requestMethod: string\n requestHeaders: Headers\n },\n): Promise<void> {\n for (const callback of callbacks) {\n if (!callback.guard.afterSuccess) continue\n await callback.guard.afterSuccess({\n ...input,\n metadata: callback.metadata ?? null,\n })\n }\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.documents.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.documents.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(payload ?? {}, ctx, translate)\n const input = convertSchema.parse(scoped)\n const guardResult = await runGuards(ctx, {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n resourceKind: 'sales.quote',\n resourceId: input.quoteId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<\n { quoteId: string; orderId?: string; orderNumber?: string },\n { orderId: string }\n >('sales.quotes.convert_to_order', { input, ctx })\n\n const orderId = result?.orderId ?? input.orderId ?? input.quoteId\n const jsonResponse = NextResponse.json({ orderId })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n jsonResponse.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 ?? 'sales.order',\n resourceId: logEntry.resourceId ?? orderId,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n })\n )\n }\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n resourceKind: 'sales.quote',\n resourceId: input.quoteId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return jsonResponse\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.quotes.convert failed', { err })\n return NextResponse.json(\n { error: translate('sales.documents.detail.convertError', 'Failed to convert quote.') },\n { status: 400 }\n )\n }\n}\n\nconst convertResponseSchema = z.object({\n orderId: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Convert quote to order',\n methods: {\n POST: {\n summary: 'Convert quote',\n description: 'Creates a sales order from a quote and removes the original quote record.',\n requestBody: {\n contentType: 'application/json',\n schema: convertSchema,\n },\n responses: [\n { status: 200, description: 'Conversion succeeded', schema: convertResponseSchema },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 409, description: 'Conflict detected', schema: z.object({ error: z.string(), code: z.string().optional() }) },\n { status: 423, description: 'Record locked', schema: z.object({ error: z.string(), code: z.string().optional() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAE/C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { withScopedPayload } from '../../utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('sales')\n\nconst convertSchema = z.object({\n quoteId: z.string().uuid(),\n orderId: z.string().uuid().optional(),\n orderNumber: z.string().trim().max(191).optional(),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['sales.quotes.manage', 'sales.orders.manage'] },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nfunction resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nasync function runGuards(\n ctx: CommandRuntimeContext,\n input: MutationGuardInput,\n): Promise<{\n ok: boolean\n errorBody?: Record<string, unknown>\n errorStatus?: number\n afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>\n}> {\n const legacyGuard = bridgeLegacyGuard(ctx.container)\n if (!legacyGuard) {\n return { ok: true, afterSuccessCallbacks: [] }\n }\n\n return runMutationGuards([legacyGuard], input, {\n userFeatures: resolveUserFeatures(ctx.auth),\n })\n}\n\nasync function runGuardAfterSuccessCallbacks(\n callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,\n input: {\n tenantId: string\n organizationId: string | null\n userId: string\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete'\n requestMethod: string\n requestHeaders: Headers\n },\n): Promise<void> {\n for (const callback of callbacks) {\n if (!callback.guard.afterSuccess) continue\n await callback.guard.afterSuccess({\n ...input,\n metadata: callback.metadata ?? null,\n })\n }\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.documents.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.documents.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(payload ?? {}, ctx, translate)\n const input = convertSchema.parse(scoped)\n const guardResult = await runGuards(ctx, {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n resourceKind: 'sales.quote',\n resourceId: input.quoteId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<\n { quoteId: string; orderId?: string; orderNumber?: string },\n { orderId: string }\n >('sales.quotes.convert_to_order', { input, ctx })\n\n const orderId = result?.orderId ?? input.orderId ?? input.quoteId\n const jsonResponse = NextResponse.json({ orderId })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n jsonResponse.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 ?? 'sales.order',\n resourceId: logEntry.resourceId ?? orderId,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n })\n )\n }\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n resourceKind: 'sales.quote',\n resourceId: input.quoteId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return jsonResponse\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.quotes.convert failed', { err })\n return NextResponse.json(\n { error: translate('sales.documents.detail.convertError', 'Failed to convert quote.') },\n { status: 400 }\n )\n }\n}\n\nconst convertResponseSchema = z.object({\n orderId: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Convert quote to order',\n methods: {\n POST: {\n summary: 'Convert quote',\n description: 'Creates a sales order from a quote and removes the original quote record.',\n requestBody: {\n contentType: 'application/json',\n schema: convertSchema,\n },\n responses: [\n { status: 200, description: 'Conversion succeeded', schema: convertResponseSchema },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 409, description: 'Conflict detected', schema: z.object({ error: z.string(), code: z.string().optional() }) },\n { status: 423, description: 'Record locked', schema: z.object({ error: z.string(), code: z.string().optional() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
validateCrudMutationGuard
|
|
16
16
|
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
17
17
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
18
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
18
19
|
const logger = createLogger("sales");
|
|
19
20
|
const metadata = {
|
|
20
21
|
GET: { requireAuth: true, requireFeatures: ["sales.settings.manage"] },
|
|
@@ -124,6 +125,10 @@ async function PUT(req) {
|
|
|
124
125
|
if (isCrudHttpError(err)) {
|
|
125
126
|
return NextResponse.json(err.body, { status: err.status });
|
|
126
127
|
}
|
|
128
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
129
|
+
if (interceptorRejection) {
|
|
130
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
131
|
+
}
|
|
127
132
|
const { translate } = await resolveTranslations();
|
|
128
133
|
logger.error("sales.settings.document-numbers.put failed", { err });
|
|
129
134
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/sales/api/settings/document-numbers/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 { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { salesSettingsUpsertSchema, type SalesSettingsUpsertInput } from '../../../data/validators'\nimport { loadSalesSettings } from '../../../commands/settings'\nimport { SalesDocumentNumberGenerator } from '../../../services/salesDocumentNumberGenerator'\nimport { DOCUMENT_NUMBER_TOKENS, DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'\nimport { withScopedPayload } from '../../utils'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n em: EntityManager\n generator: SalesDocumentNumberGenerator\n translate: (key: string, fallback?: string) => string\n organizationId: string\n tenantId: string\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.settings.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.settings.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n const generator = container.resolve('salesDocumentNumberGenerator') as SalesDocumentNumberGenerator\n\n return {\n ctx,\n em,\n generator,\n translate,\n tenantId: auth.tenantId,\n organizationId,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, generator, organizationId, tenantId } = await resolveSettingsContext(req)\n const record = await loadSalesSettings(em, { tenantId, organizationId })\n const sequences = await generator.peekSequences({ organizationId, tenantId })\n return NextResponse.json({\n orderNumberFormat: record?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: record?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,\n nextOrderNumber: sequences.order,\n nextQuoteNumber: sequences.quote,\n tokens: DOCUMENT_NUMBER_TOKENS,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.document-numbers.get failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.load_failed', 'Failed to load sales settings') },\n { status: 400 }\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, translate, generator, organizationId, tenantId } = await resolveSettingsContext(req)\n const payload = await readJsonSafe(req, {})\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = salesSettingsUpsertSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n SalesSettingsUpsertInput,\n {\n settingsId: string\n orderNumberFormat: string\n quoteNumberFormat: string\n nextOrderNumber: number\n nextQuoteNumber: number\n }\n >('sales.settings.save', { input, ctx })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const sequences = await generator.peekSequences({ organizationId, tenantId })\n\n return NextResponse.json({\n orderNumberFormat: result?.orderNumberFormat ?? input.orderNumberFormat,\n quoteNumberFormat: result?.quoteNumberFormat ?? input.quoteNumberFormat,\n nextOrderNumber: result?.nextOrderNumber ?? sequences.order,\n nextQuoteNumber: result?.nextQuoteNumber ?? sequences.quote,\n tokens: DOCUMENT_NUMBER_TOKENS,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.document-numbers.put failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.save_failed', 'Failed to save sales settings') },\n { status: 400 }\n )\n }\n}\n\nconst settingsResponseSchema = z.object({\n orderNumberFormat: z.string(),\n quoteNumberFormat: z.string(),\n nextOrderNumber: z.number(),\n nextQuoteNumber: z.number(),\n tokens: z\n .array(\n z.object({\n token: z.string(),\n description: z.string(),\n })\n )\n .optional(),\n})\n\nconst settingsErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Sales document numbering settings',\n methods: {\n GET: {\n summary: 'Get document numbering settings',\n responses: [\n { status: 200, description: 'Current numbering formats and counters', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Missing scope', schema: settingsErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document numbering settings',\n requestBody: {\n contentType: 'application/json',\n schema: salesSettingsUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated numbering formats and counters', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Invalid payload', schema: settingsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAGnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAE/C,SAAS,iCAAgE;AACzE,SAAS,yBAAyB;AAElC,SAAS,wBAAwB,6BAA6B,mCAAmC;AACjG,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
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 { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { salesSettingsUpsertSchema, type SalesSettingsUpsertInput } from '../../../data/validators'\nimport { loadSalesSettings } from '../../../commands/settings'\nimport { SalesDocumentNumberGenerator } from '../../../services/salesDocumentNumberGenerator'\nimport { DOCUMENT_NUMBER_TOKENS, DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'\nimport { withScopedPayload } from '../../utils'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('sales')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n em: EntityManager\n generator: SalesDocumentNumberGenerator\n translate: (key: string, fallback?: string) => string\n organizationId: string\n tenantId: string\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.settings.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.settings.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n const generator = container.resolve('salesDocumentNumberGenerator') as SalesDocumentNumberGenerator\n\n return {\n ctx,\n em,\n generator,\n translate,\n tenantId: auth.tenantId,\n organizationId,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, generator, organizationId, tenantId } = await resolveSettingsContext(req)\n const record = await loadSalesSettings(em, { tenantId, organizationId })\n const sequences = await generator.peekSequences({ organizationId, tenantId })\n return NextResponse.json({\n orderNumberFormat: record?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: record?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,\n nextOrderNumber: sequences.order,\n nextQuoteNumber: sequences.quote,\n tokens: DOCUMENT_NUMBER_TOKENS,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.document-numbers.get failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.load_failed', 'Failed to load sales settings') },\n { status: 400 }\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, translate, generator, organizationId, tenantId } = await resolveSettingsContext(req)\n const payload = await readJsonSafe(req, {})\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = salesSettingsUpsertSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n SalesSettingsUpsertInput,\n {\n settingsId: string\n orderNumberFormat: string\n quoteNumberFormat: string\n nextOrderNumber: number\n nextQuoteNumber: number\n }\n >('sales.settings.save', { input, ctx })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const sequences = await generator.peekSequences({ organizationId, tenantId })\n\n return NextResponse.json({\n orderNumberFormat: result?.orderNumberFormat ?? input.orderNumberFormat,\n quoteNumberFormat: result?.quoteNumberFormat ?? input.quoteNumberFormat,\n nextOrderNumber: result?.nextOrderNumber ?? sequences.order,\n nextQuoteNumber: result?.nextQuoteNumber ?? sequences.quote,\n tokens: DOCUMENT_NUMBER_TOKENS,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.document-numbers.put failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.save_failed', 'Failed to save sales settings') },\n { status: 400 }\n )\n }\n}\n\nconst settingsResponseSchema = z.object({\n orderNumberFormat: z.string(),\n quoteNumberFormat: z.string(),\n nextOrderNumber: z.number(),\n nextQuoteNumber: z.number(),\n tokens: z\n .array(\n z.object({\n token: z.string(),\n description: z.string(),\n })\n )\n .optional(),\n})\n\nconst settingsErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Sales document numbering settings',\n methods: {\n GET: {\n summary: 'Get document numbering settings',\n responses: [\n { status: 200, description: 'Current numbering formats and counters', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Missing scope', schema: settingsErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document numbering settings',\n requestBody: {\n contentType: 'application/json',\n schema: salesSettingsUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated numbering formats and counters', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Invalid payload', schema: settingsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { ensureSalesDictionary } from "../../../lib/dictionaries.js";
|
|
18
18
|
import { DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
|
|
19
19
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
20
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
20
21
|
const logger = createLogger("sales");
|
|
21
22
|
const metadata = {
|
|
22
23
|
GET: { requireAuth: true, requireFeatures: ["sales.settings.manage"] },
|
|
@@ -151,6 +152,10 @@ async function PUT(req) {
|
|
|
151
152
|
if (isCrudHttpError(err)) {
|
|
152
153
|
return NextResponse.json(err.body, { status: err.status });
|
|
153
154
|
}
|
|
155
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
156
|
+
if (interceptorRejection) {
|
|
157
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
158
|
+
}
|
|
154
159
|
const { translate } = await resolveTranslations();
|
|
155
160
|
logger.error("sales.settings.order-editing.put failed", { err });
|
|
156
161
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/sales/api/settings/order-editing/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 { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { salesEditingSettingsSchema, salesSettingsUpsertSchema } from '../../../data/validators'\nimport { loadSalesSettings } from '../../../commands/settings'\nimport { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'\nimport { withScopedPayload } from '../../utils'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { ensureSalesDictionary } from '../../../lib/dictionaries'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n em: EntityManager\n translate: (key: string, fallback?: string) => string\n organizationId: string\n tenantId: string\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.settings.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.settings.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n\n return {\n ctx,\n em,\n translate,\n tenantId: auth.tenantId,\n organizationId,\n }\n}\n\nasync function loadStatusOptions(em: EntityManager, tenantId: string, organizationId: string) {\n const dictionary = await ensureSalesDictionary({\n em,\n tenantId,\n organizationId,\n kind: 'order-status',\n })\n const entries = await em.find(\n DictionaryEntry,\n {\n dictionary,\n tenantId,\n organizationId,\n },\n { orderBy: { value: 'asc' } }\n )\n return entries.map((entry) => ({\n id: entry.id,\n value: entry.value ?? '',\n label: entry.label ?? entry.value ?? '',\n }))\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, organizationId, tenantId } = await resolveSettingsContext(req)\n const record = await loadSalesSettings(em, { tenantId, organizationId })\n const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)\n\n return NextResponse.json({\n orderCustomerEditableStatuses: record?.orderCustomerEditableStatuses ?? null,\n orderAddressEditableStatuses: record?.orderAddressEditableStatuses ?? null,\n orderStatuses,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.order-editing.get failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.load_failed', 'Failed to load sales settings') },\n { status: 400 }\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, translate, organizationId, tenantId, em } = await resolveSettingsContext(req)\n const payload = (await readJsonSafe<Record<string, unknown>>(req, {})) ?? {}\n const scoped = withScopedPayload(payload, ctx, translate)\n const parsed = salesEditingSettingsSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const current = await loadSalesSettings(em, { tenantId, organizationId })\n const commandInput = salesSettingsUpsertSchema.parse({\n ...parsed,\n orderNumberFormat: parsed.orderNumberFormat ?? current?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: parsed.quoteNumberFormat ?? current?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,\n orderNextNumber: payload.orderNextNumber,\n quoteNextNumber: payload.quoteNextNumber,\n })\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const response = await commandBus.execute('sales.settings.save', { input: commandInput, ctx })\n const result = (response as { result?: { orderCustomerEditableStatuses?: string[] | null; orderAddressEditableStatuses?: string[] | null } }).result\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)\n\n return NextResponse.json({\n orderCustomerEditableStatuses: result?.orderCustomerEditableStatuses ?? null,\n orderAddressEditableStatuses: result?.orderAddressEditableStatuses ?? null,\n orderStatuses,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.order-editing.put failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.save_failed', 'Failed to save sales settings') },\n { status: 400 }\n )\n }\n}\n\nconst orderStatusOptionSchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string(),\n})\n\nconst settingsResponseSchema = z.object({\n orderCustomerEditableStatuses: z.array(z.string()).nullable(),\n orderAddressEditableStatuses: z.array(z.string()).nullable(),\n orderStatuses: z.array(orderStatusOptionSchema),\n})\n\nconst settingsErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Sales order editing settings',\n methods: {\n GET: {\n summary: 'Get order editing guards',\n responses: [\n { status: 200, description: 'Current order editing guards', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Missing scope', schema: settingsErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update order editing guards',\n requestBody: {\n contentType: 'application/json',\n schema: salesEditingSettingsSchema,\n },\n responses: [\n { status: 200, description: 'Updated order editing guards', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Invalid payload', schema: settingsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAGnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAE/C,SAAS,4BAA4B,iCAAiC;AACtE,SAAS,yBAAyB;AAClC,SAAS,6BAA6B,mCAAmC;AACzE,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;
|
|
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 { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { salesEditingSettingsSchema, salesSettingsUpsertSchema } from '../../../data/validators'\nimport { loadSalesSettings } from '../../../commands/settings'\nimport { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'\nimport { withScopedPayload } from '../../utils'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { ensureSalesDictionary } from '../../../lib/dictionaries'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('sales')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n em: EntityManager\n translate: (key: string, fallback?: string) => string\n organizationId: string\n tenantId: string\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.settings.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.settings.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n\n return {\n ctx,\n em,\n translate,\n tenantId: auth.tenantId,\n organizationId,\n }\n}\n\nasync function loadStatusOptions(em: EntityManager, tenantId: string, organizationId: string) {\n const dictionary = await ensureSalesDictionary({\n em,\n tenantId,\n organizationId,\n kind: 'order-status',\n })\n const entries = await em.find(\n DictionaryEntry,\n {\n dictionary,\n tenantId,\n organizationId,\n },\n { orderBy: { value: 'asc' } }\n )\n return entries.map((entry) => ({\n id: entry.id,\n value: entry.value ?? '',\n label: entry.label ?? entry.value ?? '',\n }))\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, organizationId, tenantId } = await resolveSettingsContext(req)\n const record = await loadSalesSettings(em, { tenantId, organizationId })\n const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)\n\n return NextResponse.json({\n orderCustomerEditableStatuses: record?.orderCustomerEditableStatuses ?? null,\n orderAddressEditableStatuses: record?.orderAddressEditableStatuses ?? null,\n orderStatuses,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.order-editing.get failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.load_failed', 'Failed to load sales settings') },\n { status: 400 }\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, translate, organizationId, tenantId, em } = await resolveSettingsContext(req)\n const payload = (await readJsonSafe<Record<string, unknown>>(req, {})) ?? {}\n const scoped = withScopedPayload(payload, ctx, translate)\n const parsed = salesEditingSettingsSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const current = await loadSalesSettings(em, { tenantId, organizationId })\n const commandInput = salesSettingsUpsertSchema.parse({\n ...parsed,\n orderNumberFormat: parsed.orderNumberFormat ?? current?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: parsed.quoteNumberFormat ?? current?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,\n orderNextNumber: payload.orderNextNumber,\n quoteNextNumber: payload.quoteNextNumber,\n })\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const response = await commandBus.execute('sales.settings.save', { input: commandInput, ctx })\n const result = (response as { result?: { orderCustomerEditableStatuses?: string[] | null; orderAddressEditableStatuses?: string[] | null } }).result\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: 'sales.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)\n\n return NextResponse.json({\n orderCustomerEditableStatuses: result?.orderCustomerEditableStatuses ?? null,\n orderAddressEditableStatuses: result?.orderAddressEditableStatuses ?? null,\n orderStatuses,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('sales.settings.order-editing.put failed', { err })\n return NextResponse.json(\n { error: translate('sales.settings.errors.save_failed', 'Failed to save sales settings') },\n { status: 400 }\n )\n }\n}\n\nconst orderStatusOptionSchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string(),\n})\n\nconst settingsResponseSchema = z.object({\n orderCustomerEditableStatuses: z.array(z.string()).nullable(),\n orderAddressEditableStatuses: z.array(z.string()).nullable(),\n orderStatuses: z.array(orderStatusOptionSchema),\n})\n\nconst settingsErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Sales order editing settings',\n methods: {\n GET: {\n summary: 'Get order editing guards',\n responses: [\n { status: 200, description: 'Current order editing guards', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Missing scope', schema: settingsErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update order editing guards',\n requestBody: {\n contentType: 'application/json',\n schema: salesEditingSettingsSchema,\n },\n responses: [\n { status: 200, description: 'Updated order editing guards', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: settingsErrorSchema },\n { status: 400, description: 'Invalid payload', schema: settingsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
runStaffMutationGuards
|
|
15
15
|
} from "../../guards.js";
|
|
16
16
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
17
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
17
18
|
const logger = createLogger("staff");
|
|
18
19
|
const metadata = {
|
|
19
20
|
POST: { requireAuth: true, requireFeatures: ["staff.leave_requests.manage"] }
|
|
@@ -100,6 +101,10 @@ async function POST(req) {
|
|
|
100
101
|
if (isCrudHttpError(err)) {
|
|
101
102
|
return NextResponse.json(err.body, { status: err.status });
|
|
102
103
|
}
|
|
104
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
105
|
+
if (interceptorRejection) {
|
|
106
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
107
|
+
}
|
|
103
108
|
const { translate } = await resolveTranslations();
|
|
104
109
|
logger.error("staff.leave-requests.accept failed", { err });
|
|
105
110
|
return NextResponse.json({ error: translate("staff.leaveRequests.errors.accept", "Failed to approve leave request.") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/leave-requests/accept/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.accept',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\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 ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.accept failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.accept', 'Failed to approve leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Approve leave request',\n methods: {\n POST: {\n summary: 'Approve leave request',\n description: 'Approves a leave request and adds unavailability rules for the staff member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request approved', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,uCAA4E;AACrF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.accept',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\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 ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.accept failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.accept', 'Failed to approve leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Approve leave request',\n methods: {\n POST: {\n summary: 'Approve leave request',\n description: 'Approves a leave request and adds unavailability rules for the staff member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request approved', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,uCAA4E;AACrF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,0CAA0C;AAEnD,MAAM,SAAS,aAAa,OAAO;AAE5B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAC9E;AAEA,eAAe,aACb,KACgG;AAChG,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AACzG,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,MAA6B;AAAA,IACjC;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,wBAAwB,OAAO,cAAc,KAAK,SAAS;AAAA,IAC3D,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,IAClE,SAAS;AAAA,EACX;AACA,SAAO,EAAE,KAAK,UAAU;AAC1B;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,KAAK,UAAU,IAAI,MAAM,aAAa,GAAG;AACjD,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,QAAQ,wBAAwB,iCAAiC,MAAM,KAAK,SAAS;AAE3F,UAAM,OAAO,IAAI;AACjB,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,iBAAiB,IAAI,0BAA0B;AACrD,UAAM,cAAc,MAAM;AAAA,MACxB,IAAI;AAAA,MACJ;AAAA,QACE;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,OAAO;AAAA,QACrB,cAAc;AAAA,QACd,YAAY,MAAM;AAAA,QAClB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,iBAAiB;AAAA,MACnB;AAAA,MACA,oBAAoB,IAAI;AAAA,IAC1B;AACA,QAAI,CAAC,YAAY,IAAI;AACnB,aAAO,aAAa;AAAA,QAClB,YAAY,aAAa,EAAE,OAAO,6BAA6B;AAAA,QAC/D,EAAE,QAAQ,YAAY,eAAe,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,aAAc,IAAI,UAAU,QAAQ,YAAY;AACtD,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW;AAAA,MAC5C;AAAA,MACA,EAAE,OAAO,IAAI;AAAA,IACf;AAEA,QAAI,YAAY,sBAAsB,QAAQ;AAC5C,YAAM,kCAAkC,YAAY,uBAAuB;AAAA,QACzE;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,OAAO;AAAA,QACrB,cAAc;AAAA,QACd,YAAY,QAAQ,aAAa,MAAM;AAAA,QACvC,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,MAAM,IAAI,QAAQ,aAAa,KAAK,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC/F,QAAI,UAAU,aAAa,UAAU,MAAM,UAAU,WAAW;AAC9D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY,SAAS,cAAc,QAAQ,aAAa;AAAA,UACxD,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAAI;AAAA,QACtF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,MAAM,sCAAsC,EAAE,IAAI,CAAC;AAC1D,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,qCAAqC,kCAAkC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzI;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE;AAAA,QAClI,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACvF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
runStaffMutationGuards
|
|
15
15
|
} from "../../guards.js";
|
|
16
16
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
17
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
17
18
|
const logger = createLogger("staff");
|
|
18
19
|
const metadata = {
|
|
19
20
|
POST: { requireAuth: true, requireFeatures: ["staff.leave_requests.manage"] }
|
|
@@ -100,6 +101,10 @@ async function POST(req) {
|
|
|
100
101
|
if (isCrudHttpError(err)) {
|
|
101
102
|
return NextResponse.json(err.body, { status: err.status });
|
|
102
103
|
}
|
|
104
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
105
|
+
if (interceptorRejection) {
|
|
106
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
107
|
+
}
|
|
103
108
|
const { translate } = await resolveTranslations();
|
|
104
109
|
logger.error("staff.leave-requests.reject failed", { err });
|
|
105
110
|
return NextResponse.json({ error: translate("staff.leaveRequests.errors.reject", "Failed to reject leave request.") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/leave-requests/reject/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.reject',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\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 ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.reject failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.reject', 'Failed to reject leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Reject leave request',\n methods: {\n POST: {\n summary: 'Reject leave request',\n description: 'Rejects a leave request with an optional comment.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request rejected', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,uCAA4E;AACrF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.reject',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\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 ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.reject failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.reject', 'Failed to reject leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Reject leave request',\n methods: {\n POST: {\n summary: 'Reject leave request',\n description: 'Rejects a leave request with an optional comment.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request rejected', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
runStaffMutationGuards
|
|
19
19
|
} from "../../guards.js";
|
|
20
20
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
21
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
21
22
|
const logger = createLogger("staff");
|
|
22
23
|
const metadata = {
|
|
23
24
|
GET: { requireAuth: true, requireFeatures: ["staff.leave_requests.send"] },
|
|
@@ -167,6 +168,10 @@ async function POST(req) {
|
|
|
167
168
|
if (isCrudHttpError(err)) {
|
|
168
169
|
return NextResponse.json(err.body, { status: err.status });
|
|
169
170
|
}
|
|
171
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
172
|
+
if (interceptorRejection) {
|
|
173
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
174
|
+
}
|
|
170
175
|
const { translate } = await resolveTranslations();
|
|
171
176
|
logger.error("staff.teamMembers.self.create failed", { err });
|
|
172
177
|
return NextResponse.json({ error: translate("staff.teamMembers.form.errors.create", "Failed to create team member.") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/team-members/self/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember } from '../../../data/entities'\nimport {\n staffTeamMemberSelfCreateSchema,\n type StaffTeamMemberSelfCreateInput,\n type StaffTeamMemberCreateInput,\n} from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nconst selfMemberResponseSchema = z.object({\n member: z\n .object({\n id: z.string().uuid(),\n displayName: z.string(),\n userId: z.string().uuid().nullable(),\n teamId: z.string().uuid().nullable(),\n availabilityRuleSetId: z.string().uuid().nullable(),\n })\n .nullable(),\n})\n\nexport async function GET(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n const auth = ctx.auth\n if (!auth?.sub) return NextResponse.json({ member: null })\n const em = (ctx.container.resolve('em') as any)\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n if (!member) return NextResponse.json({ member: null })\n return NextResponse.json({\n member: {\n id: member.id,\n displayName: member.displayName,\n userId: member.userId ?? null,\n teamId: member.teamId ?? null,\n availabilityRuleSetId: member.availabilityRuleSetId ?? null,\n },\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n logger.error('staff.teamMembers.self.load failed', { err })\n return NextResponse.json({ member: null })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const auth = ctx.auth\n if (!auth?.sub) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const body = await req.json().catch(() => ({}))\n const parsed = parseScopedCommandInput(staffTeamMemberSelfCreateSchema, body, ctx, translate)\n const em = (ctx.container.resolve('em') as any)\n const existing = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n if (existing) {\n throw new CrudHttpError(409, { error: translate('staff.teamMembers.self.exists', 'Team member profile already exists.') })\n }\n\n const tenantId = auth.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: 'staff.team_member',\n resourceId: auth.sub,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const selfInput: StaffTeamMemberCreateInput = {\n ...parsed,\n userId: auth.sub,\n teamId: null,\n roleIds: [],\n tags: [],\n availabilityRuleSetId: null,\n isActive: true,\n }\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberCreateInput, { memberId: string }>(\n 'staff.team-members.create',\n {\n input: selfInput,\n ctx,\n },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? auth.sub,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? 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 ?? 'staff.team_member',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.teamMembers.self.create failed', { err })\n return NextResponse.json({ error: translate('staff.teamMembers.form.errors.create', 'Failed to create team member.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Self team member profile',\n methods: {\n GET: {\n summary: 'Get current user team member profile',\n description: 'Returns the staff team member linked to the current user, if any.',\n responses: [\n { status: 200, description: 'Team member profile', schema: selfMemberResponseSchema },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n ],\n },\n POST: {\n summary: 'Create current user team member profile',\n description: 'Creates a team member profile for the signed-in user.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberSelfCreateSchema,\n },\n responses: [\n { status: 201, description: 'Team member created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 409, description: 'Already exists', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC,SAAS,6BAA6B;AAEtC,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember } from '../../../data/entities'\nimport {\n staffTeamMemberSelfCreateSchema,\n type StaffTeamMemberSelfCreateInput,\n type StaffTeamMemberCreateInput,\n} from '../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nconst selfMemberResponseSchema = z.object({\n member: z\n .object({\n id: z.string().uuid(),\n displayName: z.string(),\n userId: z.string().uuid().nullable(),\n teamId: z.string().uuid().nullable(),\n availabilityRuleSetId: z.string().uuid().nullable(),\n })\n .nullable(),\n})\n\nexport async function GET(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n const auth = ctx.auth\n if (!auth?.sub) return NextResponse.json({ member: null })\n const em = (ctx.container.resolve('em') as any)\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n if (!member) return NextResponse.json({ member: null })\n return NextResponse.json({\n member: {\n id: member.id,\n displayName: member.displayName,\n userId: member.userId ?? null,\n teamId: member.teamId ?? null,\n availabilityRuleSetId: member.availabilityRuleSetId ?? null,\n },\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n logger.error('staff.teamMembers.self.load failed', { err })\n return NextResponse.json({ member: null })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const auth = ctx.auth\n if (!auth?.sub) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const body = await req.json().catch(() => ({}))\n const parsed = parseScopedCommandInput(staffTeamMemberSelfCreateSchema, body, ctx, translate)\n const em = (ctx.container.resolve('em') as any)\n const existing = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n if (existing) {\n throw new CrudHttpError(409, { error: translate('staff.teamMembers.self.exists', 'Team member profile already exists.') })\n }\n\n const tenantId = auth.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: 'staff.team_member',\n resourceId: auth.sub,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const selfInput: StaffTeamMemberCreateInput = {\n ...parsed,\n userId: auth.sub,\n teamId: null,\n roleIds: [],\n tags: [],\n availabilityRuleSetId: null,\n isActive: true,\n }\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberCreateInput, { memberId: string }>(\n 'staff.team-members.create',\n {\n input: selfInput,\n ctx,\n },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? auth.sub,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? 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 ?? 'staff.team_member',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.teamMembers.self.create failed', { err })\n return NextResponse.json({ error: translate('staff.teamMembers.form.errors.create', 'Failed to create team member.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Self team member profile',\n methods: {\n GET: {\n summary: 'Get current user team member profile',\n description: 'Returns the staff team member linked to the current user, if any.',\n responses: [\n { status: 200, description: 'Team member profile', schema: selfMemberResponseSchema },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n ],\n },\n POST: {\n summary: 'Create current user team member profile',\n description: 'Creates a team member profile for the signed-in user.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberSelfCreateSchema,\n },\n responses: [\n { status: 201, description: 'Team member created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 409, description: 'Already exists', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
runStaffMutationGuards
|
|
17
17
|
} from "../../../guards.js";
|
|
18
18
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
19
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
19
20
|
const logger = createLogger("staff");
|
|
20
21
|
const metadata = {
|
|
21
22
|
POST: { requireAuth: true, requireFeatures: ["staff.manage_team"] }
|
|
@@ -102,6 +103,10 @@ async function POST(req) {
|
|
|
102
103
|
if (isCrudHttpError(err)) {
|
|
103
104
|
return NextResponse.json(err.body, { status: err.status });
|
|
104
105
|
}
|
|
106
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
107
|
+
if (interceptorRejection) {
|
|
108
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
109
|
+
}
|
|
105
110
|
const { translate } = await resolveTranslations();
|
|
106
111
|
logger.error("staff.teamMembers.tags.assign failed", { err });
|
|
107
112
|
return NextResponse.json({ error: translate("staff.teamMembers.tags.updateError", "Failed to update tags.") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/staff/api/team-members/tags/assign/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.assign',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? 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 ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.teamMembers.tags.assign failed', { err })\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Assign team member tag',\n methods: {\n POST: {\n summary: 'Assign team member tag',\n description: 'Assigns a tag to a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 201, description: 'Tag assignment created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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('staff.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 return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.assign',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? 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 ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.teamMembers.tags.assign failed', { err })\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Assign team member tag',\n methods: {\n POST: {\n summary: 'Assign team member tag',\n description: 'Assigns a tag to a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 201, description: 'Tag assignment created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,0CAA0C;AAEnD,MAAM,SAAS,aAAa,OAAO;AAE5B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAe,aACb,KACgG;AAChG,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AACzG,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,MAA6B;AAAA,IACjC;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,wBAAwB,OAAO,cAAc,KAAK,SAAS;AAAA,IAC3D,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,IAClE,SAAS;AAAA,EACX;AACA,SAAO,EAAE,KAAK,UAAU;AAC1B;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,KAAK,UAAU,IAAI,MAAM,aAAa,GAAG;AACjD,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,QAAQ,wBAAwB,oCAAoC,MAAM,KAAK,SAAS;AAE9F,UAAM,OAAO,IAAI;AACjB,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,iBAAiB,IAAI,0BAA0B;AACrD,UAAM,cAAc,MAAM;AAAA,MACxB,IAAI;AAAA,MACJ;AAAA,QACE;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,OAAO;AAAA,QACrB,cAAc;AAAA,QACd,YAAY,MAAM;AAAA,QAClB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,iBAAiB;AAAA,MACnB;AAAA,MACA,oBAAoB,IAAI;AAAA,IAC1B;AACA,QAAI,CAAC,YAAY,IAAI;AACnB,aAAO,aAAa;AAAA,QAClB,YAAY,aAAa,EAAE,OAAO,6BAA6B;AAAA,QAC/D,EAAE,QAAQ,YAAY,eAAe,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,aAAc,IAAI,UAAU,QAAQ,YAAY;AACtD,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW;AAAA,MAC5C;AAAA,MACA,EAAE,OAAO,IAAI;AAAA,IACf;AAEA,QAAI,YAAY,sBAAsB,QAAQ;AAC5C,YAAM,kCAAkC,YAAY,uBAAuB;AAAA,QACzE;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,OAAO;AAAA,QACrB,cAAc;AAAA,QACd,YAAY,QAAQ,YAAY,MAAM;AAAA,QACtC,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,QAAQ,YAAY,KAAK,GAAG,EAAE,QAAQ,IAAI,CAAC;AACpF,QAAI,UAAU,aAAa,UAAU,MAAM,UAAU,WAAW;AAC9D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY,SAAS,cAAc,QAAQ,YAAY;AAAA,UACvD,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAAI;AAAA,QACtF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,MAAM,wCAAwC,EAAE,IAAI,CAAC;AAC5D,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,sCAAsC,wBAAwB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChI;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE;AAAA,QAC7G,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACvF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
runStaffMutationGuards
|
|
17
17
|
} from "../../../guards.js";
|
|
18
18
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
19
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
19
20
|
const logger = createLogger("staff");
|
|
20
21
|
const metadata = {
|
|
21
22
|
POST: { requireAuth: true, requireFeatures: ["staff.manage_team"] }
|
|
@@ -102,6 +103,10 @@ async function POST(req) {
|
|
|
102
103
|
if (isCrudHttpError(err)) {
|
|
103
104
|
return NextResponse.json(err.body, { status: err.status });
|
|
104
105
|
}
|
|
106
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
107
|
+
if (interceptorRejection) {
|
|
108
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
109
|
+
}
|
|
105
110
|
const { translate } = await resolveTranslations();
|
|
106
111
|
logger.error("staff.teamMembers.tags.unassign failed", { err });
|
|
107
112
|
return NextResponse.json({ error: translate("staff.teamMembers.tags.updateError", "Failed to update tags.") }, { status: 400 });
|