@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +40 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +6 -1
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/communication_channels/lib/system-user.js +8 -3
- package/dist/modules/communication_channels/lib/system-user.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +20 -18
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +11 -12
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +1 -1
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +24 -27
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +8 -10
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +4 -9
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +3 -3
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +3 -3
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +4 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +12 -13
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +14 -15
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/registrations/route.js +9 -4
- package/dist/modules/warranty_claims/api/registrations/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +11 -12
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +20 -19
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -1
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +11 -12
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +22 -1
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -1
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/commands/shared.js +2 -2
- package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
- package/dist/modules/warranty_claims/lib/portalAuthGuard.js +31 -0
- package/dist/modules/warranty_claims/lib/portalAuthGuard.js.map +7 -0
- package/package.json +7 -7
- package/src/helpers/integration/communicationChannelsFixtures.ts +13 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +47 -2
- package/src/modules/communication_channels/lib/access-control.ts +28 -0
- package/src/modules/communication_channels/lib/system-user.ts +40 -22
- package/src/modules/warranty_claims/api/ai/assess/route.ts +23 -17
- package/src/modules/warranty_claims/api/credit-memo/route.ts +14 -13
- package/src/modules/warranty_claims/api/events/route.ts +1 -1
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +29 -27
- package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +9 -10
- package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +5 -9
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +5 -9
- package/src/modules/warranty_claims/api/portal/events/route.ts +13 -13
- package/src/modules/warranty_claims/api/receiving/route.ts +17 -15
- package/src/modules/warranty_claims/api/registrations/route.ts +9 -3
- package/src/modules/warranty_claims/api/replacement-order/route.ts +14 -13
- package/src/modules/warranty_claims/api/return-label/route.ts +21 -17
- package/src/modules/warranty_claims/api/risk/route.ts +3 -1
- package/src/modules/warranty_claims/api/sales-return/route.ts +14 -13
- package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +23 -1
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +3 -1
- package/src/modules/warranty_claims/commands/shared.ts +7 -1
- package/src/modules/warranty_claims/lib/portalAuthGuard.ts +30 -0
|
@@ -22,16 +22,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
22
22
|
const contextOrResponse = await resolvePortalActionContext(req)
|
|
23
23
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
24
24
|
const context = contextOrResponse
|
|
25
|
+
const { translate } = await resolveTranslations()
|
|
25
26
|
const claimId = await resolvePortalClaimId(ctx)
|
|
26
27
|
if (!claimId) {
|
|
27
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
28
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
28
29
|
}
|
|
29
30
|
const claim = await loadOwnedClaim(context, claimId)
|
|
30
31
|
if (!claim) {
|
|
31
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
32
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
32
33
|
}
|
|
33
34
|
if (claim.status !== 'draft') {
|
|
34
|
-
const { translate } = await resolveTranslations()
|
|
35
35
|
return NextResponse.json(
|
|
36
36
|
{ ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
|
|
37
37
|
{ status: 400 },
|
|
@@ -24,16 +24,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
24
24
|
const contextOrResponse = await resolvePortalActionContext(req)
|
|
25
25
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
26
26
|
const context = contextOrResponse
|
|
27
|
+
const { translate } = await resolveTranslations()
|
|
27
28
|
const claimId = await resolvePortalClaimId(ctx)
|
|
28
29
|
if (!claimId) {
|
|
29
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
30
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
30
31
|
}
|
|
31
32
|
const claim = await loadOwnedClaim(context, claimId)
|
|
32
33
|
if (!claim) {
|
|
33
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
34
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
34
35
|
}
|
|
35
36
|
if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {
|
|
36
|
-
const { translate } = await resolveTranslations()
|
|
37
37
|
return NextResponse.json(
|
|
38
38
|
{ ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
|
|
39
39
|
{ status: 400 },
|
|
@@ -11,7 +11,8 @@ import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mer
|
|
|
11
11
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
12
12
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
13
13
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
14
|
-
import {
|
|
14
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
15
|
+
import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
|
|
15
16
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
16
17
|
import { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'
|
|
17
18
|
import {
|
|
@@ -154,14 +155,9 @@ function serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[])
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
161
|
-
}
|
|
162
|
-
if (!auth.customerEntityId) {
|
|
163
|
-
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })
|
|
164
|
-
}
|
|
158
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
159
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
160
|
+
const auth = authOrResponse
|
|
165
161
|
const container = await createRequestContainer()
|
|
166
162
|
const em = container.resolve('em') as EntityManager
|
|
167
163
|
const commandAuth: NonNullable<AuthContext> = {
|
|
@@ -7,13 +7,15 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
7
7
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
9
9
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
11
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
12
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
-
import {
|
|
13
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
13
14
|
import { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'
|
|
14
15
|
import type { CommentClaimInput } from '../../../data/validators'
|
|
15
16
|
import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'
|
|
16
17
|
import { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'
|
|
18
|
+
import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
|
|
17
19
|
|
|
18
20
|
export const metadata = {
|
|
19
21
|
GET: { requireAuth: false },
|
|
@@ -71,13 +73,9 @@ function serializeEvent(event: WarrantyClaimEvent) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (!auth.customerEntityId) {
|
|
79
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
80
|
-
}
|
|
76
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
77
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
78
|
+
const auth = authOrResponse
|
|
81
79
|
const container = await createRequestContainer()
|
|
82
80
|
const em = container.resolve('em') as EntityManager
|
|
83
81
|
const commandAuth: NonNullable<AuthContext> = {
|
|
@@ -146,14 +144,15 @@ export async function GET(req: Request) {
|
|
|
146
144
|
const contextOrResponse = await resolvePortalContext(req)
|
|
147
145
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
148
146
|
const context = contextOrResponse
|
|
147
|
+
const { translate } = await resolveTranslations()
|
|
149
148
|
const url = new URL(req.url)
|
|
150
149
|
const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })
|
|
151
150
|
if (!parsed.success) {
|
|
152
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
151
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
153
152
|
}
|
|
154
153
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
155
154
|
if (!claim) {
|
|
156
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
155
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
157
156
|
}
|
|
158
157
|
const events = await findWithDecryption(
|
|
159
158
|
context.em,
|
|
@@ -169,19 +168,20 @@ export async function POST(req: Request) {
|
|
|
169
168
|
const contextOrResponse = await resolvePortalContext(req)
|
|
170
169
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
171
170
|
const context = contextOrResponse
|
|
171
|
+
const { translate } = await resolveTranslations()
|
|
172
172
|
let body: unknown
|
|
173
173
|
try {
|
|
174
174
|
body = await req.json()
|
|
175
175
|
} catch {
|
|
176
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
176
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
177
177
|
}
|
|
178
178
|
const parsed = portalCommentSchema.safeParse(body)
|
|
179
179
|
if (!parsed.success) {
|
|
180
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
180
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
181
181
|
}
|
|
182
182
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
183
183
|
if (!claim) {
|
|
184
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
184
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
185
185
|
}
|
|
186
186
|
const input: CommentClaimInput = {
|
|
187
187
|
claimId: claim.id,
|
|
@@ -7,6 +7,7 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
9
9
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
11
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
11
12
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
12
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -63,20 +64,19 @@ function toRecord(value: unknown): Record<string, unknown> {
|
|
|
63
64
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
67
|
+
async function resolveActionContext(
|
|
68
|
+
req: Request,
|
|
69
|
+
translate: ActionRouteContext['translate'],
|
|
70
|
+
): Promise<ActionRouteContext> {
|
|
71
71
|
const container = await createRequestContainer()
|
|
72
72
|
const auth = await getAuthFromRequest(req)
|
|
73
73
|
if (!auth || !auth.tenantId) {
|
|
74
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
74
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
75
75
|
}
|
|
76
76
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
77
77
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
78
78
|
if (!organizationId) {
|
|
79
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
|
|
79
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
|
|
80
80
|
}
|
|
81
81
|
return {
|
|
82
82
|
ctx: {
|
|
@@ -89,7 +89,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
89
89
|
},
|
|
90
90
|
tenantId: auth.tenantId,
|
|
91
91
|
organizationId,
|
|
92
|
-
translate
|
|
92
|
+
translate,
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -126,7 +126,7 @@ async function runGuard(
|
|
|
126
126
|
): Promise<RouteMutationGuardResult> {
|
|
127
127
|
const userId = context.ctx.auth?.sub
|
|
128
128
|
if (!userId) {
|
|
129
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
129
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
130
130
|
}
|
|
131
131
|
return runRouteMutationGuards({
|
|
132
132
|
container: context.ctx.container,
|
|
@@ -149,26 +149,28 @@ async function executeReceivingAction(
|
|
|
149
149
|
commandBus: CommandBus,
|
|
150
150
|
action: ReceivingAction,
|
|
151
151
|
ctx: CommandRuntimeContext,
|
|
152
|
+
translate: ActionRouteContext['translate'],
|
|
152
153
|
): Promise<{ lineId: string; claimId: string }> {
|
|
153
154
|
if (action.kind === 'release') {
|
|
154
155
|
const { result } = await commandBus.execute<ClaimLineReleaseQuarantineInput, { lineId: string; claimId: string }>(
|
|
155
156
|
'warranty_claims.claim_line.release_quarantine',
|
|
156
157
|
{ input: action.input, ctx },
|
|
157
158
|
)
|
|
158
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
159
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
159
160
|
return result
|
|
160
161
|
}
|
|
161
162
|
const { result } = await commandBus.execute<ClaimLineReceiveInput, { lineId: string; claimId: string }>(
|
|
162
163
|
'warranty_claims.claim_line.receive',
|
|
163
164
|
{ input: action.input, ctx },
|
|
164
165
|
)
|
|
165
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
166
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
166
167
|
return result
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
export async function POST(req: Request) {
|
|
171
|
+
const { translate } = await resolveTranslations()
|
|
170
172
|
try {
|
|
171
|
-
const context = await resolveActionContext(req)
|
|
173
|
+
const context = await resolveActionContext(req, translate)
|
|
172
174
|
const payload = toRecord(await readJsonSafe(req, {}))
|
|
173
175
|
const action = toReceivingAction(payload, context)
|
|
174
176
|
const guarded = await runGuard(req, context, action)
|
|
@@ -178,7 +180,7 @@ export async function POST(req: Request) {
|
|
|
178
180
|
const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action
|
|
179
181
|
|
|
180
182
|
const commandBus = context.ctx.container.resolve('commandBus') as CommandBus
|
|
181
|
-
const result = await executeReceivingAction(commandBus, commandAction, context.ctx)
|
|
183
|
+
const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate)
|
|
182
184
|
|
|
183
185
|
await guarded.runAfterSuccess()
|
|
184
186
|
|
|
@@ -190,10 +192,10 @@ export async function POST(req: Request) {
|
|
|
190
192
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
191
193
|
}
|
|
192
194
|
if (err instanceof z.ZodError) {
|
|
193
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
195
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
194
196
|
}
|
|
195
197
|
logger.error('warranty_claims.receiving.post failed', { err })
|
|
196
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
198
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -5,6 +5,7 @@ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
|
5
5
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
6
6
|
import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
|
|
7
7
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
8
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
8
9
|
import { E } from '#generated/entities.ids.generated'
|
|
9
10
|
import * as F from '#generated/entities/warranty_claim_registration'
|
|
10
11
|
import { WarrantyClaimRegistration } from '../../data/entities'
|
|
@@ -92,6 +93,7 @@ async function assertRegistrationSerialUnique(
|
|
|
92
93
|
scope: { organizationId: string; tenantId: string },
|
|
93
94
|
serialNumber: string | null | undefined,
|
|
94
95
|
excludeId: string | null,
|
|
96
|
+
translate: (key: string, fallback?: string) => string,
|
|
95
97
|
): Promise<void> {
|
|
96
98
|
if (!serialNumber) return
|
|
97
99
|
const where: FilterQuery<WarrantyClaimRegistration> = {
|
|
@@ -103,7 +105,9 @@ async function assertRegistrationSerialUnique(
|
|
|
103
105
|
if (excludeId) where.id = { $ne: excludeId }
|
|
104
106
|
const existing = await em.count(WarrantyClaimRegistration, where)
|
|
105
107
|
if (existing > 0) {
|
|
106
|
-
throw new CrudHttpError(400, {
|
|
108
|
+
throw new CrudHttpError(400, {
|
|
109
|
+
error: translate('warranty_claims.errors.duplicateSerial', 'A registration with this serial number already exists.'),
|
|
110
|
+
})
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -320,8 +324,9 @@ const crud = makeCrudRoute<RawRegistrationInput, RawRegistrationInput, Registrat
|
|
|
320
324
|
const result = registrationCreateSchema.safeParse({ ...input, ...scope })
|
|
321
325
|
if (!result.success) return
|
|
322
326
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
327
|
+
const { translate } = await resolveTranslations()
|
|
323
328
|
await assertOrderBelongsToCustomer(em, scope, result.data.orderId ?? null, result.data.customerId ?? null)
|
|
324
|
-
await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null)
|
|
329
|
+
await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null, translate)
|
|
325
330
|
},
|
|
326
331
|
beforeUpdate: async (input, ctx) => {
|
|
327
332
|
const scope = scopeFromContext(ctx)
|
|
@@ -346,7 +351,8 @@ const crud = makeCrudRoute<RawRegistrationInput, RawRegistrationInput, Registrat
|
|
|
346
351
|
const effectiveOrderId = hasOwn(parsed, 'orderId') ? (parsed.orderId ?? null) : (existing.orderId ?? null)
|
|
347
352
|
await assertOrderBelongsToCustomer(em, scope, effectiveOrderId, effectiveCustomerId)
|
|
348
353
|
if (hasOwn(parsed, 'serialNumber') && (parsed.serialNumber ?? null) !== (existing.serialNumber ?? null)) {
|
|
349
|
-
|
|
354
|
+
const { translate } = await resolveTranslations()
|
|
355
|
+
await assertRegistrationSerialUnique(em, scope, parsed.serialNumber ?? null, existing.id, translate)
|
|
350
356
|
}
|
|
351
357
|
},
|
|
352
358
|
afterCreate: async (entity) => {
|
|
@@ -7,6 +7,7 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
9
9
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
11
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
11
12
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
12
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -57,24 +58,23 @@ export const metadata = {
|
|
|
57
58
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.orders.manage'] },
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
function translateKey(key: string): string {
|
|
61
|
-
return key
|
|
62
|
-
}
|
|
63
|
-
|
|
64
61
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
65
62
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
async function resolveActionContext(
|
|
65
|
+
async function resolveActionContext(
|
|
66
|
+
req: Request,
|
|
67
|
+
translate: ActionRouteContext['translate'],
|
|
68
|
+
): Promise<ActionRouteContext> {
|
|
69
69
|
const container = await createRequestContainer()
|
|
70
70
|
const auth = await getAuthFromRequest(req)
|
|
71
71
|
if (!auth || !auth.tenantId) {
|
|
72
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
72
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
73
73
|
}
|
|
74
74
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
75
75
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
76
76
|
if (!organizationId) {
|
|
77
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
|
|
77
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
|
|
78
78
|
}
|
|
79
79
|
return {
|
|
80
80
|
ctx: {
|
|
@@ -87,7 +87,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
87
87
|
},
|
|
88
88
|
tenantId: auth.tenantId,
|
|
89
89
|
organizationId,
|
|
90
|
-
translate
|
|
90
|
+
translate,
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -114,7 +114,7 @@ async function runGuard(
|
|
|
114
114
|
): Promise<RouteMutationGuardResult> {
|
|
115
115
|
const userId = context.ctx.auth?.sub
|
|
116
116
|
if (!userId) {
|
|
117
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
117
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
118
118
|
}
|
|
119
119
|
return runRouteMutationGuards({
|
|
120
120
|
container: context.ctx.container,
|
|
@@ -134,8 +134,9 @@ async function runGuard(
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
export async function POST(req: Request) {
|
|
137
|
+
const { translate } = await resolveTranslations()
|
|
137
138
|
try {
|
|
138
|
-
const context = await resolveActionContext(req)
|
|
139
|
+
const context = await resolveActionContext(req, translate)
|
|
139
140
|
const requestInput = toReplacementOrderRequest(toRecord(await readJsonSafe(req, {})))
|
|
140
141
|
const guarded = await runGuard(req, context, requestInput)
|
|
141
142
|
if (!guarded.ok) {
|
|
@@ -149,7 +150,7 @@ export async function POST(req: Request) {
|
|
|
149
150
|
'warranty_claims.claim.create_replacement_order',
|
|
150
151
|
{ input: toCommandInput(guardedInput, context), ctx: context.ctx },
|
|
151
152
|
)
|
|
152
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
153
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
153
154
|
await guarded.runAfterSuccess()
|
|
154
155
|
return NextResponse.json({
|
|
155
156
|
replacementOrderId: result.replacementOrderId,
|
|
@@ -162,10 +163,10 @@ export async function POST(req: Request) {
|
|
|
162
163
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
163
164
|
}
|
|
164
165
|
if (err instanceof z.ZodError) {
|
|
165
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
166
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
166
167
|
}
|
|
167
168
|
logger.error('warranty_claims.replacement-order.post failed', { err })
|
|
168
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
169
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -8,6 +8,7 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
8
8
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
9
9
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
10
10
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
11
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
12
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
12
13
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
13
14
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -70,24 +71,23 @@ export const metadata = {
|
|
|
70
71
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
function translateKey(key: string): string {
|
|
74
|
-
return key
|
|
75
|
-
}
|
|
76
|
-
|
|
77
74
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
78
75
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
async function resolveActionContext(
|
|
78
|
+
async function resolveActionContext(
|
|
79
|
+
req: Request,
|
|
80
|
+
translate: ActionRouteContext['translate'],
|
|
81
|
+
): Promise<ActionRouteContext> {
|
|
82
82
|
const container = await createRequestContainer()
|
|
83
83
|
const auth = await getAuthFromRequest(req)
|
|
84
84
|
if (!auth || !auth.tenantId) {
|
|
85
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
85
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
86
86
|
}
|
|
87
87
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
88
88
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
89
89
|
if (!organizationId) {
|
|
90
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
|
|
90
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
|
|
91
91
|
}
|
|
92
92
|
return {
|
|
93
93
|
ctx: {
|
|
@@ -100,7 +100,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
100
100
|
},
|
|
101
101
|
tenantId: auth.tenantId,
|
|
102
102
|
organizationId,
|
|
103
|
-
translate
|
|
103
|
+
translate,
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -143,7 +143,7 @@ async function runGuard(
|
|
|
143
143
|
): Promise<RouteMutationGuardResult> {
|
|
144
144
|
const userId = context.ctx.auth?.sub
|
|
145
145
|
if (!userId) {
|
|
146
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
146
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
147
147
|
}
|
|
148
148
|
return runRouteMutationGuards({
|
|
149
149
|
container: context.ctx.container,
|
|
@@ -168,7 +168,7 @@ async function loadClaimAndLines(
|
|
|
168
168
|
): Promise<{ claim: WarrantyClaim; lines: WarrantyClaimLine[] }> {
|
|
169
169
|
const scope = { tenantId: context.tenantId, organizationId: context.organizationId }
|
|
170
170
|
const em = (context.ctx.container.resolve('em') as EntityManager).fork()
|
|
171
|
-
const claim = await requireScopedClaim(em, claimId, scope)
|
|
171
|
+
const claim = await requireScopedClaim(em, claimId, scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
|
|
172
172
|
const lines = await findWithDecryption(
|
|
173
173
|
em,
|
|
174
174
|
WarrantyClaimLine,
|
|
@@ -179,9 +179,9 @@ async function loadClaimAndLines(
|
|
|
179
179
|
return { claim, lines }
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
function assertGeneratableStatus(claim: WarrantyClaim): void {
|
|
182
|
+
function assertGeneratableStatus(claim: WarrantyClaim, translate: ActionRouteContext['translate']): void {
|
|
183
183
|
if (claim.status !== 'approved' && claim.status !== 'awaiting_return') {
|
|
184
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.returnLabelInvalidStatus' })
|
|
184
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.returnLabelInvalidStatus', 'Return labels can only be created for approved claims that are awaiting return.') })
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -189,12 +189,13 @@ async function executeSetReturnLabel(
|
|
|
189
189
|
commandBus: CommandBus,
|
|
190
190
|
input: ClaimSetReturnLabelInput,
|
|
191
191
|
ctx: CommandRuntimeContext,
|
|
192
|
+
translate: ActionRouteContext['translate'],
|
|
192
193
|
): Promise<ReturnLabelCreatedResponse> {
|
|
193
194
|
const { result } = await commandBus.execute<ClaimSetReturnLabelInput, ReturnLabelCommandResult>(
|
|
194
195
|
'warranty_claims.claim.set_return_label',
|
|
195
196
|
{ input, ctx },
|
|
196
197
|
)
|
|
197
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
198
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
198
199
|
return {
|
|
199
200
|
status: 'created',
|
|
200
201
|
labelUrl: result.labelUrl,
|
|
@@ -211,8 +212,9 @@ export function firstTranslatableIssueKey(err: z.ZodError): string {
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
export async function POST(req: Request) {
|
|
215
|
+
const { translate } = await resolveTranslations()
|
|
214
216
|
try {
|
|
215
|
-
const context = await resolveActionContext(req)
|
|
217
|
+
const context = await resolveActionContext(req, translate)
|
|
216
218
|
const requestInput = toReturnLabelRequest(toRecord(await readJsonSafe(req, {})))
|
|
217
219
|
const guarded = await runGuard(req, context, requestInput)
|
|
218
220
|
if (!guarded.ok) {
|
|
@@ -226,13 +228,14 @@ export async function POST(req: Request) {
|
|
|
226
228
|
commandBus,
|
|
227
229
|
toSetReturnLabelInput(guardedInput, context),
|
|
228
230
|
context.ctx,
|
|
231
|
+
translate,
|
|
229
232
|
)
|
|
230
233
|
await guarded.runAfterSuccess()
|
|
231
234
|
return NextResponse.json(response)
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
const { claim, lines } = await loadClaimAndLines(context, guardedInput.claimId)
|
|
235
|
-
assertGeneratableStatus(claim)
|
|
238
|
+
assertGeneratableStatus(claim, translate)
|
|
236
239
|
await enforceWarrantyClaimOptimisticLock(
|
|
237
240
|
context.ctx,
|
|
238
241
|
claim,
|
|
@@ -260,6 +263,7 @@ export async function POST(req: Request) {
|
|
|
260
263
|
carrier: result.carrier,
|
|
261
264
|
}, context),
|
|
262
265
|
context.ctx,
|
|
266
|
+
translate,
|
|
263
267
|
)
|
|
264
268
|
await guarded.runAfterSuccess()
|
|
265
269
|
return NextResponse.json(response)
|
|
@@ -270,10 +274,10 @@ export async function POST(req: Request) {
|
|
|
270
274
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
271
275
|
}
|
|
272
276
|
if (err instanceof z.ZodError) {
|
|
273
|
-
return NextResponse.json({ error: firstTranslatableIssueKey(err) }, { status: 400 })
|
|
277
|
+
return NextResponse.json({ error: translate(firstTranslatableIssueKey(err), 'Invalid input') }, { status: 400 })
|
|
274
278
|
}
|
|
275
279
|
logger.error('warranty_claims.return-label.post failed', { err })
|
|
276
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
280
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
283
|
|
|
@@ -39,6 +39,7 @@ type RiskRouteContext = {
|
|
|
39
39
|
tenantId: string
|
|
40
40
|
organizationId: string
|
|
41
41
|
scope: WarrantyClaimScope
|
|
42
|
+
translate: (key: string, fallback?: string) => string
|
|
42
43
|
em: EntityManager
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -63,6 +64,7 @@ async function resolveRiskContext(req: Request): Promise<RiskRouteContext> {
|
|
|
63
64
|
tenantId: auth.tenantId,
|
|
64
65
|
organizationId,
|
|
65
66
|
scope: { tenantId: auth.tenantId, organizationId },
|
|
67
|
+
translate,
|
|
66
68
|
em,
|
|
67
69
|
}
|
|
68
70
|
}
|
|
@@ -72,7 +74,7 @@ export async function GET(req: Request) {
|
|
|
72
74
|
const context = await resolveRiskContext(req)
|
|
73
75
|
const url = new URL(req.url)
|
|
74
76
|
const query = querySchema.parse(Object.fromEntries(url.searchParams))
|
|
75
|
-
const claim = await requireScopedClaim(context.em, query.claimId, context.scope)
|
|
77
|
+
const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
|
|
76
78
|
const lines = await findWithDecryption(
|
|
77
79
|
context.em,
|
|
78
80
|
WarrantyClaimLine,
|
|
@@ -7,6 +7,7 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
9
9
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
11
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
11
12
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
12
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -55,24 +56,23 @@ export const metadata = {
|
|
|
55
56
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.returns.create'] },
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function translateKey(key: string): string {
|
|
59
|
-
return key
|
|
60
|
-
}
|
|
61
|
-
|
|
62
59
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
63
60
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
async function resolveActionContext(
|
|
63
|
+
async function resolveActionContext(
|
|
64
|
+
req: Request,
|
|
65
|
+
translate: ActionRouteContext['translate'],
|
|
66
|
+
): Promise<ActionRouteContext> {
|
|
67
67
|
const container = await createRequestContainer()
|
|
68
68
|
const auth = await getAuthFromRequest(req)
|
|
69
69
|
if (!auth || !auth.tenantId) {
|
|
70
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
70
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
71
71
|
}
|
|
72
72
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
73
73
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
74
74
|
if (!organizationId) {
|
|
75
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
|
|
75
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
|
|
76
76
|
}
|
|
77
77
|
return {
|
|
78
78
|
ctx: {
|
|
@@ -85,7 +85,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
85
85
|
},
|
|
86
86
|
tenantId: auth.tenantId,
|
|
87
87
|
organizationId,
|
|
88
|
-
translate
|
|
88
|
+
translate,
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -111,7 +111,7 @@ async function runGuard(
|
|
|
111
111
|
): Promise<RouteMutationGuardResult> {
|
|
112
112
|
const userId = context.ctx.auth?.sub
|
|
113
113
|
if (!userId) {
|
|
114
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
114
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
115
115
|
}
|
|
116
116
|
return runRouteMutationGuards({
|
|
117
117
|
container: context.ctx.container,
|
|
@@ -131,8 +131,9 @@ async function runGuard(
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
export async function POST(req: Request) {
|
|
134
|
+
const { translate } = await resolveTranslations()
|
|
134
135
|
try {
|
|
135
|
-
const context = await resolveActionContext(req)
|
|
136
|
+
const context = await resolveActionContext(req, translate)
|
|
136
137
|
const requestInput = toSalesReturnRequest(toRecord(await readJsonSafe(req, {})))
|
|
137
138
|
const guarded = await runGuard(req, context, requestInput)
|
|
138
139
|
if (!guarded.ok) {
|
|
@@ -144,7 +145,7 @@ export async function POST(req: Request) {
|
|
|
144
145
|
'warranty_claims.claim.create_sales_return',
|
|
145
146
|
{ input: toCommandInput(guardedInput, context), ctx: context.ctx },
|
|
146
147
|
)
|
|
147
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
148
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
148
149
|
await guarded.runAfterSuccess()
|
|
149
150
|
return NextResponse.json({ salesReturnId: result.salesReturnId, skippedLineIds: result.skippedLineIds })
|
|
150
151
|
} catch (err) {
|
|
@@ -154,10 +155,10 @@ export async function POST(req: Request) {
|
|
|
154
155
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
155
156
|
}
|
|
156
157
|
if (err instanceof z.ZodError) {
|
|
157
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
158
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
158
159
|
}
|
|
159
160
|
logger.error('warranty_claims.sales-return.post failed', { err })
|
|
160
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
161
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
|