@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
|
@@ -13,6 +13,7 @@ import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/op
|
|
|
13
13
|
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
14
14
|
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
15
15
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
16
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
16
17
|
|
|
17
18
|
const logger = createLogger('translations').child({ component: 'entity-translations' })
|
|
18
19
|
|
|
@@ -226,6 +227,10 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
|
|
|
226
227
|
if (isCrudHttpError(err)) {
|
|
227
228
|
return NextResponse.json(err.body, { status: err.status })
|
|
228
229
|
}
|
|
230
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
231
|
+
if (interceptorRejection) {
|
|
232
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
233
|
+
}
|
|
229
234
|
if (err instanceof z.ZodError) {
|
|
230
235
|
return NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 })
|
|
231
236
|
}
|
|
@@ -331,6 +336,10 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
|
|
|
331
336
|
if (isCrudHttpError(err)) {
|
|
332
337
|
return NextResponse.json(err.body, { status: err.status })
|
|
333
338
|
}
|
|
339
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
340
|
+
if (interceptorRejection) {
|
|
341
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
342
|
+
}
|
|
334
343
|
if (err instanceof z.ZodError) {
|
|
335
344
|
return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })
|
|
336
345
|
}
|
|
@@ -7,7 +7,9 @@ import { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/a
|
|
|
7
7
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
8
8
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
9
9
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
10
11
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
12
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
13
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
12
14
|
import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
|
|
13
15
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -109,7 +111,7 @@ function isSuperAdmin(auth: CommandRuntimeContext['auth']): boolean {
|
|
|
109
111
|
function buildAuthContext(context: AssessRouteContext): AiChatRequestContext {
|
|
110
112
|
const userId = context.ctx.auth?.sub
|
|
111
113
|
if (!userId) {
|
|
112
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
114
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
113
115
|
}
|
|
114
116
|
return {
|
|
115
117
|
tenantId: context.tenantId,
|
|
@@ -123,7 +125,7 @@ function buildAuthContext(context: AssessRouteContext): AiChatRequestContext {
|
|
|
123
125
|
function buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthContext, null> {
|
|
124
126
|
const auth = context.ctx.auth
|
|
125
127
|
if (!auth?.sub) {
|
|
126
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
128
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
127
129
|
}
|
|
128
130
|
return {
|
|
129
131
|
...auth,
|
|
@@ -133,16 +135,19 @@ function buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthCo
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
async function resolveAssessContext(
|
|
138
|
+
async function resolveAssessContext(
|
|
139
|
+
req: Request,
|
|
140
|
+
translate: AssessRouteContext['translate'],
|
|
141
|
+
): Promise<AssessRouteContext> {
|
|
137
142
|
const container = await createRequestContainer()
|
|
138
143
|
const auth = await getAuthFromRequest(req)
|
|
139
144
|
if (!auth || !auth.tenantId) {
|
|
140
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
145
|
+
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
141
146
|
}
|
|
142
147
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
143
148
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
144
149
|
if (!organizationId) {
|
|
145
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
|
|
150
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
|
|
146
151
|
}
|
|
147
152
|
return {
|
|
148
153
|
ctx: {
|
|
@@ -157,7 +162,7 @@ async function resolveAssessContext(req: Request): Promise<AssessRouteContext> {
|
|
|
157
162
|
organizationId,
|
|
158
163
|
container,
|
|
159
164
|
em: container.resolve<EntityManager>('em').fork(),
|
|
160
|
-
translate
|
|
165
|
+
translate,
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
|
|
@@ -172,7 +177,7 @@ async function runGuard(
|
|
|
172
177
|
): Promise<RouteMutationGuardResult> {
|
|
173
178
|
const userId = context.ctx.auth?.sub
|
|
174
179
|
if (!userId) {
|
|
175
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
180
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
176
181
|
}
|
|
177
182
|
return runRouteMutationGuards({
|
|
178
183
|
container: context.ctx.container,
|
|
@@ -196,6 +201,7 @@ async function loadScopedLine(
|
|
|
196
201
|
scope: WarrantyClaimScope,
|
|
197
202
|
claimId: string,
|
|
198
203
|
lineId: string,
|
|
204
|
+
translate: AssessRouteContext['translate'],
|
|
199
205
|
): Promise<WarrantyClaimLine> {
|
|
200
206
|
const line = await findOneWithDecryption(
|
|
201
207
|
em,
|
|
@@ -211,7 +217,7 @@ async function loadScopedLine(
|
|
|
211
217
|
scope,
|
|
212
218
|
)
|
|
213
219
|
if (!line) {
|
|
214
|
-
throw new CrudHttpError(404, { error: 'warranty_claims.errors.notFound' })
|
|
220
|
+
throw new CrudHttpError(404, { error: translate('warranty_claims.errors.notFound', 'Claim not found.') })
|
|
215
221
|
}
|
|
216
222
|
return line
|
|
217
223
|
}
|
|
@@ -237,7 +243,7 @@ async function verifyAttachmentLinkedToTarget(
|
|
|
237
243
|
],
|
|
238
244
|
}) : false
|
|
239
245
|
if (!linked) {
|
|
240
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.attachmentNotLinked' })
|
|
246
|
+
throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.attachmentNotLinked', 'The attachment is not linked to this claim.') })
|
|
241
247
|
}
|
|
242
248
|
}
|
|
243
249
|
|
|
@@ -278,12 +284,13 @@ async function persistAssessmentPayload(
|
|
|
278
284
|
}
|
|
279
285
|
|
|
280
286
|
export async function POST(req: Request) {
|
|
287
|
+
const { translate } = await resolveTranslations()
|
|
281
288
|
try {
|
|
282
|
-
const context = await resolveAssessContext(req)
|
|
289
|
+
const context = await resolveAssessContext(req, translate)
|
|
283
290
|
const payload = toRecord(await readJsonSafe(req, {}))
|
|
284
291
|
const parsed = toAssessInput(payload, context)
|
|
285
292
|
if (parsed.kind === 'damage' && !parsed.lineId) {
|
|
286
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })
|
|
293
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.invalidInput', 'Invalid input') })
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
const guarded = parsed.lineId ? await runGuard(req, context, parsed as AssessBodyInput & { lineId: string }) : null
|
|
@@ -295,9 +302,9 @@ export async function POST(req: Request) {
|
|
|
295
302
|
|
|
296
303
|
if (input.kind === 'damage') {
|
|
297
304
|
if (!input.lineId) {
|
|
298
|
-
throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })
|
|
305
|
+
throw new CrudHttpError(400, { error: translate('warranty_claims.errors.invalidInput', 'Invalid input') })
|
|
299
306
|
}
|
|
300
|
-
const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId)
|
|
307
|
+
const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate)
|
|
301
308
|
await verifyAttachmentLinkedToTarget(context, {
|
|
302
309
|
attachmentId: input.attachmentId,
|
|
303
310
|
claimId: input.claimId,
|
|
@@ -317,8 +324,8 @@ export async function POST(req: Request) {
|
|
|
317
324
|
return NextResponse.json({ status: 'ok', assessment })
|
|
318
325
|
}
|
|
319
326
|
|
|
320
|
-
await requireScopedClaim(context.em, input.claimId, scope)
|
|
321
|
-
const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId) : null
|
|
327
|
+
await requireScopedClaim(context.em, input.claimId, scope, {}, translate('warranty_claims.errors.notFound', 'Claim not found.'))
|
|
328
|
+
const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate) : null
|
|
322
329
|
await verifyAttachmentLinkedToTarget(context, {
|
|
323
330
|
attachmentId: input.attachmentId,
|
|
324
331
|
claimId: input.claimId,
|
|
@@ -338,6 +345,10 @@ export async function POST(req: Request) {
|
|
|
338
345
|
return NextResponse.json({ status: 'ok', extraction })
|
|
339
346
|
} catch (err) {
|
|
340
347
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
348
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
349
|
+
if (interceptorRejection) {
|
|
350
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
351
|
+
}
|
|
341
352
|
if (isWarrantyAiNotConfiguredError(err)) {
|
|
342
353
|
return NextResponse.json({ status: 'notConfigured' })
|
|
343
354
|
}
|
|
@@ -345,10 +356,10 @@ export async function POST(req: Request) {
|
|
|
345
356
|
return NextResponse.json({ status: 'aiUnavailable' })
|
|
346
357
|
}
|
|
347
358
|
if (err instanceof z.ZodError) {
|
|
348
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
359
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
349
360
|
}
|
|
350
361
|
logger.error('warranty_claims.ai.assess.post failed', { err })
|
|
351
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
362
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
352
363
|
}
|
|
353
364
|
}
|
|
354
365
|
|
|
@@ -6,6 +6,7 @@ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/d
|
|
|
6
6
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
7
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
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'
|
|
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'
|
|
@@ -114,6 +115,10 @@ export async function POST(req: Request) {
|
|
|
114
115
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })
|
|
115
116
|
} catch (err) {
|
|
116
117
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
118
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
119
|
+
if (interceptorRejection) {
|
|
120
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
121
|
+
}
|
|
117
122
|
const { translate } = await resolveTranslations()
|
|
118
123
|
if (err instanceof z.ZodError) {
|
|
119
124
|
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
@@ -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'
|
|
@@ -58,24 +60,23 @@ export const metadata = {
|
|
|
58
60
|
POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.credit_memos.manage'] },
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
function translateKey(key: string): string {
|
|
62
|
-
return key
|
|
63
|
-
}
|
|
64
|
-
|
|
65
63
|
function toRecord(value: unknown): Record<string, unknown> {
|
|
66
64
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
async function resolveActionContext(
|
|
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
|
|
|
@@ -114,7 +115,7 @@ async function runGuard(
|
|
|
114
115
|
): Promise<RouteMutationGuardResult> {
|
|
115
116
|
const userId = context.ctx.auth?.sub
|
|
116
117
|
if (!userId) {
|
|
117
|
-
throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
|
|
118
|
+
throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
118
119
|
}
|
|
119
120
|
return runRouteMutationGuards({
|
|
120
121
|
container: context.ctx.container,
|
|
@@ -134,8 +135,9 @@ async function runGuard(
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
export async function POST(req: Request) {
|
|
138
|
+
const { translate } = await resolveTranslations()
|
|
137
139
|
try {
|
|
138
|
-
const context = await resolveActionContext(req)
|
|
140
|
+
const context = await resolveActionContext(req, translate)
|
|
139
141
|
const requestInput = toCreditMemoRequest(toRecord(await readJsonSafe(req, {})))
|
|
140
142
|
const guarded = await runGuard(req, context, requestInput)
|
|
141
143
|
if (!guarded.ok) {
|
|
@@ -149,7 +151,7 @@ export async function POST(req: Request) {
|
|
|
149
151
|
'warranty_claims.claim.create_credit_memo',
|
|
150
152
|
{ input: toCommandInput(guardedInput, context), ctx: context.ctx },
|
|
151
153
|
)
|
|
152
|
-
if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
|
|
154
|
+
if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
|
|
153
155
|
await guarded.runAfterSuccess()
|
|
154
156
|
return NextResponse.json({
|
|
155
157
|
creditMemoId: result.creditMemoId,
|
|
@@ -159,11 +161,15 @@ export async function POST(req: Request) {
|
|
|
159
161
|
})
|
|
160
162
|
} catch (err) {
|
|
161
163
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
164
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
165
|
+
if (interceptorRejection) {
|
|
166
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
167
|
+
}
|
|
162
168
|
if (err instanceof z.ZodError) {
|
|
163
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
169
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
164
170
|
}
|
|
165
171
|
logger.error('warranty_claims.credit-memo.post failed', { err })
|
|
166
|
-
return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
172
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
167
173
|
}
|
|
168
174
|
}
|
|
169
175
|
|
|
@@ -7,6 +7,7 @@ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/d
|
|
|
7
7
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
9
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
10
11
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
11
12
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
12
13
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
@@ -130,7 +131,7 @@ export async function GET(req: Request) {
|
|
|
130
131
|
const context = await resolveEventContext(req)
|
|
131
132
|
const url = new URL(req.url)
|
|
132
133
|
const query = eventListQuerySchema.parse({ claimId: url.searchParams.get('claimId') ?? undefined })
|
|
133
|
-
const claim = await requireScopedClaim(context.em, query.claimId, context.scope)
|
|
134
|
+
const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
|
|
134
135
|
const events = await findWithDecryption(
|
|
135
136
|
context.em,
|
|
136
137
|
WarrantyClaimEvent,
|
|
@@ -175,6 +176,10 @@ export async function POST(req: Request) {
|
|
|
175
176
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.claimId })
|
|
176
177
|
} catch (err) {
|
|
177
178
|
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
179
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
180
|
+
if (interceptorRejection) {
|
|
181
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
182
|
+
}
|
|
178
183
|
const { translate } = await resolveTranslations()
|
|
179
184
|
if (err instanceof z.ZodError) {
|
|
180
185
|
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
@@ -9,7 +9,7 @@ import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mer
|
|
|
9
9
|
import { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
10
10
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
11
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
12
|
-
import {
|
|
12
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
13
13
|
import { Attachment, AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'
|
|
14
14
|
import { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'
|
|
15
15
|
import { StorageDriverFactory } from '@open-mercato/core/modules/attachments/lib/drivers'
|
|
@@ -28,6 +28,7 @@ import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'
|
|
|
28
28
|
import { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from '../../../lib/attachmentVisibility'
|
|
29
29
|
import { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'
|
|
30
30
|
import { resolvePortalAttachmentUploadService } from '../../../lib/portalAttachmentUpload'
|
|
31
|
+
import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
|
|
31
32
|
|
|
32
33
|
const CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'
|
|
33
34
|
const logger = createLogger('warranty_claims').child({ route: 'portal-attachments' })
|
|
@@ -80,13 +81,9 @@ function toIso(value: Date | string | null | undefined): string | null {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
if (!auth.customerEntityId) {
|
|
88
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
89
|
-
}
|
|
84
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
85
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
86
|
+
const auth = authOrResponse
|
|
90
87
|
const container = await createRequestContainer()
|
|
91
88
|
const em = container.resolve('em') as EntityManager
|
|
92
89
|
return {
|
|
@@ -240,19 +237,23 @@ async function deletePortalAttachment(context: PortalContext, attachment: Attach
|
|
|
240
237
|
}
|
|
241
238
|
}
|
|
242
239
|
|
|
243
|
-
async function streamOwnedAttachment(
|
|
240
|
+
async function streamOwnedAttachment(
|
|
241
|
+
context: PortalContext,
|
|
242
|
+
attachmentId: string,
|
|
243
|
+
translate: (key: string, fallback?: string) => string,
|
|
244
|
+
): Promise<Response> {
|
|
244
245
|
const owned = await loadOwnedVisibleAttachment(context, attachmentId)
|
|
245
246
|
if (!owned) {
|
|
246
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
247
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
247
248
|
}
|
|
248
249
|
const { attachment } = owned
|
|
249
250
|
const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode })
|
|
250
251
|
if (!partition) {
|
|
251
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.load_failed' }, { status: 500 })
|
|
252
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data.') }, { status: 500 })
|
|
252
253
|
}
|
|
253
254
|
const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true })
|
|
254
255
|
if (!access.ok) {
|
|
255
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
256
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
256
257
|
}
|
|
257
258
|
const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {
|
|
258
259
|
tenantId: attachment.tenantId ?? '',
|
|
@@ -262,7 +263,7 @@ async function streamOwnedAttachment(context: PortalContext, attachmentId: strin
|
|
|
262
263
|
try {
|
|
263
264
|
buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer
|
|
264
265
|
} catch {
|
|
265
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
266
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
266
267
|
}
|
|
267
268
|
const headers: Record<string, string> = {
|
|
268
269
|
'Cache-Control': 'private, max-age=60',
|
|
@@ -279,14 +280,15 @@ export async function GET(req: Request) {
|
|
|
279
280
|
const contextOrResponse = await resolvePortalContext(req)
|
|
280
281
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
281
282
|
const context = contextOrResponse
|
|
283
|
+
const { translate } = await resolveTranslations()
|
|
282
284
|
const url = new URL(req.url)
|
|
283
285
|
const attachmentId = url.searchParams.get('attachmentId')
|
|
284
286
|
if (attachmentId) {
|
|
285
287
|
const attachmentIdParsed = z.string().uuid().safeParse(attachmentId)
|
|
286
288
|
if (!attachmentIdParsed.success) {
|
|
287
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
289
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
288
290
|
}
|
|
289
|
-
return streamOwnedAttachment(context, attachmentIdParsed.data)
|
|
291
|
+
return streamOwnedAttachment(context, attachmentIdParsed.data, translate)
|
|
290
292
|
}
|
|
291
293
|
const parsed = attachmentQuerySchema.safeParse({
|
|
292
294
|
claimId: url.searchParams.get('claimId') ?? undefined,
|
|
@@ -294,11 +296,11 @@ export async function GET(req: Request) {
|
|
|
294
296
|
pageSize: url.searchParams.get('pageSize') ?? undefined,
|
|
295
297
|
})
|
|
296
298
|
if (!parsed.success) {
|
|
297
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
299
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
298
300
|
}
|
|
299
301
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
300
302
|
if (!claim) {
|
|
301
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
303
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
302
304
|
}
|
|
303
305
|
const filter = {
|
|
304
306
|
entityId: CLAIM_ATTACHMENT_ENTITY_ID,
|
|
@@ -343,12 +345,12 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
343
345
|
const contextOrResponse = await resolvePortalContext(req)
|
|
344
346
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
345
347
|
const context = contextOrResponse
|
|
348
|
+
const { t, translate } = await resolveTranslations()
|
|
346
349
|
const contentType = req.headers.get('content-type') || ''
|
|
347
350
|
if (!contentType.toLowerCase().includes('multipart/form-data')) {
|
|
348
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
351
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
349
352
|
}
|
|
350
353
|
if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {
|
|
351
|
-
const { t } = await resolveTranslations()
|
|
352
354
|
return NextResponse.json({
|
|
353
355
|
ok: false,
|
|
354
356
|
error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),
|
|
@@ -364,15 +366,15 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
364
366
|
? z.string().uuid().safeParse(form.get('attachmentId') ?? undefined)
|
|
365
367
|
: null
|
|
366
368
|
if (!parsed.success || (replacement && !parsedAttachmentId?.success)) {
|
|
367
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
369
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
368
370
|
}
|
|
369
371
|
const claim = await loadOwnedClaim(context, parsed.data.claimId)
|
|
370
372
|
if (!claim) {
|
|
371
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
373
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
372
374
|
}
|
|
373
375
|
const file = form.get('file')
|
|
374
376
|
if (!(file instanceof File)) {
|
|
375
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.fileRequired' }, { status: 400 })
|
|
377
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.fileRequired', 'Choose a file before uploading.') }, { status: 400 })
|
|
376
378
|
}
|
|
377
379
|
const buffer = Buffer.from(await file.arrayBuffer())
|
|
378
380
|
|
|
@@ -381,7 +383,7 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
381
383
|
? await loadOwnedVisibleAttachment(context, attachmentId)
|
|
382
384
|
: null
|
|
383
385
|
if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {
|
|
384
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
386
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? 'update' : 'create', {
|
|
@@ -397,7 +399,7 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
397
399
|
|
|
398
400
|
const uploadService = resolvePortalAttachmentUploadService(context.container)
|
|
399
401
|
if (!uploadService) {
|
|
400
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.attachmentError' }, { status: 503 })
|
|
402
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.') }, { status: 503 })
|
|
401
403
|
}
|
|
402
404
|
|
|
403
405
|
let attachment: Attachment
|
|
@@ -414,7 +416,6 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
414
416
|
})
|
|
415
417
|
} catch (error) {
|
|
416
418
|
if (isScopedAttachmentUploadError(error)) {
|
|
417
|
-
const { t } = await resolveTranslations()
|
|
418
419
|
const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code]
|
|
419
420
|
?? ['warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.']
|
|
420
421
|
return NextResponse.json(
|
|
@@ -451,16 +452,17 @@ export async function DELETE(req: Request) {
|
|
|
451
452
|
const contextOrResponse = await resolvePortalContext(req)
|
|
452
453
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
453
454
|
const context = contextOrResponse
|
|
455
|
+
const { translate } = await resolveTranslations()
|
|
454
456
|
const url = new URL(req.url)
|
|
455
457
|
const parsed = deleteQuerySchema.safeParse({
|
|
456
458
|
attachmentId: url.searchParams.get('attachmentId') ?? undefined,
|
|
457
459
|
})
|
|
458
460
|
if (!parsed.success) {
|
|
459
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
461
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
460
462
|
}
|
|
461
463
|
const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId)
|
|
462
464
|
if (!owned) {
|
|
463
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
465
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
464
466
|
}
|
|
465
467
|
const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, 'delete', {
|
|
466
468
|
claimId: owned.claim.id,
|
|
@@ -3,10 +3,12 @@ import { z } from 'zod'
|
|
|
3
3
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
5
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
|
-
import {
|
|
6
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
7
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
7
8
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
8
9
|
import { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'
|
|
9
10
|
import { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'
|
|
11
|
+
import { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'
|
|
10
12
|
|
|
11
13
|
export const metadata = {
|
|
12
14
|
GET: { requireAuth: false },
|
|
@@ -40,13 +42,9 @@ async function resolveClaimId(ctx: RouteContext): Promise<string | null> {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
if (!auth.customerEntityId) {
|
|
48
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
49
|
-
}
|
|
45
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
46
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
47
|
+
const auth = authOrResponse
|
|
50
48
|
const container = await createRequestContainer()
|
|
51
49
|
const em = container.resolve('em') as EntityManager
|
|
52
50
|
return {
|
|
@@ -108,9 +106,10 @@ export async function GET(req: Request, ctx: RouteContext) {
|
|
|
108
106
|
const contextOrResponse = await resolvePortalContext(req)
|
|
109
107
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
110
108
|
const context = contextOrResponse
|
|
109
|
+
const { translate } = await resolveTranslations()
|
|
111
110
|
const claimId = await resolveClaimId(ctx)
|
|
112
111
|
if (!claimId) {
|
|
113
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
112
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
114
113
|
}
|
|
115
114
|
const scope = { tenantId: context.tenantId, organizationId: context.organizationId }
|
|
116
115
|
const claim = await loadPortalOwnedClaim(
|
|
@@ -123,7 +122,7 @@ export async function GET(req: Request, ctx: RouteContext) {
|
|
|
123
122
|
claimId,
|
|
124
123
|
)
|
|
125
124
|
if (!claim) {
|
|
126
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
|
|
125
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
|
|
127
126
|
}
|
|
128
127
|
const lines = await findWithDecryption(
|
|
129
128
|
context.em,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
1
|
import { z } from 'zod'
|
|
3
2
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
3
|
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
5
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
5
|
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
7
6
|
import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
8
|
-
import {
|
|
7
|
+
import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
9
8
|
import { WarrantyClaim } from '../../../../data/entities'
|
|
10
9
|
import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../../commands/shared'
|
|
11
10
|
import { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'
|
|
11
|
+
import { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'
|
|
12
12
|
|
|
13
13
|
export type PortalClaimActionRouteContext = { params: Promise<{ id: string }> }
|
|
14
14
|
|
|
@@ -34,13 +34,9 @@ export async function resolvePortalClaimId(ctx: PortalClaimActionRouteContext):
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export async function resolvePortalActionContext(req: Request): Promise<PortalClaimActionContext | Response> {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
if (!auth.customerEntityId) {
|
|
42
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
43
|
-
}
|
|
37
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req)
|
|
38
|
+
if (authOrResponse instanceof Response) return authOrResponse
|
|
39
|
+
const auth = authOrResponse
|
|
44
40
|
const container = await createRequestContainer()
|
|
45
41
|
const em = container.resolve('em') as EntityManager
|
|
46
42
|
const commandAuth: NonNullable<AuthContext> = {
|
|
@@ -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 {
|
|
@@ -21,16 +22,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
21
22
|
const contextOrResponse = await resolvePortalActionContext(req)
|
|
22
23
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
23
24
|
const context = contextOrResponse
|
|
25
|
+
const { translate } = await resolveTranslations()
|
|
24
26
|
const claimId = await resolvePortalClaimId(ctx)
|
|
25
27
|
if (!claimId) {
|
|
26
|
-
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 })
|
|
27
29
|
}
|
|
28
30
|
const claim = await loadOwnedClaim(context, claimId)
|
|
29
31
|
if (!claim) {
|
|
30
|
-
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 })
|
|
31
33
|
}
|
|
32
34
|
if (claim.status !== 'draft') {
|
|
33
|
-
const { translate } = await resolveTranslations()
|
|
34
35
|
return NextResponse.json(
|
|
35
36
|
{ ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
|
|
36
37
|
{ status: 400 },
|
|
@@ -55,6 +56,10 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
|
|
|
55
56
|
if (isCrudHttpError(err)) {
|
|
56
57
|
return NextResponse.json(err.body, { status: err.status })
|
|
57
58
|
}
|
|
59
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
60
|
+
if (interceptorRejection) {
|
|
61
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
62
|
+
}
|
|
58
63
|
throw err
|
|
59
64
|
}
|
|
60
65
|
|