@open-mercato/core 0.7.1-develop.7131.1.20abf9e885 → 0.7.1-develop.7136.1.0f76137a1d

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.
Files changed (73) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  3. package/dist/modules/communication_channels/api/post/test-seed/route.js +40 -2
  4. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  5. package/dist/modules/communication_channels/lib/access-control.js +6 -1
  6. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  7. package/dist/modules/communication_channels/lib/system-user.js +8 -3
  8. package/dist/modules/communication_channels/lib/system-user.js.map +2 -2
  9. package/dist/modules/warranty_claims/api/ai/assess/route.js +20 -18
  10. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  11. package/dist/modules/warranty_claims/api/credit-memo/route.js +11 -12
  12. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  13. package/dist/modules/warranty_claims/api/events/route.js +1 -1
  14. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  15. package/dist/modules/warranty_claims/api/portal/attachments/route.js +24 -27
  16. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  17. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +8 -10
  18. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +2 -2
  19. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +4 -9
  20. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +2 -2
  21. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +3 -3
  22. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  23. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +3 -3
  24. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  25. package/dist/modules/warranty_claims/api/portal/claims/route.js +4 -9
  26. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  27. package/dist/modules/warranty_claims/api/portal/events/route.js +12 -13
  28. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  29. package/dist/modules/warranty_claims/api/receiving/route.js +14 -15
  30. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  31. package/dist/modules/warranty_claims/api/registrations/route.js +9 -4
  32. package/dist/modules/warranty_claims/api/registrations/route.js.map +2 -2
  33. package/dist/modules/warranty_claims/api/replacement-order/route.js +11 -12
  34. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  35. package/dist/modules/warranty_claims/api/return-label/route.js +20 -19
  36. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  37. package/dist/modules/warranty_claims/api/risk/route.js +2 -1
  38. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  39. package/dist/modules/warranty_claims/api/sales-return/route.js +11 -12
  40. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  41. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +22 -1
  42. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +2 -2
  43. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -1
  44. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  45. package/dist/modules/warranty_claims/commands/shared.js +2 -2
  46. package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
  47. package/dist/modules/warranty_claims/lib/portalAuthGuard.js +31 -0
  48. package/dist/modules/warranty_claims/lib/portalAuthGuard.js.map +7 -0
  49. package/package.json +7 -7
  50. package/src/helpers/integration/communicationChannelsFixtures.ts +13 -0
  51. package/src/modules/communication_channels/api/post/test-seed/route.ts +47 -2
  52. package/src/modules/communication_channels/lib/access-control.ts +28 -0
  53. package/src/modules/communication_channels/lib/system-user.ts +40 -22
  54. package/src/modules/warranty_claims/api/ai/assess/route.ts +23 -17
  55. package/src/modules/warranty_claims/api/credit-memo/route.ts +14 -13
  56. package/src/modules/warranty_claims/api/events/route.ts +1 -1
  57. package/src/modules/warranty_claims/api/portal/attachments/route.ts +29 -27
  58. package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +9 -10
  59. package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +5 -9
  60. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +3 -3
  61. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +3 -3
  62. package/src/modules/warranty_claims/api/portal/claims/route.ts +5 -9
  63. package/src/modules/warranty_claims/api/portal/events/route.ts +13 -13
  64. package/src/modules/warranty_claims/api/receiving/route.ts +17 -15
  65. package/src/modules/warranty_claims/api/registrations/route.ts +9 -3
  66. package/src/modules/warranty_claims/api/replacement-order/route.ts +14 -13
  67. package/src/modules/warranty_claims/api/return-label/route.ts +21 -17
  68. package/src/modules/warranty_claims/api/risk/route.ts +3 -1
  69. package/src/modules/warranty_claims/api/sales-return/route.ts +14 -13
  70. package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +23 -1
  71. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +3 -1
  72. package/src/modules/warranty_claims/commands/shared.ts +7 -1
  73. package/src/modules/warranty_claims/lib/portalAuthGuard.ts +30 -0
@@ -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 { 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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",
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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\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: context.translate('warranty_claims.errors.unauthorized', '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: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return {\n ...auth,\n sub: auth.sub,\n tenantId: context.tenantId,\n orgId: context.organizationId,\n }\n}\n\nasync function resolveAssessContext(\n req: Request,\n translate: AssessRouteContext['translate'],\n): Promise<AssessRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\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 container,\n em: container.resolve<EntityManager>('em').fork(),\n translate,\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: 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_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 translate: AssessRouteContext['translate'],\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: translate('warranty_claims.errors.notFound', 'Claim not found.') })\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: context.translate('warranty_claims.errors.attachmentNotLinked', 'The attachment is not linked to this claim.') })\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 const { translate } = await resolveTranslations()\n try {\n const context = await resolveAssessContext(req, translate)\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: translate('warranty_claims.errors.invalidInput', 'Invalid input') })\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: translate('warranty_claims.errors.invalidInput', 'Invalid input') })\n }\n const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate)\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, {}, translate('warranty_claims.errors.notFound', 'Claim not found.'))\n const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate) : 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: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.ai.assess.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { 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
  }
@@ -6,6 +6,7 @@ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/d
6
6
  import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
7
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
8
8
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
9
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
10
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
10
11
  import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
11
12
  import { createLogger } from "@open-mercato/shared/lib/logger";
@@ -30,22 +31,19 @@ const errorResponseSchema = z.object({
30
31
  const metadata = {
31
32
  POST: { requireAuth: true, requireFeatures: ["warranty_claims.claim.manage", "sales.credit_memos.manage"] }
32
33
  };
33
- function translateKey(key) {
34
- return key;
35
- }
36
34
  function toRecord(value) {
37
35
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
38
36
  }
39
- async function resolveActionContext(req) {
37
+ async function resolveActionContext(req, translate) {
40
38
  const container = await createRequestContainer();
41
39
  const auth = await getAuthFromRequest(req);
42
40
  if (!auth || !auth.tenantId) {
43
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
41
+ throw new CrudHttpError(401, { error: translate("warranty_claims.errors.unauthorized", "Unauthorized") });
44
42
  }
45
43
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
46
44
  const organizationId = scope?.selectedId ?? auth.orgId ?? null;
47
45
  if (!organizationId) {
48
- throw new CrudHttpError(400, { error: "warranty_claims.errors.organization_required" });
46
+ throw new CrudHttpError(400, { error: translate("warranty_claims.errors.organization_required", "Organization context is required.") });
49
47
  }
50
48
  return {
51
49
  ctx: {
@@ -58,7 +56,7 @@ async function resolveActionContext(req) {
58
56
  },
59
57
  tenantId: auth.tenantId,
60
58
  organizationId,
61
- translate: translateKey
59
+ translate
62
60
  };
63
61
  }
64
62
  function toCreditMemoRequest(payload) {
@@ -74,7 +72,7 @@ function toCommandInput(input, context) {
74
72
  async function runGuard(req, context, input) {
75
73
  const userId = context.ctx.auth?.sub;
76
74
  if (!userId) {
77
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
75
+ throw new CrudHttpError(401, { error: context.translate("warranty_claims.errors.unauthorized", "Unauthorized") });
78
76
  }
79
77
  return runRouteMutationGuards({
80
78
  container: context.ctx.container,
@@ -93,8 +91,9 @@ async function runGuard(req, context, input) {
93
91
  });
94
92
  }
95
93
  async function POST(req) {
94
+ const { translate } = await resolveTranslations();
96
95
  try {
97
- const context = await resolveActionContext(req);
96
+ const context = await resolveActionContext(req, translate);
98
97
  const requestInput = toCreditMemoRequest(toRecord(await readJsonSafe(req, {})));
99
98
  const guarded = await runGuard(req, context, requestInput);
100
99
  if (!guarded.ok) {
@@ -106,7 +105,7 @@ async function POST(req) {
106
105
  "warranty_claims.claim.create_credit_memo",
107
106
  { input: toCommandInput(guardedInput, context), ctx: context.ctx }
108
107
  );
109
- if (!result) throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
108
+ if (!result) throw new CrudHttpError(400, { error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") });
110
109
  await guarded.runAfterSuccess();
111
110
  return NextResponse.json({
112
111
  creditMemoId: result.creditMemoId,
@@ -121,10 +120,10 @@ async function POST(req) {
121
120
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
122
121
  }
123
122
  if (err instanceof z.ZodError) {
124
- return NextResponse.json({ error: "warranty_claims.errors.invalidInput" }, { status: 400 });
123
+ return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
125
124
  }
126
125
  logger.error("warranty_claims.credit-memo.post failed", { err });
127
- return NextResponse.json({ error: "warranty_claims.errors.save_failed" }, { status: 500 });
126
+ return NextResponse.json({ error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") }, { status: 500 });
128
127
  }
129
128
  }
130
129
  const openApi = {
@@ -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 { 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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",
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 { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\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 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(\n req: Request,\n translate: ActionRouteContext['translate'],\n): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\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 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: 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 POST(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const context = await resolveActionContext(req, translate)\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: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })\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: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.credit-memo.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { 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
  }
@@ -105,7 +105,7 @@ async function GET(req) {
105
105
  const context = await resolveEventContext(req);
106
106
  const url = new URL(req.url);
107
107
  const query = eventListQuerySchema.parse({ claimId: url.searchParams.get("claimId") ?? void 0 });
108
- const claim = await requireScopedClaim(context.em, query.claimId, context.scope);
108
+ const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate("warranty_claims.errors.notFound", "Claim not found."));
109
109
  const events = await findWithDecryption(
110
110
  context.em,
111
111
  WarrantyClaimEvent,
@@ -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 { 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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",
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, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))\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
  }
@@ -6,7 +6,6 @@ import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-muta
6
6
  import { emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
7
7
  import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
8
  import { createLogger } from "@open-mercato/shared/lib/logger";
9
- import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
10
9
  import { Attachment, AttachmentPartition } from "@open-mercato/core/modules/attachments/data/entities";
11
10
  import { buildAttachmentImageUrl, slugifyAttachmentFileName } from "@open-mercato/core/modules/attachments/lib/imageUrls";
12
11
  import { StorageDriverFactory } from "@open-mercato/core/modules/attachments/lib/drivers";
@@ -22,6 +21,7 @@ import { WARRANTY_CLAIM_RESOURCE_KIND } from "../../../commands/shared.js";
22
21
  import { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from "../../../lib/attachmentVisibility.js";
23
22
  import { loadPortalOwnedClaim } from "../../../lib/portalClaimAccess.js";
24
23
  import { resolvePortalAttachmentUploadService } from "../../../lib/portalAttachmentUpload.js";
24
+ import { resolveLinkedCustomerAuth } from "../../../lib/portalAuthGuard.js";
25
25
  const CLAIM_ATTACHMENT_ENTITY_ID = "warranty_claims:warranty_claim";
26
26
  const logger = createLogger("warranty_claims").child({ route: "portal-attachments" });
27
27
  const ATTACHMENT_ERROR_TRANSLATIONS = {
@@ -57,13 +57,9 @@ function toIso(value) {
57
57
  return Number.isNaN(date.getTime()) ? value : date.toISOString();
58
58
  }
59
59
  async function resolvePortalContext(req) {
60
- const auth = await getCustomerAuthFromRequest(req);
61
- if (!auth) {
62
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.unauthorized" }, { status: 401 });
63
- }
64
- if (!auth.customerEntityId) {
65
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
66
- }
60
+ const authOrResponse = await resolveLinkedCustomerAuth(req);
61
+ if (authOrResponse instanceof Response) return authOrResponse;
62
+ const auth = authOrResponse;
67
63
  const container = await createRequestContainer();
68
64
  const em = container.resolve("em");
69
65
  return {
@@ -199,19 +195,19 @@ async function deletePortalAttachment(context, attachment) {
199
195
  await dataEngine.flushOrmEntityChanges();
200
196
  }
201
197
  }
202
- async function streamOwnedAttachment(context, attachmentId) {
198
+ async function streamOwnedAttachment(context, attachmentId, translate) {
203
199
  const owned = await loadOwnedVisibleAttachment(context, attachmentId);
204
200
  if (!owned) {
205
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
201
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
206
202
  }
207
203
  const { attachment } = owned;
208
204
  const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode });
209
205
  if (!partition) {
210
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.load_failed" }, { status: 500 });
206
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.load_failed", "Failed to load warranty claim data.") }, { status: 500 });
211
207
  }
212
208
  const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true });
213
209
  if (!access.ok) {
214
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
210
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
215
211
  }
216
212
  const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {
217
213
  tenantId: attachment.tenantId ?? "",
@@ -221,7 +217,7 @@ async function streamOwnedAttachment(context, attachmentId) {
221
217
  try {
222
218
  buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer;
223
219
  } catch {
224
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
220
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
225
221
  }
226
222
  const headers = {
227
223
  "Cache-Control": "private, max-age=60",
@@ -237,14 +233,15 @@ async function GET(req) {
237
233
  const contextOrResponse = await resolvePortalContext(req);
238
234
  if (contextOrResponse instanceof Response) return contextOrResponse;
239
235
  const context = contextOrResponse;
236
+ const { translate } = await resolveTranslations();
240
237
  const url = new URL(req.url);
241
238
  const attachmentId = url.searchParams.get("attachmentId");
242
239
  if (attachmentId) {
243
240
  const attachmentIdParsed = z.string().uuid().safeParse(attachmentId);
244
241
  if (!attachmentIdParsed.success) {
245
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
242
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
246
243
  }
247
- return streamOwnedAttachment(context, attachmentIdParsed.data);
244
+ return streamOwnedAttachment(context, attachmentIdParsed.data, translate);
248
245
  }
249
246
  const parsed = attachmentQuerySchema.safeParse({
250
247
  claimId: url.searchParams.get("claimId") ?? void 0,
@@ -252,11 +249,11 @@ async function GET(req) {
252
249
  pageSize: url.searchParams.get("pageSize") ?? void 0
253
250
  });
254
251
  if (!parsed.success) {
255
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
252
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
256
253
  }
257
254
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
258
255
  if (!claim) {
259
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
256
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
260
257
  }
261
258
  const filter = {
262
259
  entityId: CLAIM_ATTACHMENT_ENTITY_ID,
@@ -296,12 +293,12 @@ async function handleUpload(req, replacement) {
296
293
  const contextOrResponse = await resolvePortalContext(req);
297
294
  if (contextOrResponse instanceof Response) return contextOrResponse;
298
295
  const context = contextOrResponse;
296
+ const { t, translate } = await resolveTranslations();
299
297
  const contentType = req.headers.get("content-type") || "";
300
298
  if (!contentType.toLowerCase().includes("multipart/form-data")) {
301
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
299
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
302
300
  }
303
301
  if (!isMultipartRequestWithinUploadLimit(req.headers.get("content-length"))) {
304
- const { t } = await resolveTranslations();
305
302
  return NextResponse.json({
306
303
  ok: false,
307
304
  error: t("attachments.errors.maxUploadSize", "Attachment exceeds the maximum upload size.")
@@ -314,21 +311,21 @@ async function handleUpload(req, replacement) {
314
311
  });
315
312
  const parsedAttachmentId = replacement ? z.string().uuid().safeParse(form.get("attachmentId") ?? void 0) : null;
316
313
  if (!parsed.success || replacement && !parsedAttachmentId?.success) {
317
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
314
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
318
315
  }
319
316
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
320
317
  if (!claim) {
321
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
318
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
322
319
  }
323
320
  const file = form.get("file");
324
321
  if (!(file instanceof File)) {
325
- return NextResponse.json({ ok: false, error: "warranty_claims.portal.detail.fileRequired" }, { status: 400 });
322
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.portal.detail.fileRequired", "Choose a file before uploading.") }, { status: 400 });
326
323
  }
327
324
  const buffer = Buffer.from(await file.arrayBuffer());
328
325
  const attachmentId = replacement && parsedAttachmentId?.success ? parsedAttachmentId.data : null;
329
326
  const replacementTarget = attachmentId ? await loadOwnedVisibleAttachment(context, attachmentId) : null;
330
327
  if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {
331
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
328
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
332
329
  }
333
330
  const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? "update" : "create", {
334
331
  claimId: claim.id,
@@ -342,7 +339,7 @@ async function handleUpload(req, replacement) {
342
339
  }
343
340
  const uploadService = resolvePortalAttachmentUploadService(context.container);
344
341
  if (!uploadService) {
345
- return NextResponse.json({ ok: false, error: "warranty_claims.portal.detail.attachmentError" }, { status: 503 });
342
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.portal.detail.attachmentError", "Attachment upload failed.") }, { status: 503 });
346
343
  }
347
344
  let attachment;
348
345
  try {
@@ -358,7 +355,6 @@ async function handleUpload(req, replacement) {
358
355
  });
359
356
  } catch (error) {
360
357
  if (isScopedAttachmentUploadError(error)) {
361
- const { t } = await resolveTranslations();
362
358
  const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code] ?? ["warranty_claims.portal.detail.attachmentError", "Attachment upload failed."];
363
359
  return NextResponse.json(
364
360
  { ok: false, error: t(key, fallback), code: error.code },
@@ -389,16 +385,17 @@ async function DELETE(req) {
389
385
  const contextOrResponse = await resolvePortalContext(req);
390
386
  if (contextOrResponse instanceof Response) return contextOrResponse;
391
387
  const context = contextOrResponse;
388
+ const { translate } = await resolveTranslations();
392
389
  const url = new URL(req.url);
393
390
  const parsed = deleteQuerySchema.safeParse({
394
391
  attachmentId: url.searchParams.get("attachmentId") ?? void 0
395
392
  });
396
393
  if (!parsed.success) {
397
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
394
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
398
395
  }
399
396
  const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId);
400
397
  if (!owned) {
401
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
398
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
402
399
  }
403
400
  const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, "delete", {
404
401
  claimId: owned.claim.id,