@open-mercato/core 0.7.1-develop.7123.1.25365e718d → 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/.turbo/turbo-build.log +1 -1
- 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/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +5 -117
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -3
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +33 -9
- package/dist/modules/customers/commands/deals.js.map +3 -3
- package/dist/modules/customers/components/AddressEditor.js +71 -1
- package/dist/modules/customers/components/AddressEditor.js.map +2 -2
- package/dist/modules/customers/utils/addressFormat.js +18 -2
- package/dist/modules/customers/utils/addressFormat.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/sales/components/documents/AddressesSection.js +17 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -24
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/normalizeAddressDraft.js +29 -0
- package/dist/modules/sales/components/documents/normalizeAddressDraft.js.map +7 -0
- 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/customers/backend/customers/deals/[id]/hooks/types.ts +0 -23
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +11 -172
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -1
- package/src/modules/customers/commands/deals.ts +84 -9
- package/src/modules/customers/components/AddressEditor.tsx +116 -1
- package/src/modules/customers/i18n/de.json +6 -0
- package/src/modules/customers/i18n/en.json +6 -0
- package/src/modules/customers/i18n/es.json +6 -0
- package/src/modules/customers/i18n/ko.json +6 -0
- package/src/modules/customers/i18n/pl.json +6 -0
- package/src/modules/customers/utils/addressFormat.tsx +59 -1
- 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/sales/components/documents/AddressesSection.tsx +48 -5
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +5 -23
- package/src/modules/sales/components/documents/normalizeAddressDraft.ts +29 -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
|
@@ -4,6 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
7
8
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
8
9
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
9
10
|
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
@@ -266,6 +267,10 @@ async function POST(req) {
|
|
|
266
267
|
return NextResponse.json({ status: "ok", extraction });
|
|
267
268
|
} catch (err) {
|
|
268
269
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
270
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
271
|
+
if (interceptorRejection) {
|
|
272
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
273
|
+
}
|
|
269
274
|
if (isWarrantyAiNotConfiguredError(err)) {
|
|
270
275
|
return NextResponse.json({ status: "notConfigured" });
|
|
271
276
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/warranty_claims/api/ai/assess/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest, type AuthContext } 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 { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { AiChatRequestContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/attachment-bridge-types'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { AttachmentTargetAccessService } from '@open-mercato/core/modules/attachments/lib/target-access-service'\nimport { WarrantyClaimLine } from '../../../data/entities'\nimport {\n assessDamagePhoto,\n extractProofOfPurchase,\n isWarrantyAiNotConfiguredError,\n isWarrantyAiUnavailableError,\n} from '../../../lib/aiAssist'\nimport {\n WARRANTY_CLAIM_LINE_RESOURCE_KIND,\n requireScopedClaim,\n type WarrantyClaimScope,\n} from '../../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst uuid = z.string().uuid()\nconst optimisticLockTokenSchema = z.union([z.string().datetime(), z.date()]).nullable().optional()\nconst CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'\nconst LINE_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim_line'\n\nconst assessBodySchema = z\n .object({\n claimId: uuid,\n lineId: uuid.optional(),\n attachmentId: uuid,\n kind: z.enum(['damage', 'proof']),\n organizationId: uuid.optional(),\n tenantId: uuid.optional(),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\ntype AssessBodyInput = z.infer<typeof assessBodySchema>\n\ntype AssessRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n container: AwilixContainer\n em: EntityManager\n translate: (key: string, fallback?: string) => string\n}\n\ntype SetAssessmentCommandInput = {\n id: string\n organizationId: string\n tenantId: string\n assessmentPayload: Record<string, unknown>\n updatedAt?: string | Date | null\n}\n\nconst damageAssessmentSchema = z.object({\n damageType: z.string(),\n severity: z.enum(['minor', 'moderate', 'severe', 'unknown']),\n probableCause: z.string(),\n misuseSuspected: z.boolean(),\n confidence: z.number(),\n summary: z.string(),\n})\n\nconst proofExtractionSchema = z.object({\n purchaseDate: z.string().nullable(),\n serialNumber: z.string().nullable(),\n amount: z.string().nullable(),\n currency: z.string().nullable(),\n merchant: z.string().nullable(),\n confidence: z.number(),\n})\n\nconst assessResponseSchema = z.union([\n z.object({\n status: z.literal('ok'),\n assessment: damageAssessmentSchema.optional(),\n extraction: proofExtractionSchema.optional(),\n }),\n z.object({ status: z.literal('notConfigured') }),\n z.object({ status: z.literal('aiUnavailable') }),\n])\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction isSuperAdmin(auth: CommandRuntimeContext['auth']): boolean {\n return Boolean(auth && toRecord(auth).isSuperAdmin === true)\n}\n\nfunction buildAuthContext(context: AssessRouteContext): AiChatRequestContext {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId,\n features: [],\n isSuperAdmin: isSuperAdmin(context.ctx.auth),\n }\n}\n\nfunction buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthContext, null> {\n const auth = context.ctx.auth\n if (!auth?.sub) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return {\n ...auth,\n sub: auth.sub,\n tenantId: context.tenantId,\n orgId: context.organizationId,\n }\n}\n\nasync function resolveAssessContext(req: Request): Promise<AssessRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\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, { error: 'warranty_claims.errors.organization_required' })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n container,\n em: container.resolve<EntityManager>('em').fork(),\n translate: (key: string) => key,\n }\n}\n\nfunction toAssessInput(payload: Record<string, unknown>, context: AssessRouteContext): AssessBodyInput {\n return assessBodySchema.parse(withScopedPayload(payload, context.ctx, context.translate))\n}\n\nasync function runGuard(\n req: Request,\n context: AssessRouteContext,\n input: AssessBodyInput & { lineId: string },\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_LINE_RESOURCE_KIND,\n resourceId: input.lineId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nasync function loadScopedLine(\n em: EntityManager,\n scope: WarrantyClaimScope,\n claimId: string,\n lineId: string,\n): Promise<WarrantyClaimLine> {\n const line = await findOneWithDecryption(\n em,\n WarrantyClaimLine,\n {\n id: lineId,\n claim: claimId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n {},\n scope,\n )\n if (!line) {\n throw new CrudHttpError(404, { error: 'warranty_claims.errors.notFound' })\n }\n return line\n}\n\nasync function verifyAttachmentLinkedToTarget(\n context: AssessRouteContext,\n input: { attachmentId: string; claimId: string; lineId?: string | null },\n): Promise<void> {\n let service: AttachmentTargetAccessService | null = null\n try {\n service = context.container.resolve<AttachmentTargetAccessService>('attachmentTargetAccessService')\n } catch {\n service = null\n }\n const linked = service ? await service.canAccessLinkedTarget({\n attachmentId: input.attachmentId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n auth: buildAttachmentAuthContext(context),\n targets: [\n { entityId: CLAIM_ATTACHMENT_ENTITY_ID, recordId: input.claimId },\n ...(input.lineId ? [{ entityId: LINE_ATTACHMENT_ENTITY_ID, recordId: input.lineId }] : []),\n ],\n }) : false\n if (!linked) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.attachmentNotLinked' })\n }\n}\n\nfunction mergeAssessmentPayload(\n current: Record<string, unknown> | null | undefined,\n key: 'damage' | 'proof',\n value: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n ...(current ?? {}),\n [key]: value,\n generatedAt: new Date().toISOString(),\n }\n}\n\nasync function persistAssessmentPayload(\n context: AssessRouteContext,\n line: WarrantyClaimLine,\n key: 'damage' | 'proof',\n value: Record<string, unknown>,\n updatedAt: string | Date | null | undefined,\n): Promise<void> {\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const assessmentPayload = mergeAssessmentPayload(line.assessmentPayload ?? null, key, value)\n await commandBus.execute<SetAssessmentCommandInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.set_assessment',\n {\n input: {\n id: line.id,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n assessmentPayload,\n updatedAt,\n },\n ctx: context.ctx,\n },\n )\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveAssessContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const parsed = toAssessInput(payload, context)\n if (parsed.kind === 'damage' && !parsed.lineId) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })\n }\n\n const guarded = parsed.lineId ? await runGuard(req, context, parsed as AssessBodyInput & { lineId: string }) : null\n if (guarded && !guarded.ok) {\n return guarded.response\n }\n const input = guarded?.modifiedPayload ? toAssessInput(guarded.modifiedPayload, context) : parsed\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n\n if (input.kind === 'damage') {\n if (!input.lineId) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })\n }\n const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId)\n await verifyAttachmentLinkedToTarget(context, {\n attachmentId: input.attachmentId,\n claimId: input.claimId,\n lineId: input.lineId,\n })\n const assessment = await assessDamagePhoto({\n em: context.em,\n container: context.container,\n scope,\n claimId: input.claimId,\n lineId: input.lineId,\n attachmentId: input.attachmentId,\n authContext: buildAuthContext(context),\n })\n await persistAssessmentPayload(context, line, 'damage', assessment, input.updatedAt)\n await guarded?.runAfterSuccess()\n return NextResponse.json({ status: 'ok', assessment })\n }\n\n await requireScopedClaim(context.em, input.claimId, scope)\n const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId) : null\n await verifyAttachmentLinkedToTarget(context, {\n attachmentId: input.attachmentId,\n claimId: input.claimId,\n lineId: input.lineId ?? null,\n })\n const extraction = await extractProofOfPurchase({\n em: context.em,\n container: context.container,\n scope,\n attachmentId: input.attachmentId,\n authContext: buildAuthContext(context),\n })\n if (line) {\n await persistAssessmentPayload(context, line, 'proof', extraction, input.updatedAt)\n await guarded?.runAfterSuccess()\n }\n return NextResponse.json({ status: 'ok', extraction })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (isWarrantyAiNotConfiguredError(err)) {\n return NextResponse.json({ status: 'notConfigured' })\n }\n if (isWarrantyAiUnavailableError(err)) {\n return NextResponse.json({ status: 'aiUnavailable' })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n logger.error('warranty_claims.ai.assess.post failed', { err })\n return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Assess warranty claim attachment with AI',\n methods: {\n POST: {\n summary: 'Assess a damage photo or extract proof-of-purchase facts',\n requestBody: { contentType: 'application/json', schema: assessBodySchema },\n responses: [\n {\n status: 200,\n description: 'Assessment completed or AI gracefully degraded',\n schema: assessResponseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest, type AuthContext } 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 { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { AiChatRequestContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/attachment-bridge-types'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { AttachmentTargetAccessService } from '@open-mercato/core/modules/attachments/lib/target-access-service'\nimport { WarrantyClaimLine } from '../../../data/entities'\nimport {\n assessDamagePhoto,\n extractProofOfPurchase,\n isWarrantyAiNotConfiguredError,\n isWarrantyAiUnavailableError,\n} from '../../../lib/aiAssist'\nimport {\n WARRANTY_CLAIM_LINE_RESOURCE_KIND,\n requireScopedClaim,\n type WarrantyClaimScope,\n} from '../../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst uuid = z.string().uuid()\nconst optimisticLockTokenSchema = z.union([z.string().datetime(), z.date()]).nullable().optional()\nconst CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'\nconst LINE_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim_line'\n\nconst assessBodySchema = z\n .object({\n claimId: uuid,\n lineId: uuid.optional(),\n attachmentId: uuid,\n kind: z.enum(['damage', 'proof']),\n organizationId: uuid.optional(),\n tenantId: uuid.optional(),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\ntype AssessBodyInput = z.infer<typeof assessBodySchema>\n\ntype AssessRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n container: AwilixContainer\n em: EntityManager\n translate: (key: string, fallback?: string) => string\n}\n\ntype SetAssessmentCommandInput = {\n id: string\n organizationId: string\n tenantId: string\n assessmentPayload: Record<string, unknown>\n updatedAt?: string | Date | null\n}\n\nconst damageAssessmentSchema = z.object({\n damageType: z.string(),\n severity: z.enum(['minor', 'moderate', 'severe', 'unknown']),\n probableCause: z.string(),\n misuseSuspected: z.boolean(),\n confidence: z.number(),\n summary: z.string(),\n})\n\nconst proofExtractionSchema = z.object({\n purchaseDate: z.string().nullable(),\n serialNumber: z.string().nullable(),\n amount: z.string().nullable(),\n currency: z.string().nullable(),\n merchant: z.string().nullable(),\n confidence: z.number(),\n})\n\nconst assessResponseSchema = z.union([\n z.object({\n status: z.literal('ok'),\n assessment: damageAssessmentSchema.optional(),\n extraction: proofExtractionSchema.optional(),\n }),\n z.object({ status: z.literal('notConfigured') }),\n z.object({ status: z.literal('aiUnavailable') }),\n])\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction isSuperAdmin(auth: CommandRuntimeContext['auth']): boolean {\n return Boolean(auth && toRecord(auth).isSuperAdmin === true)\n}\n\nfunction buildAuthContext(context: AssessRouteContext): AiChatRequestContext {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId,\n features: [],\n isSuperAdmin: isSuperAdmin(context.ctx.auth),\n }\n}\n\nfunction buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthContext, null> {\n const auth = context.ctx.auth\n if (!auth?.sub) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return {\n ...auth,\n sub: auth.sub,\n tenantId: context.tenantId,\n orgId: context.organizationId,\n }\n}\n\nasync function resolveAssessContext(req: Request): Promise<AssessRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\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, { error: 'warranty_claims.errors.organization_required' })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n container,\n em: container.resolve<EntityManager>('em').fork(),\n translate: (key: string) => key,\n }\n}\n\nfunction toAssessInput(payload: Record<string, unknown>, context: AssessRouteContext): AssessBodyInput {\n return assessBodySchema.parse(withScopedPayload(payload, context.ctx, context.translate))\n}\n\nasync function runGuard(\n req: Request,\n context: AssessRouteContext,\n input: AssessBodyInput & { lineId: string },\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_LINE_RESOURCE_KIND,\n resourceId: input.lineId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nasync function loadScopedLine(\n em: EntityManager,\n scope: WarrantyClaimScope,\n claimId: string,\n lineId: string,\n): Promise<WarrantyClaimLine> {\n const line = await findOneWithDecryption(\n em,\n WarrantyClaimLine,\n {\n id: lineId,\n claim: claimId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n {},\n scope,\n )\n if (!line) {\n throw new CrudHttpError(404, { error: 'warranty_claims.errors.notFound' })\n }\n return line\n}\n\nasync function verifyAttachmentLinkedToTarget(\n context: AssessRouteContext,\n input: { attachmentId: string; claimId: string; lineId?: string | null },\n): Promise<void> {\n let service: AttachmentTargetAccessService | null = null\n try {\n service = context.container.resolve<AttachmentTargetAccessService>('attachmentTargetAccessService')\n } catch {\n service = null\n }\n const linked = service ? await service.canAccessLinkedTarget({\n attachmentId: input.attachmentId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n auth: buildAttachmentAuthContext(context),\n targets: [\n { entityId: CLAIM_ATTACHMENT_ENTITY_ID, recordId: input.claimId },\n ...(input.lineId ? [{ entityId: LINE_ATTACHMENT_ENTITY_ID, recordId: input.lineId }] : []),\n ],\n }) : false\n if (!linked) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.attachmentNotLinked' })\n }\n}\n\nfunction mergeAssessmentPayload(\n current: Record<string, unknown> | null | undefined,\n key: 'damage' | 'proof',\n value: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n ...(current ?? {}),\n [key]: value,\n generatedAt: new Date().toISOString(),\n }\n}\n\nasync function persistAssessmentPayload(\n context: AssessRouteContext,\n line: WarrantyClaimLine,\n key: 'damage' | 'proof',\n value: Record<string, unknown>,\n updatedAt: string | Date | null | undefined,\n): Promise<void> {\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const assessmentPayload = mergeAssessmentPayload(line.assessmentPayload ?? null, key, value)\n await commandBus.execute<SetAssessmentCommandInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.set_assessment',\n {\n input: {\n id: line.id,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n assessmentPayload,\n updatedAt,\n },\n ctx: context.ctx,\n },\n )\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveAssessContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const parsed = toAssessInput(payload, context)\n if (parsed.kind === 'damage' && !parsed.lineId) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })\n }\n\n const guarded = parsed.lineId ? await runGuard(req, context, parsed as AssessBodyInput & { lineId: string }) : null\n if (guarded && !guarded.ok) {\n return guarded.response\n }\n const input = guarded?.modifiedPayload ? toAssessInput(guarded.modifiedPayload, context) : parsed\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n\n if (input.kind === 'damage') {\n if (!input.lineId) {\n throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })\n }\n const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId)\n await verifyAttachmentLinkedToTarget(context, {\n attachmentId: input.attachmentId,\n claimId: input.claimId,\n lineId: input.lineId,\n })\n const assessment = await assessDamagePhoto({\n em: context.em,\n container: context.container,\n scope,\n claimId: input.claimId,\n lineId: input.lineId,\n attachmentId: input.attachmentId,\n authContext: buildAuthContext(context),\n })\n await persistAssessmentPayload(context, line, 'damage', assessment, input.updatedAt)\n await guarded?.runAfterSuccess()\n return NextResponse.json({ status: 'ok', assessment })\n }\n\n await requireScopedClaim(context.em, input.claimId, scope)\n const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId) : null\n await verifyAttachmentLinkedToTarget(context, {\n attachmentId: input.attachmentId,\n claimId: input.claimId,\n lineId: input.lineId ?? null,\n })\n const extraction = await extractProofOfPurchase({\n em: context.em,\n container: context.container,\n scope,\n attachmentId: input.attachmentId,\n authContext: buildAuthContext(context),\n })\n if (line) {\n await persistAssessmentPayload(context, line, 'proof', extraction, input.updatedAt)\n await guarded?.runAfterSuccess()\n }\n return NextResponse.json({ status: 'ok', extraction })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (isWarrantyAiNotConfiguredError(err)) {\n return NextResponse.json({ status: 'notConfigured' })\n }\n if (isWarrantyAiUnavailableError(err)) {\n return NextResponse.json({ status: 'aiUnavailable' })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n logger.error('warranty_claims.ai.assess.post failed', { err })\n return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Assess warranty claim attachment with AI',\n methods: {\n POST: {\n summary: 'Assess a damage photo or extract proof-of-purchase facts',\n requestBody: { contentType: 'application/json', schema: assessBodySchema },\n responses: [\n {\n status: 200,\n description: 'Assessment completed or AI gracefully degraded',\n schema: assessResponseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["line"]
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
8
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
9
10
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
10
11
|
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
@@ -91,6 +92,10 @@ async function POST(req) {
|
|
|
91
92
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id });
|
|
92
93
|
} catch (err) {
|
|
93
94
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
95
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
96
|
+
if (interceptorRejection) {
|
|
97
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
98
|
+
}
|
|
94
99
|
const { translate } = await resolveTranslations();
|
|
95
100
|
if (err instanceof z.ZodError) {
|
|
96
101
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/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 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 { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { assignClaimInputSchema, type AssignClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\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('warranty_claims.errors.unauthorized', 'Unauthorized') })\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, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toAssignInput(scopedPayload: Record<string, unknown>): AssignClaimInput {\n return assignClaimInputSchema.parse({\n id: scopedPayload.id,\n assigneeUserId: scopedPayload.assigneeUserId,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: AssignClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toAssignInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toAssignInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<AssignClaimInput, { claimId: string }>(\n 'warranty_claims.claim.assign',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.assign.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Assign warranty claim',\n methods: {\n POST: {\n summary: 'Assign or unassign a claim',\n requestBody: { contentType: 'application/json', schema: assignClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Claim assigned',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAC7B,SAAS,8BAA6D;AACtE,SAAS,yBAAyB;AAElC,SAAS,8BAAqD;AAC9D,SAAS,oCAAoC;AAC7C,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,iBAAiB;AAStC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,8BAA8B,EAAE;AAC/E;AAEA,SAAS,SAAS,OAAyC;AACzD,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAmC,CAAC;AAC3G;AAEA,eAAe,qBAAqB,KAA2C;AAC7E,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,EAC1G;AACA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAC1D,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,gDAAgD,kCAAkC,EAAE,CAAC;AAAA,EACvI;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,MACnB,wBAAwB;AAAA,MACxB,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,MAClE,SAAS;AAAA,IACX;AAAA,IACA,UAAU,KAAK;AAAA,IACf;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,cAAc,eAA0D;AAC/E,SAAO,uBAAuB,MAAM;AAAA,IAClC,IAAI,cAAc;AAAA,IAClB,gBAAgB,cAAc;AAAA,EAChC,CAAC;AACH;AAEA,eAAe,SACb,KACA,SACA,OACmC;AACnC,QAAM,SAAS,QAAQ,IAAI,MAAM;AACjC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,EAClH;AACA,SAAO,uBAAuB;AAAA,IAC5B,WAAW,QAAQ,IAAI;AAAA,IACvB;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,IAC1B;AAAA,IACA,OAAO;AAAA,MACL,cAAc;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,WAAW;AAAA,MACX,iBAAiB,EAAE,GAAG,MAAM;AAAA,IAC9B;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,UAAM,UAAU,SAAS,MAAM,aAAa,KAAK,CAAC,CAAC,CAAC;AACpD,UAAM,gBAAgB,kBAAkB,SAAS,QAAQ,KAAK,QAAQ,SAAS;AAC/E,UAAM,QAAQ,cAAc,aAAa;AACzC,UAAM,UAAU,MAAM,SAAS,KAAK,SAAS,KAAK;AAClD,QAAI,CAAC,QAAQ,IAAI;AACf,aAAO,QAAQ;AAAA,IACjB;AACA,UAAM,eAAe,QAAQ,kBAAkB,cAAc,QAAQ,eAAe,IAAI;AAExF,UAAM,aAAa,QAAQ,IAAI,UAAU,QAAQ,YAAY;AAC7D,UAAM,EAAE,OAAO,IAAI,MAAM,WAAW;AAAA,MAClC;AAAA,MACA,EAAE,OAAO,cAAc,KAAK,QAAQ,IAAI;AAAA,IAC1C;AAEA,UAAM,QAAQ,gBAAgB;AAE9B,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,QAAQ,WAAW,aAAa,GAAG,CAAC;AAAA,EACpF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,uCAAuC,eAAe,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxH;AACA,WAAO,MAAM,sCAAsC,EAAE,IAAI,CAAC;AAC1D,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,sCAAsC,+BAA+B,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvI;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa,EAAE,aAAa,oBAAoB,QAAQ,uBAAuB;AAAA,MAC/E,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,QAClE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { 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 { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { assignClaimInputSchema, type AssignClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\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('warranty_claims.errors.unauthorized', 'Unauthorized') })\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, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toAssignInput(scopedPayload: Record<string, unknown>): AssignClaimInput {\n return assignClaimInputSchema.parse({\n id: scopedPayload.id,\n assigneeUserId: scopedPayload.assigneeUserId,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: AssignClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toAssignInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toAssignInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<AssignClaimInput, { claimId: string }>(\n 'warranty_claims.claim.assign',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.assign.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Assign warranty claim',\n methods: {\n POST: {\n summary: 'Assign or unassign a claim',\n requestBody: { contentType: 'application/json', schema: assignClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Claim assigned',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
7
8
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
8
9
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
9
10
|
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
@@ -115,6 +116,10 @@ async function POST(req) {
|
|
|
115
116
|
});
|
|
116
117
|
} catch (err) {
|
|
117
118
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
119
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
120
|
+
if (interceptorRejection) {
|
|
121
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
122
|
+
}
|
|
118
123
|
if (err instanceof z.ZodError) {
|
|
119
124
|
return NextResponse.json({ error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
120
125
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/credit-memo/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 { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n claimCreateCreditMemoSchema,\n type ClaimCreateCreditMemoInput,\n} from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\ntype CreditMemoCommandResult = {\n claimId: string\n creditMemoId: string\n creditMemoUpdatedAt: string | null\n skippedLineIds: string[]\n grandTotalGrossAmount: string\n currencyCode: string\n}\n\nconst creditMemoRequestSchema = z\n .object({\n claimId: z.string().uuid(),\n updatedAt: z.string().datetime().nullable().optional(),\n })\n .strict()\n\ntype CreditMemoRequest = z.infer<typeof creditMemoRequestSchema>\n\nconst creditMemoResponseSchema = z.object({\n creditMemoId: z.string().uuid(),\n skippedLineIds: z.array(z.string().uuid()),\n grandTotalGrossAmount: z.string(),\n currencyCode: z.string(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.credit_memos.manage'] },\n}\n\nfunction translateKey(key: string): string {\n return key\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\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, { error: 'warranty_claims.errors.organization_required' })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate: translateKey,\n }\n}\n\nfunction toCreditMemoRequest(payload: Record<string, unknown>): CreditMemoRequest {\n return creditMemoRequestSchema.parse(payload)\n}\n\nfunction toCommandInput(\n input: CreditMemoRequest,\n context: ActionRouteContext,\n): ClaimCreateCreditMemoInput {\n const scopedPayload = withScopedPayload({\n id: input.claimId,\n updatedAt: input.updatedAt,\n }, context.ctx, context.translate)\n return claimCreateCreditMemoSchema.parse(scopedPayload)\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: CreditMemoRequest,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const requestInput = toCreditMemoRequest(toRecord(await readJsonSafe(req, {})))\n const guarded = await runGuard(req, context, requestInput)\n if (!guarded.ok) {\n return guarded.response\n }\n const guardedInput = guarded.modifiedPayload\n ? toCreditMemoRequest(guarded.modifiedPayload)\n : requestInput\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<ClaimCreateCreditMemoInput, CreditMemoCommandResult>(\n 'warranty_claims.claim.create_credit_memo',\n { input: toCommandInput(guardedInput, context), ctx: context.ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })\n await guarded.runAfterSuccess()\n return NextResponse.json({\n creditMemoId: result.creditMemoId,\n skippedLineIds: result.skippedLineIds,\n grandTotalGrossAmount: result.grandTotalGrossAmount,\n currencyCode: result.currencyCode,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n logger.error('warranty_claims.credit-memo.post failed', { err })\n return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Create a credit memo from a warranty claim',\n methods: {\n POST: {\n summary: 'Create and link a credit memo from eligible received credit or refund lines',\n description: 'Amounts are prorated from source order-line totals. Version 1 does not prorate order-level adjustments.',\n requestBody: { contentType: 'application/json', schema: creditMemoRequestSchema },\n responses: [\n {\n status: 200,\n description: 'Credit memo created and linked; ineligible or unresolvable claim lines are reported as skipped',\n schema: creditMemoResponseSchema,\n },\n { status: 400, description: 'Invalid request or claim is not eligible for credit-memo creation', schema: errorResponseSchema },\n { status: 401, description: 'Authentication required', schema: errorResponseSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorResponseSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAC7B,SAAS,8BAA6D;AACtE,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAEK;AACP,SAAS,oCAAoC;AAE7C,MAAM,SAAS,aAAa,iBAAiB;AAkB7C,MAAM,0BAA0B,EAC7B,OAAO;AAAA,EACN,SAAS,EAAE,OAAO,EAAE,KAAK;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS;AACvD,CAAC,EACA,OAAO;AAIV,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,cAAc,EAAE,OAAO,EAAE,KAAK;AAAA,EAC9B,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;AAAA,EACzC,uBAAuB,EAAE,OAAO;AAAA,EAChC,cAAc,EAAE,OAAO;AACzB,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,2BAA2B,EAAE;AAC5G;AAEA,SAAS,aAAa,KAAqB;AACzC,SAAO;AACT;AAEA,SAAS,SAAS,OAAyC;AACzD,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAmC,CAAC;AAC3G;AAEA,eAAe,qBAAqB,KAA2C;AAC7E,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,sCAAsC,CAAC;AAAA,EAC/E;AACA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAC1D,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,+CAA+C,CAAC;AAAA,EACxF;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,MACnB,wBAAwB;AAAA,MACxB,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,MAClE,SAAS;AAAA,IACX;AAAA,IACA,UAAU,KAAK;AAAA,IACf;AAAA,IACA,WAAW;AAAA,EACb;AACF;AAEA,SAAS,oBAAoB,SAAqD;AAChF,SAAO,wBAAwB,MAAM,OAAO;AAC9C;AAEA,SAAS,eACP,OACA,SAC4B;AAC5B,QAAM,gBAAgB,kBAAkB;AAAA,IACtC,IAAI,MAAM;AAAA,IACV,WAAW,MAAM;AAAA,EACnB,GAAG,QAAQ,KAAK,QAAQ,SAAS;AACjC,SAAO,4BAA4B,MAAM,aAAa;AACxD;AAEA,eAAe,SACb,KACA,SACA,OACmC;AACnC,QAAM,SAAS,QAAQ,IAAI,MAAM;AACjC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,sCAAsC,CAAC;AAAA,EAC/E;AACA,SAAO,uBAAuB;AAAA,IAC5B,WAAW,QAAQ,IAAI;AAAA,IACvB;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,IAC1B;AAAA,IACA,OAAO;AAAA,MACL,cAAc;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,WAAW;AAAA,MACX,iBAAiB,EAAE,GAAG,MAAM;AAAA,IAC9B;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,UAAM,eAAe,oBAAoB,SAAS,MAAM,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9E,UAAM,UAAU,MAAM,SAAS,KAAK,SAAS,YAAY;AACzD,QAAI,CAAC,QAAQ,IAAI;AACf,aAAO,QAAQ;AAAA,IACjB;AACA,UAAM,eAAe,QAAQ,kBACzB,oBAAoB,QAAQ,eAAe,IAC3C;AACJ,UAAM,aAAa,QAAQ,IAAI,UAAU,QAAQ,YAAY;AAC7D,UAAM,EAAE,OAAO,IAAI,MAAM,WAAW;AAAA,MAClC;AAAA,MACA,EAAE,OAAO,eAAe,cAAc,OAAO,GAAG,KAAK,QAAQ,IAAI;AAAA,IACnE;AACA,QAAI,CAAC,OAAQ,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,qCAAqC,CAAC;AACzF,UAAM,QAAQ,gBAAgB;AAC9B,WAAO,aAAa,KAAK;AAAA,MACvB,cAAc,OAAO;AAAA,MACrB,gBAAgB,OAAO;AAAA,MACvB,uBAAuB,OAAO;AAAA,MAC9B,cAAc,OAAO;AAAA,IACvB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,sCAAsC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC5F;AACA,WAAO,MAAM,2CAA2C,EAAE,IAAI,CAAC;AAC/D,WAAO,aAAa,KAAK,EAAE,OAAO,qCAAqC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;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,EAAE,aAAa,oBAAoB,QAAQ,wBAAwB;AAAA,MAChF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA,EAAE,QAAQ,KAAK,aAAa,qEAAqE,QAAQ,oBAAoB;AAAA,QAC7H,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,oBAAoB;AAAA,QACnF,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,oBAAoB;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,oBAAoB;AAAA,MACtF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n claimCreateCreditMemoSchema,\n type ClaimCreateCreditMemoInput,\n} from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\ntype CreditMemoCommandResult = {\n claimId: string\n creditMemoId: string\n creditMemoUpdatedAt: string | null\n skippedLineIds: string[]\n grandTotalGrossAmount: string\n currencyCode: string\n}\n\nconst creditMemoRequestSchema = z\n .object({\n claimId: z.string().uuid(),\n updatedAt: z.string().datetime().nullable().optional(),\n })\n .strict()\n\ntype CreditMemoRequest = z.infer<typeof creditMemoRequestSchema>\n\nconst creditMemoResponseSchema = z.object({\n creditMemoId: z.string().uuid(),\n skippedLineIds: z.array(z.string().uuid()),\n grandTotalGrossAmount: z.string(),\n currencyCode: z.string(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.credit_memos.manage'] },\n}\n\nfunction translateKey(key: string): string {\n return key\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\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, { error: 'warranty_claims.errors.organization_required' })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate: translateKey,\n }\n}\n\nfunction toCreditMemoRequest(payload: Record<string, unknown>): CreditMemoRequest {\n return creditMemoRequestSchema.parse(payload)\n}\n\nfunction toCommandInput(\n input: CreditMemoRequest,\n context: ActionRouteContext,\n): ClaimCreateCreditMemoInput {\n const scopedPayload = withScopedPayload({\n id: input.claimId,\n updatedAt: input.updatedAt,\n }, context.ctx, context.translate)\n return claimCreateCreditMemoSchema.parse(scopedPayload)\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: CreditMemoRequest,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const requestInput = toCreditMemoRequest(toRecord(await readJsonSafe(req, {})))\n const guarded = await runGuard(req, context, requestInput)\n if (!guarded.ok) {\n return guarded.response\n }\n const guardedInput = guarded.modifiedPayload\n ? toCreditMemoRequest(guarded.modifiedPayload)\n : requestInput\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<ClaimCreateCreditMemoInput, CreditMemoCommandResult>(\n 'warranty_claims.claim.create_credit_memo',\n { input: toCommandInput(guardedInput, context), ctx: context.ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })\n await guarded.runAfterSuccess()\n return NextResponse.json({\n creditMemoId: result.creditMemoId,\n skippedLineIds: result.skippedLineIds,\n grandTotalGrossAmount: result.grandTotalGrossAmount,\n currencyCode: result.currencyCode,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n logger.error('warranty_claims.credit-memo.post failed', { err })\n return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Create a credit memo from a warranty claim',\n methods: {\n POST: {\n summary: 'Create and link a credit memo from eligible received credit or refund lines',\n description: 'Amounts are prorated from source order-line totals. Version 1 does not prorate order-level adjustments.',\n requestBody: { contentType: 'application/json', schema: creditMemoRequestSchema },\n responses: [\n {\n status: 200,\n description: 'Credit memo created and linked; ineligible or unresolvable claim lines are reported as skipped',\n schema: creditMemoResponseSchema,\n },\n { status: 400, description: 'Invalid request or claim is not eligible for credit-memo creation', schema: errorResponseSchema },\n { status: 401, description: 'Authentication required', schema: errorResponseSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorResponseSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
8
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
9
10
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
10
11
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
@@ -143,6 +144,10 @@ async function POST(req) {
|
|
|
143
144
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.claimId });
|
|
144
145
|
} catch (err) {
|
|
145
146
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
147
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
148
|
+
if (interceptorRejection) {
|
|
149
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
150
|
+
}
|
|
146
151
|
const { translate } = await resolveTranslations();
|
|
147
152
|
if (err instanceof z.ZodError) {
|
|
148
153
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/events/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimEvent } from '../../data/entities'\nimport { commentClaimInputSchema, type CommentClaimInput } from '../../data/validators'\nimport { requireScopedClaim, WARRANTY_CLAIM_RESOURCE_KIND, type WarrantyClaimScope } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst eventListQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\ntype EventRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nfunction toIso(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n }\n return null\n}\n\nfunction serializeEvent(event: WarrantyClaimEvent) {\n return {\n id: event.id,\n claimId: relationId(event.claim),\n kind: event.kind,\n visibility: event.visibility,\n body: event.body ?? null,\n payload: event.payload ?? null,\n actorUserId: event.actorUserId ?? null,\n actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolveEventContext(req: Request): Promise<EventRouteContext> {\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('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n ctx: {\n container,\n auth,\n organizationScope,\n selectedOrganizationId: organizationId,\n organizationIds: organizationScope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nasync function runCommentGuard(\n req: Request,\n context: EventRouteContext,\n input: CommentClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveEventContext(req)\n const url = new URL(req.url)\n const query = eventListQuerySchema.parse({ claimId: url.searchParams.get('claimId') ?? undefined })\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope)\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, tenantId: context.scope.tenantId, organizationId: context.scope.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n context.scope,\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.events.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.notFound', 'Warranty claim not found.') }, { status: 404 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveEventContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const parsed = commentClaimInputSchema.parse(payload)\n const input: CommentClaimInput = { ...parsed, actorCustomerId: undefined }\n const guarded = await runCommentGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload\n ? { ...commentClaimInputSchema.parse(guarded.modifiedPayload), actorCustomerId: undefined }\n : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.claimId })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.events.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.string(),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorUserId: z.string().uuid().nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim timeline events',\n methods: {\n GET: {\n summary: 'List timeline events for a claim',\n query: eventListQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a staff comment to a claim timeline',\n requestBody: { contentType: 'application/json', schema: commentClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Comment appended',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimEvent } from '../../data/entities'\nimport { commentClaimInputSchema, type CommentClaimInput } from '../../data/validators'\nimport { requireScopedClaim, WARRANTY_CLAIM_RESOURCE_KIND, type WarrantyClaimScope } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst eventListQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\ntype EventRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nfunction toIso(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n }\n return null\n}\n\nfunction serializeEvent(event: WarrantyClaimEvent) {\n return {\n id: event.id,\n claimId: relationId(event.claim),\n kind: event.kind,\n visibility: event.visibility,\n body: event.body ?? null,\n payload: event.payload ?? null,\n actorUserId: event.actorUserId ?? null,\n actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolveEventContext(req: Request): Promise<EventRouteContext> {\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('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n ctx: {\n container,\n auth,\n organizationScope,\n selectedOrganizationId: organizationId,\n organizationIds: organizationScope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nasync function runCommentGuard(\n req: Request,\n context: EventRouteContext,\n input: CommentClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveEventContext(req)\n const url = new URL(req.url)\n const query = eventListQuerySchema.parse({ claimId: url.searchParams.get('claimId') ?? undefined })\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope)\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, tenantId: context.scope.tenantId, organizationId: context.scope.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n context.scope,\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.events.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.notFound', 'Warranty claim not found.') }, { status: 404 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveEventContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const parsed = commentClaimInputSchema.parse(payload)\n const input: CommentClaimInput = { ...parsed, actorCustomerId: undefined }\n const guarded = await runCommentGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload\n ? { ...commentClaimInputSchema.parse(guarded.modifiedPayload), actorCustomerId: undefined }\n : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.claimId })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.events.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.string(),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorUserId: z.string().uuid().nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim timeline events',\n methods: {\n GET: {\n summary: 'List timeline events for a claim',\n query: eventListQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a staff comment to a claim timeline',\n requestBody: { contentType: 'application/json', schema: commentClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Comment appended',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
4
5
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
6
|
import {
|
|
6
7
|
loadOwnedClaim,
|
|
@@ -48,6 +49,10 @@ async function POST(req, ctx) {
|
|
|
48
49
|
if (isCrudHttpError(err)) {
|
|
49
50
|
return NextResponse.json(err.body, { status: err.status });
|
|
50
51
|
}
|
|
52
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
53
|
+
if (interceptorRejection) {
|
|
54
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
55
|
+
}
|
|
51
56
|
throw err;
|
|
52
57
|
}
|
|
53
58
|
await guarded.runAfterSuccess();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/submit/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n loadOwnedClaim,\n loadOwnedClaimFresh,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (claim.status !== 'draft') {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'submit',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claim.id, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n await guarded.runAfterSuccess()\n\n const updated = await loadOwnedClaimFresh(context, claim.id)\n return NextResponse.json({ ok: true, claimId: claim.id, status: updated?.status ?? 'submitted' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim submit action',\n methods: {\n POST: {\n summary: 'Submit an owned draft claim from the customer portal',\n responses: [\n {\n status: 200,\n description: 'Claim submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,MAAI,MAAM,WAAW,SAAS;AAC5B,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA,EAAE,OAAO,EAAE,IAAI,MAAM,IAAI,iBAAiB,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW;AAAA,IAC1F;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,QAAM,UAAU,MAAM,oBAAoB,SAAS,MAAM,EAAE;AAC3D,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,SAAS,UAAU,YAAY,CAAC;AAClG;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n loadOwnedClaim,\n loadOwnedClaimFresh,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (claim.status !== 'draft') {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'submit',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claim.id, actorCustomerId: context.customerId }, ctx: context.commandCtx },\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 throw err\n }\n\n await guarded.runAfterSuccess()\n\n const updated = await loadOwnedClaimFresh(context, claim.id)\n return NextResponse.json({ ok: true, claimId: claim.id, status: updated?.status ?? 'submitted' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim submit action',\n methods: {\n POST: {\n summary: 'Submit an owned draft claim from the customer portal',\n responses: [\n {\n status: 200,\n description: 'Claim submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,MAAI,MAAM,WAAW,SAAS;AAC5B,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA,EAAE,OAAO,EAAE,IAAI,MAAM,IAAI,iBAAiB,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW;AAAA,IAC1F;AAAA,EACF,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;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,QAAM,UAAU,MAAM,oBAAoB,SAAS,MAAM,EAAE;AAC3D,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,SAAS,UAAU,YAAY,CAAC;AAClG;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
4
5
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
6
|
import {
|
|
6
7
|
loadOwnedClaim,
|
|
@@ -52,6 +53,10 @@ async function POST(req, ctx) {
|
|
|
52
53
|
if (isCrudHttpError(err)) {
|
|
53
54
|
return NextResponse.json(err.body, { status: err.status });
|
|
54
55
|
}
|
|
56
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
57
|
+
if (interceptorRejection) {
|
|
58
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
59
|
+
}
|
|
55
60
|
throw err;
|
|
56
61
|
}
|
|
57
62
|
await guarded.runAfterSuccess();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/withdraw/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'\nimport {\n loadOwnedClaim,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst PORTAL_WITHDRAWABLE_STATUSES = new Set<WarrantyClaimStatus>(['draft', 'submitted'])\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'withdraw',\n toStatus: 'cancelled',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n {\n input: { id: claim.id, toStatus: 'cancelled', actorCustomerId: context.customerId },\n ctx: context.commandCtx,\n },\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: claim.id, status: 'cancelled' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim withdraw action',\n methods: {\n POST: {\n summary: 'Withdraw (cancel) an owned claim while it is draft or submitted',\n responses: [\n {\n status: 200,\n description: 'Claim withdrawn',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.literal('cancelled') }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AAGpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,+BAA+B,oBAAI,IAAyB,CAAC,SAAS,WAAW,CAAC;AAExF,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,MAAI,CAAC,6BAA6B,IAAI,MAAM,MAAM,GAAG;AACnD,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,QACE,OAAO,EAAE,IAAI,MAAM,IAAI,UAAU,aAAa,iBAAiB,QAAQ,WAAW;AAAA,QAClF,KAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC/E;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'\nimport {\n loadOwnedClaim,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst PORTAL_WITHDRAWABLE_STATUSES = new Set<WarrantyClaimStatus>(['draft', 'submitted'])\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'withdraw',\n toStatus: 'cancelled',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n {\n input: { id: claim.id, toStatus: 'cancelled', actorCustomerId: context.customerId },\n ctx: context.commandCtx,\n },\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 throw err\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: claim.id, status: 'cancelled' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim withdraw action',\n methods: {\n POST: {\n summary: 'Withdraw (cancel) an owned claim while it is draft or submitted',\n responses: [\n {\n status: 200,\n description: 'Claim withdrawn',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.literal('cancelled') }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAGpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,+BAA+B,oBAAI,IAAyB,CAAC,SAAS,WAAW,CAAC;AAExF,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,MAAI,CAAC,6BAA6B,IAAI,MAAM,MAAM,GAAG;AACnD,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,QACE,OAAO,EAAE,IAAI,MAAM,IAAI,UAAU,aAAa,iBAAiB,QAAQ,WAAW;AAAA,QAClF,KAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF,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;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC/E;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
5
6
|
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
6
7
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
7
8
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
@@ -401,6 +402,10 @@ async function POST(req) {
|
|
|
401
402
|
if (isCrudHttpError(err)) {
|
|
402
403
|
return NextResponse.json(err.body, { status: err.status });
|
|
403
404
|
}
|
|
405
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
406
|
+
if (interceptorRejection) {
|
|
407
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
408
|
+
}
|
|
404
409
|
throw err;
|
|
405
410
|
}
|
|
406
411
|
}
|