@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7135.1.19bf969756
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/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +5 -0
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/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/customers/api/activities/route.js +13 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
- package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/complete/route.js +5 -0
- package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
- package/dist/modules/customers/api/labels/assign/route.js +5 -0
- package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
- package/dist/modules/customers/api/labels/route.js +5 -0
- package/dist/modules/customers/api/labels/route.js.map +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js +5 -0
- package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
- package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +13 -0
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +5 -0
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
- package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
- package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +5 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +5 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +13 -0
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +5 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/forward/route.js +5 -0
- package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/reply/route.js +5 -0
- package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +9 -0
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/token/[token]/route.js +5 -0
- package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +5 -0
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +5 -0
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +5 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +25 -18
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +5 -0
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +16 -12
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +6 -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 +8 -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 +8 -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 +9 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +17 -13
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +19 -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 +16 -12
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +25 -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 +16 -12
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +5 -0
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/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/route.js +5 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/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/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/communicationChannelsFixtures.ts +13 -0
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
- package/src/modules/auth/api/profile/route.ts +5 -0
- package/src/modules/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/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
- package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
- package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
- package/src/modules/customers/api/interactions/complete/route.ts +5 -0
- package/src/modules/customers/api/labels/assign/route.ts +5 -0
- package/src/modules/customers/api/labels/route.ts +5 -0
- package/src/modules/customers/api/labels/unassign/route.ts +5 -0
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
- package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
- package/src/modules/customers/api/pipelines/route.ts +13 -0
- package/src/modules/customers/api/settings/address-format/route.ts +5 -0
- package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
- package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
- package/src/modules/customers/api/tags/assign/route.ts +5 -0
- package/src/modules/customers/api/tags/unassign/route.ts +5 -0
- package/src/modules/customers/api/todos/route.ts +13 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
- package/src/modules/directory/api/organization-branding/route.ts +5 -0
- package/src/modules/eudr/api/plots/import/route.ts +7 -0
- package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
- package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
- package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
- package/src/modules/messages/api/[id]/forward/route.ts +5 -0
- package/src/modules/messages/api/[id]/reply/route.ts +5 -0
- package/src/modules/messages/api/[id]/route.ts +9 -0
- package/src/modules/messages/api/token/[token]/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
- package/src/modules/sales/api/quotes/accept/route.ts +5 -0
- package/src/modules/sales/api/quotes/convert/route.ts +5 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
- package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
- package/src/modules/staff/api/team-members/self/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
- package/src/modules/warranty_claims/api/ai/assess/route.ts +28 -17
- package/src/modules/warranty_claims/api/assign/route.ts +5 -0
- package/src/modules/warranty_claims/api/credit-memo/route.ts +19 -13
- package/src/modules/warranty_claims/api/events/route.ts +6 -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 +8 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +8 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +10 -9
- package/src/modules/warranty_claims/api/portal/events/route.ts +18 -13
- package/src/modules/warranty_claims/api/receiving/route.ts +22 -15
- package/src/modules/warranty_claims/api/registrations/route.ts +9 -3
- package/src/modules/warranty_claims/api/replacement-order/route.ts +19 -13
- package/src/modules/warranty_claims/api/return-label/route.ts +26 -17
- package/src/modules/warranty_claims/api/risk/route.ts +3 -1
- package/src/modules/warranty_claims/api/sales-return/route.ts +19 -13
- package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
- package/src/modules/warranty_claims/api/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/transition/route.ts +5 -0
- package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +23 -1
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
- 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
- package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
4
4
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
5
6
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
6
7
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
7
8
|
import type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'
|
|
@@ -23,16 +24,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
23
24
|
const contextOrResponse = await resolvePortalActionContext(req)
|
|
24
25
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
25
26
|
const context = contextOrResponse
|
|
27
|
+
const { translate } = await resolveTranslations()
|
|
26
28
|
const claimId = await resolvePortalClaimId(ctx)
|
|
27
29
|
if (!claimId) {
|
|
28
|
-
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 })
|
|
29
31
|
}
|
|
30
32
|
const claim = await loadOwnedClaim(context, claimId)
|
|
31
33
|
if (!claim) {
|
|
32
|
-
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 })
|
|
33
35
|
}
|
|
34
36
|
if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {
|
|
35
|
-
const { translate } = await resolveTranslations()
|
|
36
37
|
return NextResponse.json(
|
|
37
38
|
{ ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
|
|
38
39
|
{ status: 400 },
|
|
@@ -61,6 +62,10 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
61
62
|
if (isCrudHttpError(err)) {
|
|
62
63
|
return NextResponse.json(err.body, { status: err.status })
|
|
63
64
|
}
|
|
65
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
66
|
+
if (interceptorRejection) {
|
|
67
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
68
|
+
}
|
|
64
69
|
throw err
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -5,12 +5,14 @@ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
6
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
7
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
8
9
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
9
10
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
10
11
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
12
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
12
13
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
13
|
-
import {
|
|
14
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
15
|
+
import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
|
|
14
16
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
15
17
|
import { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'
|
|
16
18
|
import {
|
|
@@ -153,14 +155,9 @@ function serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[])
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
160
|
-
}
|
|
161
|
-
if (!auth.customerEntityId) {
|
|
162
|
-
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })
|
|
163
|
-
}
|
|
158
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
159
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
160
|
+
const auth = authOrResponse
|
|
164
161
|
const container = await createRequestContainer()
|
|
165
162
|
const em = container.resolve('em') as EntityManager
|
|
166
163
|
const commandAuth: NonNullable<AuthContext> = {
|
|
@@ -522,6 +519,10 @@ export async function POST(req: Request) {
|
|
|
522
519
|
if (isCrudHttpError(err)) {
|
|
523
520
|
return NextResponse.json(err.body, { status: err.status })
|
|
524
521
|
}
|
|
522
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
523
|
+
if (interceptorRejection) {
|
|
524
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
525
|
+
}
|
|
525
526
|
throw err
|
|
526
527
|
}
|
|
527
528
|
}
|
|
@@ -5,14 +5,17 @@ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
6
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
7
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
8
9
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
11
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
10
12
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
|
-
import {
|
|
13
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
12
14
|
import { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'
|
|
13
15
|
import type { CommentClaimInput } from '../../../data/validators'
|
|
14
16
|
import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'
|
|
15
17
|
import { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'
|
|
18
|
+
import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
|
|
16
19
|
|
|
17
20
|
export const metadata = {
|
|
18
21
|
GET: { requireAuth: false },
|
|
@@ -70,13 +73,9 @@ function serializeEvent(event: WarrantyClaimEvent) {
|
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (!auth.customerEntityId) {
|
|
78
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
79
|
-
}
|
|
76
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
77
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
78
|
+
const auth = authOrResponse
|
|
80
79
|
const container = await createRequestContainer()
|
|
81
80
|
const em = container.resolve('em') as EntityManager
|
|
82
81
|
const commandAuth: NonNullable<AuthContext> = {
|
|
@@ -145,14 +144,15 @@ export async function GET(req: Request) {
|
|
|
145
144
|
const contextOrResponse = await resolvePortalContext(req)
|
|
146
145
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
147
146
|
const context = contextOrResponse
|
|
147
|
+
const { translate } = await resolveTranslations()
|
|
148
148
|
const url = new URL(req.url)
|
|
149
149
|
const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })
|
|
150
150
|
if (!parsed.success) {
|
|
151
|
-
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 })
|
|
152
152
|
}
|
|
153
153
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
154
154
|
if (!claim) {
|
|
155
|
-
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 })
|
|
156
156
|
}
|
|
157
157
|
const events = await findWithDecryption(
|
|
158
158
|
context.em,
|
|
@@ -168,19 +168,20 @@ export async function POST(req: Request) {
|
|
|
168
168
|
const contextOrResponse = await resolvePortalContext(req)
|
|
169
169
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
170
170
|
const context = contextOrResponse
|
|
171
|
+
const { translate } = await resolveTranslations()
|
|
171
172
|
let body: unknown
|
|
172
173
|
try {
|
|
173
174
|
body = await req.json()
|
|
174
175
|
} catch {
|
|
175
|
-
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 })
|
|
176
177
|
}
|
|
177
178
|
const parsed = portalCommentSchema.safeParse(body)
|
|
178
179
|
if (!parsed.success) {
|
|
179
|
-
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 })
|
|
180
181
|
}
|
|
181
182
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
182
183
|
if (!claim) {
|
|
183
|
-
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 })
|
|
184
185
|
}
|
|
185
186
|
const input: CommentClaimInput = {
|
|
186
187
|
claimId: claim.id,
|
|
@@ -213,6 +214,10 @@ export async function POST(req: Request) {
|
|
|
213
214
|
if (isCrudHttpError(err)) {
|
|
214
215
|
return NextResponse.json(err.body, { status: err.status })
|
|
215
216
|
}
|
|
217
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
218
|
+
if (interceptorRejection) {
|
|
219
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
220
|
+
}
|
|
216
221
|
throw err
|
|
217
222
|
}
|
|
218
223
|
|
|
@@ -5,7 +5,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
6
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
8
9
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
11
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
10
12
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
11
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -62,20 +64,19 @@ function toRecord(value: unknown): Record<string, unknown> {
|
|
|
62
64
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
67
|
+
async function resolveActionContext(
|
|
68
|
+
req: Request,
|
|
69
|
+
translate: ActionRouteContext['translate'],
|
|
70
|
+
): Promise<ActionRouteContext> {
|
|
70
71
|
const container = await createRequestContainer()
|
|
71
72
|
const auth = await getAuthFromRequest(req)
|
|
72
73
|
if (!auth || !auth.tenantId) {
|
|
73
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
74
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
74
75
|
}
|
|
75
76
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
76
77
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
77
78
|
if (!organizationId) {
|
|
78
|
-
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.') })
|
|
79
80
|
}
|
|
80
81
|
return {
|
|
81
82
|
ctx: {
|
|
@@ -88,7 +89,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
88
89
|
},
|
|
89
90
|
tenantId: auth.tenantId,
|
|
90
91
|
organizationId,
|
|
91
|
-
translate
|
|
92
|
+
translate,
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -125,7 +126,7 @@ async function runGuard(
|
|
|
125
126
|
): Promise<RouteMutationGuardResult> {
|
|
126
127
|
const userId = context.ctx.auth?.sub
|
|
127
128
|
if (!userId) {
|
|
128
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
129
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
129
130
|
}
|
|
130
131
|
return runRouteMutationGuards({
|
|
131
132
|
container: context.ctx.container,
|
|
@@ -148,26 +149,28 @@ async function executeReceivingAction(
|
|
|
148
149
|
commandBus: CommandBus,
|
|
149
150
|
action: ReceivingAction,
|
|
150
151
|
ctx: CommandRuntimeContext,
|
|
152
|
+
translate: ActionRouteContext['translate'],
|
|
151
153
|
): Promise<{ lineId: string; claimId: string }> {
|
|
152
154
|
if (action.kind === 'release') {
|
|
153
155
|
const { result } = await commandBus.execute<ClaimLineReleaseQuarantineInput, { lineId: string; claimId: string }>(
|
|
154
156
|
'warranty_claims.claim_line.release_quarantine',
|
|
155
157
|
{ input: action.input, ctx },
|
|
156
158
|
)
|
|
157
|
-
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.') })
|
|
158
160
|
return result
|
|
159
161
|
}
|
|
160
162
|
const { result } = await commandBus.execute<ClaimLineReceiveInput, { lineId: string; claimId: string }>(
|
|
161
163
|
'warranty_claims.claim_line.receive',
|
|
162
164
|
{ input: action.input, ctx },
|
|
163
165
|
)
|
|
164
|
-
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.') })
|
|
165
167
|
return result
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
export async function POST(req: Request) {
|
|
171
|
+
const { translate } = await resolveTranslations()
|
|
169
172
|
try {
|
|
170
|
-
const context = await resolveActionContext(req)
|
|
173
|
+
const context = await resolveActionContext(req, translate)
|
|
171
174
|
const payload = toRecord(await readJsonSafe(req, {}))
|
|
172
175
|
const action = toReceivingAction(payload, context)
|
|
173
176
|
const guarded = await runGuard(req, context, action)
|
|
@@ -177,18 +180,22 @@ export async function POST(req: Request) {
|
|
|
177
180
|
const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action
|
|
178
181
|
|
|
179
182
|
const commandBus = context.ctx.container.resolve('commandBus') as CommandBus
|
|
180
|
-
const result = await executeReceivingAction(commandBus, commandAction, context.ctx)
|
|
183
|
+
const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate)
|
|
181
184
|
|
|
182
185
|
await guarded.runAfterSuccess()
|
|
183
186
|
|
|
184
187
|
return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.claimId })
|
|
185
188
|
} catch (err) {
|
|
186
189
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
190
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
191
|
+
if (interceptorRejection) {
|
|
192
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
193
|
+
}
|
|
187
194
|
if (err instanceof z.ZodError) {
|
|
188
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
195
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
189
196
|
}
|
|
190
197
|
logger.error('warranty_claims.receiving.post failed', { err })
|
|
191
|
-
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 })
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
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) => {
|
|
@@ -5,7 +5,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
6
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
7
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
8
9
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
11
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
10
12
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
11
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -56,24 +58,23 @@ export const metadata = {
|
|
|
56
58
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.orders.manage'] },
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function translateKey(key: string): string {
|
|
60
|
-
return key
|
|
61
|
-
}
|
|
62
|
-
|
|
63
61
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
64
62
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
async function resolveActionContext(
|
|
65
|
+
async function resolveActionContext(
|
|
66
|
+
req: Request,
|
|
67
|
+
translate: ActionRouteContext['translate'],
|
|
68
|
+
): Promise<ActionRouteContext> {
|
|
68
69
|
const container = await createRequestContainer()
|
|
69
70
|
const auth = await getAuthFromRequest(req)
|
|
70
71
|
if (!auth || !auth.tenantId) {
|
|
71
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
72
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
72
73
|
}
|
|
73
74
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
74
75
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
75
76
|
if (!organizationId) {
|
|
76
|
-
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.') })
|
|
77
78
|
}
|
|
78
79
|
return {
|
|
79
80
|
ctx: {
|
|
@@ -86,7 +87,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
86
87
|
},
|
|
87
88
|
tenantId: auth.tenantId,
|
|
88
89
|
organizationId,
|
|
89
|
-
translate
|
|
90
|
+
translate,
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -113,7 +114,7 @@ async function runGuard(
|
|
|
113
114
|
): Promise<RouteMutationGuardResult> {
|
|
114
115
|
const userId = context.ctx.auth?.sub
|
|
115
116
|
if (!userId) {
|
|
116
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
117
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
117
118
|
}
|
|
118
119
|
return runRouteMutationGuards({
|
|
119
120
|
container: context.ctx.container,
|
|
@@ -133,8 +134,9 @@ async function runGuard(
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
export async function POST(req: Request) {
|
|
137
|
+
const { translate } = await resolveTranslations()
|
|
136
138
|
try {
|
|
137
|
-
const context = await resolveActionContext(req)
|
|
139
|
+
const context = await resolveActionContext(req, translate)
|
|
138
140
|
const requestInput = toReplacementOrderRequest(toRecord(await readJsonSafe(req, {})))
|
|
139
141
|
const guarded = await runGuard(req, context, requestInput)
|
|
140
142
|
if (!guarded.ok) {
|
|
@@ -148,7 +150,7 @@ export async function POST(req: Request) {
|
|
|
148
150
|
'warranty_claims.claim.create_replacement_order',
|
|
149
151
|
{ input: toCommandInput(guardedInput, context), ctx: context.ctx },
|
|
150
152
|
)
|
|
151
|
-
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.') })
|
|
152
154
|
await guarded.runAfterSuccess()
|
|
153
155
|
return NextResponse.json({
|
|
154
156
|
replacementOrderId: result.replacementOrderId,
|
|
@@ -156,11 +158,15 @@ export async function POST(req: Request) {
|
|
|
156
158
|
})
|
|
157
159
|
} catch (err) {
|
|
158
160
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
161
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
162
|
+
if (interceptorRejection) {
|
|
163
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
164
|
+
}
|
|
159
165
|
if (err instanceof z.ZodError) {
|
|
160
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
166
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
161
167
|
}
|
|
162
168
|
logger.error('warranty_claims.replacement-order.post failed', { err })
|
|
163
|
-
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 })
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
|
|
@@ -6,7 +6,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
6
6
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
7
7
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
8
8
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
9
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
9
10
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
11
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
12
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
11
13
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
12
14
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -69,24 +71,23 @@ export const metadata = {
|
|
|
69
71
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
function translateKey(key: string): string {
|
|
73
|
-
return key
|
|
74
|
-
}
|
|
75
|
-
|
|
76
74
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
77
75
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
async function resolveActionContext(
|
|
78
|
+
async function resolveActionContext(
|
|
79
|
+
req: Request,
|
|
80
|
+
translate: ActionRouteContext['translate'],
|
|
81
|
+
): Promise<ActionRouteContext> {
|
|
81
82
|
const container = await createRequestContainer()
|
|
82
83
|
const auth = await getAuthFromRequest(req)
|
|
83
84
|
if (!auth || !auth.tenantId) {
|
|
84
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
85
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
85
86
|
}
|
|
86
87
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
87
88
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
88
89
|
if (!organizationId) {
|
|
89
|
-
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.') })
|
|
90
91
|
}
|
|
91
92
|
return {
|
|
92
93
|
ctx: {
|
|
@@ -99,7 +100,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
|
|
|
99
100
|
},
|
|
100
101
|
tenantId: auth.tenantId,
|
|
101
102
|
organizationId,
|
|
102
|
-
translate
|
|
103
|
+
translate,
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -142,7 +143,7 @@ async function runGuard(
|
|
|
142
143
|
): Promise<RouteMutationGuardResult> {
|
|
143
144
|
const userId = context.ctx.auth?.sub
|
|
144
145
|
if (!userId) {
|
|
145
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
146
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
146
147
|
}
|
|
147
148
|
return runRouteMutationGuards({
|
|
148
149
|
container: context.ctx.container,
|
|
@@ -167,7 +168,7 @@ async function loadClaimAndLines(
|
|
|
167
168
|
): Promise<{ claim: WarrantyClaim; lines: WarrantyClaimLine[] }> {
|
|
168
169
|
const scope = { tenantId: context.tenantId, organizationId: context.organizationId }
|
|
169
170
|
const em = (context.ctx.container.resolve('em') as EntityManager).fork()
|
|
170
|
-
const claim = await requireScopedClaim(em, claimId, scope)
|
|
171
|
+
const claim = await requireScopedClaim(em, claimId, scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
|
|
171
172
|
const lines = await findWithDecryption(
|
|
172
173
|
em,
|
|
173
174
|
WarrantyClaimLine,
|
|
@@ -178,9 +179,9 @@ async function loadClaimAndLines(
|
|
|
178
179
|
return { claim, lines }
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
function assertGeneratableStatus(claim: WarrantyClaim): void {
|
|
182
|
+
function assertGeneratableStatus(claim: WarrantyClaim, translate: ActionRouteContext['translate']): void {
|
|
182
183
|
if (claim.status !== 'approved' && claim.status !== 'awaiting_return') {
|
|
183
|
-
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.') })
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
@@ -188,12 +189,13 @@ async function executeSetReturnLabel(
|
|
|
188
189
|
commandBus: CommandBus,
|
|
189
190
|
input: ClaimSetReturnLabelInput,
|
|
190
191
|
ctx: CommandRuntimeContext,
|
|
192
|
+
translate: ActionRouteContext['translate'],
|
|
191
193
|
): Promise<ReturnLabelCreatedResponse> {
|
|
192
194
|
const { result } = await commandBus.execute<ClaimSetReturnLabelInput, ReturnLabelCommandResult>(
|
|
193
195
|
'warranty_claims.claim.set_return_label',
|
|
194
196
|
{ input, ctx },
|
|
195
197
|
)
|
|
196
|
-
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.') })
|
|
197
199
|
return {
|
|
198
200
|
status: 'created',
|
|
199
201
|
labelUrl: result.labelUrl,
|
|
@@ -210,8 +212,9 @@ export function firstTranslatableIssueKey(err: z.ZodError): string {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
export async function POST(req: Request) {
|
|
215
|
+
const { translate } = await resolveTranslations()
|
|
213
216
|
try {
|
|
214
|
-
const context = await resolveActionContext(req)
|
|
217
|
+
const context = await resolveActionContext(req, translate)
|
|
215
218
|
const requestInput = toReturnLabelRequest(toRecord(await readJsonSafe(req, {})))
|
|
216
219
|
const guarded = await runGuard(req, context, requestInput)
|
|
217
220
|
if (!guarded.ok) {
|
|
@@ -225,13 +228,14 @@ export async function POST(req: Request) {
|
|
|
225
228
|
commandBus,
|
|
226
229
|
toSetReturnLabelInput(guardedInput, context),
|
|
227
230
|
context.ctx,
|
|
231
|
+
translate,
|
|
228
232
|
)
|
|
229
233
|
await guarded.runAfterSuccess()
|
|
230
234
|
return NextResponse.json(response)
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
const { claim, lines } = await loadClaimAndLines(context, guardedInput.claimId)
|
|
234
|
-
assertGeneratableStatus(claim)
|
|
238
|
+
assertGeneratableStatus(claim, translate)
|
|
235
239
|
await enforceWarrantyClaimOptimisticLock(
|
|
236
240
|
context.ctx,
|
|
237
241
|
claim,
|
|
@@ -259,16 +263,21 @@ export async function POST(req: Request) {
|
|
|
259
263
|
carrier: result.carrier,
|
|
260
264
|
}, context),
|
|
261
265
|
context.ctx,
|
|
266
|
+
translate,
|
|
262
267
|
)
|
|
263
268
|
await guarded.runAfterSuccess()
|
|
264
269
|
return NextResponse.json(response)
|
|
265
270
|
} catch (err) {
|
|
266
271
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
272
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
273
|
+
if (interceptorRejection) {
|
|
274
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
275
|
+
}
|
|
267
276
|
if (err instanceof z.ZodError) {
|
|
268
|
-
return NextResponse.json({ error: firstTranslatableIssueKey(err) }, { status: 400 })
|
|
277
|
+
return NextResponse.json({ error: translate(firstTranslatableIssueKey(err), 'Invalid input') }, { status: 400 })
|
|
269
278
|
}
|
|
270
279
|
logger.error('warranty_claims.return-label.post failed', { err })
|
|
271
|
-
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 })
|
|
272
281
|
}
|
|
273
282
|
}
|
|
274
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,
|