@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/portal/claims/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } 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 { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\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 stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\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\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const { translate } = await resolveTranslations()\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const { translate } = await resolveTranslations()\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\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\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\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 type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } 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 { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\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 stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\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\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const { translate } = await resolveTranslations()\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\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\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\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,11 +4,12 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
5
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
6
6
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
7
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
8
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
- import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
9
9
  import { WarrantyClaimEvent } from "../../../data/entities.js";
10
10
  import { WARRANTY_CLAIM_RESOURCE_KIND } from "../../../commands/shared.js";
11
11
  import { loadPortalOwnedClaim } from "../../../lib/portalClaimAccess.js";
12
+ import { resolveLinkedCustomerAuth } from "../../../lib/portalAuthGuard.js";
12
13
  const metadata = {
13
14
  GET: { requireAuth: false },
14
15
  POST: { requireAuth: false }
@@ -47,13 +48,9 @@ function serializeEvent(event) {
47
48
  };
48
49
  }
49
50
  async function resolvePortalContext(req) {
50
- const auth = await getCustomerAuthFromRequest(req);
51
- if (!auth) {
52
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.unauthorized" }, { status: 401 });
53
- }
54
- if (!auth.customerEntityId) {
55
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
56
- }
51
+ const authOrResponse = await resolveLinkedCustomerAuth(req);
52
+ if (authOrResponse instanceof Response) return authOrResponse;
53
+ const auth = authOrResponse;
57
54
  const container = await createRequestContainer();
58
55
  const em = container.resolve("em");
59
56
  const commandAuth = {
@@ -114,14 +111,15 @@ async function GET(req) {
114
111
  const contextOrResponse = await resolvePortalContext(req);
115
112
  if (contextOrResponse instanceof Response) return contextOrResponse;
116
113
  const context = contextOrResponse;
114
+ const { translate } = await resolveTranslations();
117
115
  const url = new URL(req.url);
118
116
  const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get("claimId") ?? void 0 });
119
117
  if (!parsed.success) {
120
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
118
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
121
119
  }
122
120
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
123
121
  if (!claim) {
124
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
122
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
125
123
  }
126
124
  const events = await findWithDecryption(
127
125
  context.em,
@@ -136,19 +134,20 @@ async function POST(req) {
136
134
  const contextOrResponse = await resolvePortalContext(req);
137
135
  if (contextOrResponse instanceof Response) return contextOrResponse;
138
136
  const context = contextOrResponse;
137
+ const { translate } = await resolveTranslations();
139
138
  let body;
140
139
  try {
141
140
  body = await req.json();
142
141
  } catch {
143
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
142
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
144
143
  }
145
144
  const parsed = portalCommentSchema.safeParse(body);
146
145
  if (!parsed.success) {
147
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
146
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
148
147
  }
149
148
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
150
149
  if (!claim) {
151
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
150
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
152
151
  }
153
152
  const input = {
154
153
  claimId: claim.id,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/warranty_claims/api/portal/events/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } 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 { 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 { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'\nimport type { CommentClaimInput } from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst eventQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\nconst portalCommentSchema = z\n .object({\n claimId: z.string().uuid(),\n body: z.string().trim().min(1).max(8000),\n })\n .strict()\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\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: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\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 actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalCommentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, visibility: 'customer', tenantId: context.tenantId, organizationId: context.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const parsed = portalCommentSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const input: CommentClaimInput = {\n claimId: claim.id,\n body: parsed.data.body,\n visibility: 'customer',\n actorCustomerId: context.customerId,\n }\n const guarded = await runPortalCommentGuard(req, context, claim.id, { ...input })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: CommentClaimInput = guarded.modifiedPayload\n ? {\n ...input,\n ...guarded.modifiedPayload,\n claimId: input.claimId,\n visibility: input.visibility,\n actorCustomerId: input.actorCustomerId,\n }\n : input\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n await guarded.runAfterSuccess()\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 return NextResponse.json({ ok: true, claimId: claim.id })\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.literal('customer'),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim timeline',\n methods: {\n GET: {\n summary: 'List customer-visible timeline events for an owned claim',\n query: eventQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer-visible timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a customer-visible customer comment',\n requestBody: { contentType: 'application/json', schema: portalCommentSchema },\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 type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } 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 { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'\nimport type { CommentClaimInput } from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst eventQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\nconst portalCommentSchema = z\n .object({\n claimId: z.string().uuid(),\n body: z.string().trim().min(1).max(8000),\n })\n .strict()\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\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: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\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 actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalCommentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const url = new URL(req.url)\n const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, visibility: 'customer', tenantId: context.tenantId, organizationId: context.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalCommentSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const input: CommentClaimInput = {\n claimId: claim.id,\n body: parsed.data.body,\n visibility: 'customer',\n actorCustomerId: context.customerId,\n }\n const guarded = await runPortalCommentGuard(req, context, claim.id, { ...input })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: CommentClaimInput = guarded.modifiedPayload\n ? {\n ...input,\n ...guarded.modifiedPayload,\n claimId: input.claimId,\n visibility: input.visibility,\n actorCustomerId: input.actorCustomerId,\n }\n : input\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n await guarded.runAfterSuccess()\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 return NextResponse.json({ ok: true, claimId: claim.id })\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.literal('customer'),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim timeline',\n methods: {\n GET: {\n summary: 'List customer-visible timeline events for an owned claim',\n query: eventQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer-visible timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a customer-visible customer comment',\n requestBody: { contentType: 'application/json', schema: portalCommentSchema },\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,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 {
@@ -36,19 +37,16 @@ const metadata = {
36
37
  function toRecord(value) {
37
38
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
38
39
  }
39
- function translateKey(key) {
40
- return key;
41
- }
42
- async function resolveActionContext(req) {
40
+ async function resolveActionContext(req, translate) {
43
41
  const container = await createRequestContainer();
44
42
  const auth = await getAuthFromRequest(req);
45
43
  if (!auth || !auth.tenantId) {
46
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
44
+ throw new CrudHttpError(401, { error: translate("warranty_claims.errors.unauthorized", "Unauthorized") });
47
45
  }
48
46
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
49
47
  const organizationId = scope?.selectedId ?? auth.orgId ?? null;
50
48
  if (!organizationId) {
51
- throw new CrudHttpError(400, { error: "warranty_claims.errors.organization_required" });
49
+ throw new CrudHttpError(400, { error: translate("warranty_claims.errors.organization_required", "Organization context is required.") });
52
50
  }
53
51
  return {
54
52
  ctx: {
@@ -61,7 +59,7 @@ async function resolveActionContext(req) {
61
59
  },
62
60
  tenantId: auth.tenantId,
63
61
  organizationId,
64
- translate: translateKey
62
+ translate
65
63
  };
66
64
  }
67
65
  function toReceivingAction(payload, context) {
@@ -91,7 +89,7 @@ function toGuardedAction(kind, payload) {
91
89
  async function runGuard(req, context, action) {
92
90
  const userId = context.ctx.auth?.sub;
93
91
  if (!userId) {
94
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
92
+ throw new CrudHttpError(401, { error: context.translate("warranty_claims.errors.unauthorized", "Unauthorized") });
95
93
  }
96
94
  return runRouteMutationGuards({
97
95
  container: context.ctx.container,
@@ -109,25 +107,26 @@ async function runGuard(req, context, action) {
109
107
  }
110
108
  });
111
109
  }
112
- async function executeReceivingAction(commandBus, action, ctx) {
110
+ async function executeReceivingAction(commandBus, action, ctx, translate) {
113
111
  if (action.kind === "release") {
114
112
  const { result: result2 } = await commandBus.execute(
115
113
  "warranty_claims.claim_line.release_quarantine",
116
114
  { input: action.input, ctx }
117
115
  );
118
- if (!result2) throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
116
+ if (!result2) throw new CrudHttpError(400, { error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") });
119
117
  return result2;
120
118
  }
121
119
  const { result } = await commandBus.execute(
122
120
  "warranty_claims.claim_line.receive",
123
121
  { input: action.input, ctx }
124
122
  );
125
- if (!result) throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
123
+ if (!result) throw new CrudHttpError(400, { error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") });
126
124
  return result;
127
125
  }
128
126
  async function POST(req) {
127
+ const { translate } = await resolveTranslations();
129
128
  try {
130
- const context = await resolveActionContext(req);
129
+ const context = await resolveActionContext(req, translate);
131
130
  const payload = toRecord(await readJsonSafe(req, {}));
132
131
  const action = toReceivingAction(payload, context);
133
132
  const guarded = await runGuard(req, context, action);
@@ -136,7 +135,7 @@ async function POST(req) {
136
135
  }
137
136
  const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action;
138
137
  const commandBus = context.ctx.container.resolve("commandBus");
139
- const result = await executeReceivingAction(commandBus, commandAction, context.ctx);
138
+ const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate);
140
139
  await guarded.runAfterSuccess();
141
140
  return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.claimId });
142
141
  } catch (err) {
@@ -146,10 +145,10 @@ async function POST(req) {
146
145
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
147
146
  }
148
147
  if (err instanceof z.ZodError) {
149
- return NextResponse.json({ error: "warranty_claims.errors.invalidInput" }, { status: 400 });
148
+ return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
150
149
  }
151
150
  logger.error("warranty_claims.receiving.post failed", { err });
152
- return NextResponse.json({ error: "warranty_claims.errors.save_failed" }, { status: 500 });
151
+ return NextResponse.json({ error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") }, { status: 500 });
153
152
  }
154
153
  }
155
154
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/receiving/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 {\n claimConditionGradeSchema,\n claimLineReceiveSchema,\n claimLineReleaseQuarantineSchema,\n type ClaimLineReceiveInput,\n type ClaimLineReleaseQuarantineInput,\n} from '../../data/validators'\nimport { WARRANTY_CLAIM_LINE_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\ntype ReceivingAction =\n | { kind: 'receive'; input: ClaimLineReceiveInput }\n | { kind: 'release'; input: ClaimLineReleaseQuarantineInput }\n\nconst uuid = z.string().uuid()\nconst optimisticLockTokenSchema = z.string().datetime().nullable().optional()\n\nconst receiveBodySchema = z\n .object({\n lineId: uuid,\n conditionGrade: claimConditionGradeSchema,\n inspectionNotes: z.string().trim().max(4000).optional(),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\nconst releaseBodySchema = z\n .object({\n lineId: uuid,\n action: z.literal('release'),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\nconst receivingBodySchema = z.union([releaseBodySchema, receiveBodySchema])\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.receiving.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 translateKey(key: string): string {\n return key\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 toReceivingAction(payload: Record<string, unknown>, context: ActionRouteContext): ReceivingAction {\n const releaseBody = releaseBodySchema.safeParse(payload)\n if (releaseBody.success) {\n const scopedPayload = withScopedPayload({\n id: releaseBody.data.lineId,\n updatedAt: releaseBody.data.updatedAt,\n }, context.ctx, context.translate)\n return { kind: 'release', input: claimLineReleaseQuarantineSchema.parse(scopedPayload) }\n }\n const body = receiveBodySchema.parse(payload)\n const scopedPayload = withScopedPayload({\n id: body.lineId,\n conditionGrade: body.conditionGrade,\n inspectionNotes: body.inspectionNotes,\n updatedAt: body.updatedAt,\n }, context.ctx, context.translate)\n return { kind: 'receive', input: claimLineReceiveSchema.parse(scopedPayload) }\n}\n\nfunction toGuardedAction(kind: ReceivingAction['kind'], payload: Record<string, unknown>): ReceivingAction {\n if (kind === 'release') {\n return { kind, input: claimLineReleaseQuarantineSchema.parse(payload) }\n }\n return { kind, input: claimLineReceiveSchema.parse(payload) }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n action: ReceivingAction,\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: action.input.id,\n operation: 'custom',\n mutationPayload: { ...action.input },\n },\n })\n}\n\nasync function executeReceivingAction(\n commandBus: CommandBus,\n action: ReceivingAction,\n ctx: CommandRuntimeContext,\n): Promise<{ lineId: string; claimId: string }> {\n if (action.kind === 'release') {\n const { result } = await commandBus.execute<ClaimLineReleaseQuarantineInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.release_quarantine',\n { input: action.input, ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })\n return result\n }\n const { result } = await commandBus.execute<ClaimLineReceiveInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.receive',\n { input: action.input, ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })\n return result\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 action = toReceivingAction(payload, context)\n const guarded = await runGuard(req, context, action)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const result = await executeReceivingAction(commandBus, commandAction, context.ctx)\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.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 if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n logger.error('warranty_claims.receiving.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: 'Receive or release a warranty claim line',\n methods: {\n POST: {\n summary: 'Grade a received line or release its quarantine hold',\n requestBody: { contentType: 'application/json', schema: receivingBodySchema },\n responses: [\n {\n status: 200,\n description: 'Receiving action applied',\n schema: z.object({\n ok: z.boolean(),\n lineId: z.string().uuid(),\n claimId: z.string().uuid(),\n }),\n },\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 {\n claimConditionGradeSchema,\n claimLineReceiveSchema,\n claimLineReleaseQuarantineSchema,\n type ClaimLineReceiveInput,\n type ClaimLineReleaseQuarantineInput,\n} from '../../data/validators'\nimport { WARRANTY_CLAIM_LINE_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\ntype ReceivingAction =\n | { kind: 'receive'; input: ClaimLineReceiveInput }\n | { kind: 'release'; input: ClaimLineReleaseQuarantineInput }\n\nconst uuid = z.string().uuid()\nconst optimisticLockTokenSchema = z.string().datetime().nullable().optional()\n\nconst receiveBodySchema = z\n .object({\n lineId: uuid,\n conditionGrade: claimConditionGradeSchema,\n inspectionNotes: z.string().trim().max(4000).optional(),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\nconst releaseBodySchema = z\n .object({\n lineId: uuid,\n action: z.literal('release'),\n updatedAt: optimisticLockTokenSchema,\n })\n .strict()\n\nconst receivingBodySchema = z.union([releaseBodySchema, receiveBodySchema])\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.receiving.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 toReceivingAction(payload: Record<string, unknown>, context: ActionRouteContext): ReceivingAction {\n const releaseBody = releaseBodySchema.safeParse(payload)\n if (releaseBody.success) {\n const scopedPayload = withScopedPayload({\n id: releaseBody.data.lineId,\n updatedAt: releaseBody.data.updatedAt,\n }, context.ctx, context.translate)\n return { kind: 'release', input: claimLineReleaseQuarantineSchema.parse(scopedPayload) }\n }\n const body = receiveBodySchema.parse(payload)\n const scopedPayload = withScopedPayload({\n id: body.lineId,\n conditionGrade: body.conditionGrade,\n inspectionNotes: body.inspectionNotes,\n updatedAt: body.updatedAt,\n }, context.ctx, context.translate)\n return { kind: 'receive', input: claimLineReceiveSchema.parse(scopedPayload) }\n}\n\nfunction toGuardedAction(kind: ReceivingAction['kind'], payload: Record<string, unknown>): ReceivingAction {\n if (kind === 'release') {\n return { kind, input: claimLineReleaseQuarantineSchema.parse(payload) }\n }\n return { kind, input: claimLineReceiveSchema.parse(payload) }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n action: ReceivingAction,\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: action.input.id,\n operation: 'custom',\n mutationPayload: { ...action.input },\n },\n })\n}\n\nasync function executeReceivingAction(\n commandBus: CommandBus,\n action: ReceivingAction,\n ctx: CommandRuntimeContext,\n translate: ActionRouteContext['translate'],\n): Promise<{ lineId: string; claimId: string }> {\n if (action.kind === 'release') {\n const { result } = await commandBus.execute<ClaimLineReleaseQuarantineInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.release_quarantine',\n { input: action.input, ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })\n return result\n }\n const { result } = await commandBus.execute<ClaimLineReceiveInput, { lineId: string; claimId: string }>(\n 'warranty_claims.claim_line.receive',\n { input: action.input, ctx },\n )\n if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })\n return result\n}\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const context = await resolveActionContext(req, translate)\n const payload = toRecord(await readJsonSafe(req, {}))\n const action = toReceivingAction(payload, context)\n const guarded = await runGuard(req, context, action)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate)\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.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 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.receiving.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: 'Receive or release a warranty claim line',\n methods: {\n POST: {\n summary: 'Grade a received line or release its quarantine hold',\n requestBody: { contentType: 'application/json', schema: receivingBodySchema },\n responses: [\n {\n status: 200,\n description: 'Receiving action applied',\n schema: z.object({\n ok: z.boolean(),\n lineId: z.string().uuid(),\n claimId: z.string().uuid(),\n }),\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["scopedPayload", "result"]
7
7
  }
@@ -3,6 +3,7 @@ import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
3
3
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
4
  import { buildIlikeTerm } from "@open-mercato/shared/lib/db/buildIlikeTerm";
5
5
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
6
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
6
7
  import { E } from "../../../../generated/entities.ids.generated.js";
7
8
  import * as F from "../../../../generated/entities/warranty_claim_registration/index.js";
8
9
  import { WarrantyClaimRegistration } from "../../data/entities.js";
@@ -67,7 +68,7 @@ function parseUpdateInput(input, ctx) {
67
68
  function hasOwn(input, key) {
68
69
  return Object.prototype.hasOwnProperty.call(input, key);
69
70
  }
70
- async function assertRegistrationSerialUnique(em, scope, serialNumber, excludeId) {
71
+ async function assertRegistrationSerialUnique(em, scope, serialNumber, excludeId, translate) {
71
72
  if (!serialNumber) return;
72
73
  const where = {
73
74
  serialNumber,
@@ -78,7 +79,9 @@ async function assertRegistrationSerialUnique(em, scope, serialNumber, excludeId
78
79
  if (excludeId) where.id = { $ne: excludeId };
79
80
  const existing = await em.count(WarrantyClaimRegistration, where);
80
81
  if (existing > 0) {
81
- throw new CrudHttpError(400, { error: "warranty_claims.errors.duplicateSerial" });
82
+ throw new CrudHttpError(400, {
83
+ error: translate("warranty_claims.errors.duplicateSerial", "A registration with this serial number already exists.")
84
+ });
82
85
  }
83
86
  }
84
87
  function parseDate(value) {
@@ -278,8 +281,9 @@ const crud = makeCrudRoute({
278
281
  const result = registrationCreateSchema.safeParse({ ...input, ...scope });
279
282
  if (!result.success) return;
280
283
  const em = ctx.container.resolve("em").fork();
284
+ const { translate } = await resolveTranslations();
281
285
  await assertOrderBelongsToCustomer(em, scope, result.data.orderId ?? null, result.data.customerId ?? null);
282
- await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null);
286
+ await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null, translate);
283
287
  },
284
288
  beforeUpdate: async (input, ctx) => {
285
289
  const scope = scopeFromContext(ctx);
@@ -304,7 +308,8 @@ const crud = makeCrudRoute({
304
308
  const effectiveOrderId = hasOwn(parsed, "orderId") ? parsed.orderId ?? null : existing.orderId ?? null;
305
309
  await assertOrderBelongsToCustomer(em, scope, effectiveOrderId, effectiveCustomerId);
306
310
  if (hasOwn(parsed, "serialNumber") && (parsed.serialNumber ?? null) !== (existing.serialNumber ?? null)) {
307
- await assertRegistrationSerialUnique(em, scope, parsed.serialNumber ?? null, existing.id);
311
+ const { translate } = await resolveTranslations();
312
+ await assertRegistrationSerialUnique(em, scope, parsed.serialNumber ?? null, existing.id, translate);
308
313
  }
309
314
  },
310
315
  afterCreate: async (entity) => {