@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7294.1.0ef99fc518
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/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +21 -3
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +6 -4
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +6 -4
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/index.js +3 -1
- package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +17 -9
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +12 -7
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/assignableStaff.js +103 -22
- package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
- package/dist/modules/customers/setup.js +10 -2
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +13 -2
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/schedule-value.js +30 -0
- package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
- package/dist/modules/portal/lib/navigation.js +3 -4
- package/dist/modules/portal/lib/navigation.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +79 -14
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/extension-points.js +16 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +3 -3
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +4 -4
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
- package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +3 -3
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +3 -3
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
- 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 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +4 -2
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/acl/route.ts +4 -3
- package/src/modules/auth/components/AclEditor.tsx +38 -2
- package/src/modules/auth/i18n/de.json +5 -1
- package/src/modules/auth/i18n/en.json +5 -1
- package/src/modules/auth/i18n/es.json +5 -1
- package/src/modules/auth/i18n/ko.json +5 -1
- package/src/modules/auth/i18n/pl.json +5 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
- package/src/modules/customers/backend/customers/people/page.tsx +6 -4
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
- package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
- package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
- package/src/modules/customers/components/detail/schedule/index.ts +1 -1
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
- package/src/modules/customers/components/formConfig.tsx +35 -12
- package/src/modules/customers/data/validators.ts +14 -8
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/assignableStaff.ts +172 -34
- package/src/modules/customers/setup.ts +8 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
- package/src/modules/data_sync/api/schedules/route.ts +5 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
- package/src/modules/data_sync/data/validators.ts +26 -2
- package/src/modules/data_sync/i18n/de.json +2 -0
- package/src/modules/data_sync/i18n/en.json +2 -0
- package/src/modules/data_sync/i18n/es.json +2 -0
- package/src/modules/data_sync/i18n/ko.json +2 -0
- package/src/modules/data_sync/i18n/pl.json +2 -0
- package/src/modules/data_sync/lib/schedule-value.ts +45 -0
- package/src/modules/eudr/data/validators.ts +3 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
- package/src/modules/portal/i18n/de.json +1 -0
- package/src/modules/portal/i18n/en.json +1 -0
- package/src/modules/portal/i18n/es.json +1 -0
- package/src/modules/portal/i18n/ko.json +1 -0
- package/src/modules/portal/i18n/pl.json +1 -0
- package/src/modules/portal/lib/navigation.ts +8 -3
- package/src/modules/query_index/lib/engine.ts +111 -16
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
- package/src/modules/staff/extension-points.ts +16 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
- package/src/modules/staff/i18n/de.json +3 -0
- package/src/modules/staff/i18n/en.json +3 -0
- package/src/modules/staff/i18n/es.json +3 -0
- package/src/modules/staff/i18n/ko.json +3 -0
- package/src/modules/staff/i18n/pl.json +3 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
- package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
- package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
- package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
- package/src/modules/warranty_claims/api/assign/route.ts +3 -3
- package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
- package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
- package/src/modules/warranty_claims/api/events/route.ts +4 -4
- package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
- package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
- package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
- package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
- package/src/modules/warranty_claims/api/risk/route.ts +2 -2
- package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
- package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
- package/src/modules/warranty_claims/api/stats/route.ts +2 -2
- package/src/modules/warranty_claims/api/submit/route.ts +3 -3
- package/src/modules/warranty_claims/api/transition/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
- package/src/modules/warranty_claims/backend/page.tsx +4 -2
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/i18n/de.json +1 -1
- package/src/modules/workflows/i18n/en.json +1 -1
- package/src/modules/workflows/i18n/es.json +1 -1
- package/src/modules/workflows/i18n/ko.json +1 -1
- package/src/modules/workflows/i18n/pl.json +1 -1
- package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/stats/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql, type SelectQueryBuilder } from 'kysely'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveEffectiveWarrantyClaimSettings } from '../../lib/settings'\nimport { SLA_EXCLUDED_STATUSES } from '../../lib/deskFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype NumericAggregateValue = string | number | bigint | null\n\ntype WarrantyClaimsStatsTable = {\n id: string\n organization_id: string\n tenant_id: string\n status: string\n deleted_at: Date | null\n sla_due_at: Date | null\n sla_paused_at: Date | null\n assignee_user_id: string | null\n submitted_at: Date | null\n resolved_at: Date | null\n total_approved_amount: string | number | null\n total_recovered_amount: string | number | null\n currency_code: string | null\n}\n\ntype WarrantyClaimsStatsDb = {\n warranty_claims: WarrantyClaimsStatsTable\n}\n\ntype StatsRouteContext = {\n tenantId: string\n organizationIds: string[]\n userId: string | null\n em: EntityManager\n}\n\nconst querySchema = z.object({}).strict()\n\nconst OPEN_STATUSES = [\n 'submitted',\n 'in_review',\n 'info_requested',\n 'approved',\n 'awaiting_return',\n 'received',\n 'inspecting',\n]\n\nconst RESOLUTION_STATUSES = ['resolved', 'closed']\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000\n\nconst recoveredByCurrencySchema = z.object({\n currencyCode: z.string().nullable(),\n total: z.number(),\n})\n\nconst resultSchema = z.object({\n openByStatus: z.record(z.string(), z.number().int().nonnegative()),\n overdue: z.number().int().nonnegative(),\n slaAtRisk: z.number().int().nonnegative(),\n assignedToMe: z.number().int().nonnegative(),\n resolvedLast30d: z.number().int().nonnegative(),\n avgResolutionDays: z.number().nullable(),\n approvalRatePct: z.number().nullable(),\n recoveredLast30dByCurrency: z.array(recoveredByCurrencySchema),\n slaAtRiskThresholdPct: z.number().int().min(1).max(100),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: resultSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nfunction parseNumeric(value: NumericAggregateValue | undefined): number {\n if (value === undefined || value === null) return 0\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction applyOrganizationScope<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n organizationIds: string[],\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n if (organizationIds.length === 1) {\n return query.where('organization_id', '=', organizationIds[0])\n }\n return query.where('organization_id', 'in', organizationIds)\n}\n\nfunction baseClaimsQuery<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n context: StatsRouteContext,\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n return applyOrganizationScope(\n query\n .where('tenant_id', '=', context.tenantId)\n .where('deleted_at', 'is', null),\n context.organizationIds,\n )\n}\n\nasync function resolveStatsContext(req: Request): Promise<StatsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationIds = Array.isArray(scope?.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : auth.orgId\n ? [auth.orgId]\n : []\n if (organizationIds.length === 0) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n tenantId: auth.tenantId,\n organizationIds,\n userId: typeof auth.sub === 'string' ? auth.sub : null,\n em: (container.resolve('em') as EntityManager).fork(),\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n querySchema.parse(Object.fromEntries(url.searchParams))\n const context = await resolveStatsContext(req)\n const db = context.em.getKysely<WarrantyClaimsStatsDb>()\n const now = new Date()\n const thirtyDaysAgo = new Date(now.getTime() - THIRTY_DAYS_MS)\n const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(context.em, {\n tenantId: context.tenantId,\n organizationId: context.organizationIds[0] ?? null,\n })\n\n const openRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(['status', sql<NumericAggregateValue>`count(*)`.as('count')])\n .where('status', 'in', OPEN_STATUSES)\n .groupBy('status'),\n context,\n ).execute()\n const openByStatus: Record<string, number> = Object.fromEntries(OPEN_STATUSES.map((status) => [status, 0]))\n for (const row of openRows) {\n openByStatus[row.status] = Math.trunc(parseNumeric(row.count))\n }\n\n const overdueRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '<', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES]),\n context,\n ).executeTakeFirst()\n const overdue = Math.trunc(parseNumeric(overdueRow?.count))\n\n const atRiskRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '>', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES])\n .where('submitted_at', 'is not', null)\n .where(sql<boolean>`sla_due_at > submitted_at`)\n .where(sql<boolean>`extract(epoch from (${now}::timestamptz - submitted_at)) * 100 >= extract(epoch from (sla_due_at - submitted_at)) * ${effectiveSettings.slaAtRiskThresholdPct}`),\n context,\n ).executeTakeFirst()\n const slaAtRisk = Math.trunc(parseNumeric(atRiskRow?.count))\n\n let assignedToMe = 0\n if (context.userId) {\n const assignedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('status', 'in', OPEN_STATUSES)\n .where('assignee_user_id', '=', context.userId),\n context,\n ).executeTakeFirst()\n assignedToMe = Math.trunc(parseNumeric(assignedRow?.count))\n }\n\n const resolvedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const resolvedLast30d = Math.trunc(parseNumeric(resolvedRow?.count))\n\n const avgRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`avg(extract(epoch from (resolved_at - submitted_at)) / 86400.0)`.as('avgDays'))\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('submitted_at', 'is not', null),\n context,\n ).executeTakeFirst()\n const avgValue = avgRow?.avgDays\n const avgResolutionDays = avgValue === undefined || avgValue === null\n ? null\n : Number(parseNumeric(avgValue).toFixed(1))\n\n const approvalRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n sql<NumericAggregateValue>`count(*)`.as('total'),\n sql<NumericAggregateValue>`count(*) filter (where total_approved_amount > 0)`.as('approved'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const approvalTotal = parseNumeric(approvalRow?.total)\n const approvalApproved = parseNumeric(approvalRow?.approved)\n const approvalRatePct = approvalTotal > 0 ? Math.round((approvalApproved / approvalTotal) * 100) : null\n\n const recoveredRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n 'currency_code as currencyCode',\n sql<NumericAggregateValue>`coalesce(sum(total_recovered_amount), 0)`.as('total'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('total_recovered_amount', '>', 0)\n .groupBy('currency_code'),\n context,\n ).execute()\n // `group by` returns rows in an unspecified order, so consumers that render a\n // single bucket would otherwise show an arbitrary currency. Sort largest-first\n // with the code as a tiebreak so the response is deterministic.\n const recoveredLast30dByCurrency = recoveredRows\n .map((row) => ({\n currencyCode: row.currencyCode ?? null,\n total: Number(parseNumeric(row.total).toFixed(2)),\n }))\n .sort((left, right) =>\n right.total - left.total\n || (left.currencyCode ?? '').localeCompare(right.currencyCode ?? ''))\n\n return NextResponse.json({\n ok: true,\n result: {\n openByStatus,\n overdue,\n slaAtRisk,\n assignedToMe,\n resolvedLast30d,\n avgResolutionDays,\n approvalRatePct,\n recoveredLast30dByCurrency,\n slaAtRiskThresholdPct: effectiveSettings.slaAtRiskThresholdPct,\n },\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.stats.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim statistics',\n methods: {\n GET: {\n summary: 'Get warranty claim queue and resolution statistics',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Warranty claim statistics',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,WAAoC;AAC7C,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql, type SelectQueryBuilder } from 'kysely'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveEffectiveWarrantyClaimSettings } from '../../lib/settings'\nimport { SLA_EXCLUDED_STATUSES } from '../../lib/deskFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype NumericAggregateValue = string | number | bigint | null\n\ntype WarrantyClaimsStatsTable = {\n id: string\n organization_id: string\n tenant_id: string\n status: string\n deleted_at: Date | null\n sla_due_at: Date | null\n sla_paused_at: Date | null\n assignee_user_id: string | null\n submitted_at: Date | null\n resolved_at: Date | null\n total_approved_amount: string | number | null\n total_recovered_amount: string | number | null\n currency_code: string | null\n}\n\ntype WarrantyClaimsStatsDb = {\n warranty_claims: WarrantyClaimsStatsTable\n}\n\ntype StatsRouteContext = {\n tenantId: string\n organizationIds: string[]\n userId: string | null\n em: EntityManager\n}\n\nconst querySchema = z.object({}).strict()\n\nconst OPEN_STATUSES = [\n 'submitted',\n 'in_review',\n 'info_requested',\n 'approved',\n 'awaiting_return',\n 'received',\n 'inspecting',\n]\n\nconst RESOLUTION_STATUSES = ['resolved', 'closed']\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000\n\nconst recoveredByCurrencySchema = z.object({\n currencyCode: z.string().nullable(),\n total: z.number(),\n})\n\nconst resultSchema = z.object({\n openByStatus: z.record(z.string(), z.number().int().nonnegative()),\n overdue: z.number().int().nonnegative(),\n slaAtRisk: z.number().int().nonnegative(),\n assignedToMe: z.number().int().nonnegative(),\n resolvedLast30d: z.number().int().nonnegative(),\n avgResolutionDays: z.number().nullable(),\n approvalRatePct: z.number().nullable(),\n recoveredLast30dByCurrency: z.array(recoveredByCurrencySchema),\n slaAtRiskThresholdPct: z.number().int().min(1).max(100),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: resultSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nfunction parseNumeric(value: NumericAggregateValue | undefined): number {\n if (value === undefined || value === null) return 0\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction applyOrganizationScope<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n organizationIds: string[],\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n if (organizationIds.length === 1) {\n return query.where('organization_id', '=', organizationIds[0])\n }\n return query.where('organization_id', 'in', organizationIds)\n}\n\nfunction baseClaimsQuery<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n context: StatsRouteContext,\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n return applyOrganizationScope(\n query\n .where('tenant_id', '=', context.tenantId)\n .where('deleted_at', 'is', null),\n context.organizationIds,\n )\n}\n\nasync function resolveStatsContext(req: Request): Promise<StatsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationIds = Array.isArray(scope?.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : auth.orgId\n ? [auth.orgId]\n : []\n if (organizationIds.length === 0) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n tenantId: auth.tenantId,\n organizationIds,\n userId: typeof auth.sub === 'string' ? auth.sub : null,\n em: (container.resolve('em') as EntityManager).fork(),\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n querySchema.parse(Object.fromEntries(url.searchParams))\n const context = await resolveStatsContext(req)\n const db = context.em.getKysely<WarrantyClaimsStatsDb>()\n const now = new Date()\n const thirtyDaysAgo = new Date(now.getTime() - THIRTY_DAYS_MS)\n const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(context.em, {\n tenantId: context.tenantId,\n organizationId: context.organizationIds[0] ?? null,\n })\n\n const openRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(['status', sql<NumericAggregateValue>`count(*)`.as('count')])\n .where('status', 'in', OPEN_STATUSES)\n .groupBy('status'),\n context,\n ).execute()\n const openByStatus: Record<string, number> = Object.fromEntries(OPEN_STATUSES.map((status) => [status, 0]))\n for (const row of openRows) {\n openByStatus[row.status] = Math.trunc(parseNumeric(row.count))\n }\n\n const overdueRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '<', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES]),\n context,\n ).executeTakeFirst()\n const overdue = Math.trunc(parseNumeric(overdueRow?.count))\n\n const atRiskRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '>', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES])\n .where('submitted_at', 'is not', null)\n .where(sql<boolean>`sla_due_at > submitted_at`)\n .where(sql<boolean>`extract(epoch from (${now}::timestamptz - submitted_at)) * 100 >= extract(epoch from (sla_due_at - submitted_at)) * ${effectiveSettings.slaAtRiskThresholdPct}`),\n context,\n ).executeTakeFirst()\n const slaAtRisk = Math.trunc(parseNumeric(atRiskRow?.count))\n\n let assignedToMe = 0\n if (context.userId) {\n const assignedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('status', 'in', OPEN_STATUSES)\n .where('assignee_user_id', '=', context.userId),\n context,\n ).executeTakeFirst()\n assignedToMe = Math.trunc(parseNumeric(assignedRow?.count))\n }\n\n const resolvedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const resolvedLast30d = Math.trunc(parseNumeric(resolvedRow?.count))\n\n const avgRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`avg(extract(epoch from (resolved_at - submitted_at)) / 86400.0)`.as('avgDays'))\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('submitted_at', 'is not', null),\n context,\n ).executeTakeFirst()\n const avgValue = avgRow?.avgDays\n const avgResolutionDays = avgValue === undefined || avgValue === null\n ? null\n : Number(parseNumeric(avgValue).toFixed(1))\n\n const approvalRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n sql<NumericAggregateValue>`count(*)`.as('total'),\n sql<NumericAggregateValue>`count(*) filter (where total_approved_amount > 0)`.as('approved'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const approvalTotal = parseNumeric(approvalRow?.total)\n const approvalApproved = parseNumeric(approvalRow?.approved)\n const approvalRatePct = approvalTotal > 0 ? Math.round((approvalApproved / approvalTotal) * 100) : null\n\n const recoveredRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n 'currency_code as currencyCode',\n sql<NumericAggregateValue>`coalesce(sum(total_recovered_amount), 0)`.as('total'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('total_recovered_amount', '>', 0)\n .groupBy('currency_code'),\n context,\n ).execute()\n // `group by` returns rows in an unspecified order, so consumers that render a\n // single bucket would otherwise show an arbitrary currency. Sort largest-first\n // with the code as a tiebreak so the response is deterministic.\n const recoveredLast30dByCurrency = recoveredRows\n .map((row) => ({\n currencyCode: row.currencyCode ?? null,\n total: Number(parseNumeric(row.total).toFixed(2)),\n }))\n .sort((left, right) =>\n right.total - left.total\n || (left.currencyCode ?? '').localeCompare(right.currencyCode ?? ''))\n\n return NextResponse.json({\n ok: true,\n result: {\n openByStatus,\n overdue,\n slaAtRisk,\n assignedToMe,\n resolvedLast30d,\n avgResolutionDays,\n approvalRatePct,\n recoveredLast30dByCurrency,\n slaAtRiskThresholdPct: effectiveSettings.slaAtRiskThresholdPct,\n },\n })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.stats.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim statistics',\n methods: {\n GET: {\n summary: 'Get warranty claim queue and resolution statistics',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Warranty claim statistics',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
9
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
10
10
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
@@ -87,12 +87,12 @@ async function POST(req) {
|
|
|
87
87
|
await guarded.runAfterSuccess();
|
|
88
88
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id });
|
|
89
89
|
} catch (err) {
|
|
90
|
-
|
|
90
|
+
const { translate } = await resolveTranslations();
|
|
91
|
+
if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
|
|
91
92
|
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
92
93
|
if (interceptorRejection) {
|
|
93
94
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
94
95
|
}
|
|
95
|
-
const { translate } = await resolveTranslations();
|
|
96
96
|
if (err instanceof z.ZodError) {
|
|
97
97
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
98
98
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/submit/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\n// Scope is derived from the authenticated principal, never accepted from the body \u2014\n// mirroring the sibling transition/assign routes. `withScopedPayload` prefers a\n// caller-supplied tenantId/organizationId over the actor's context, so admitting them\n// here would let the request choose its own scope.\nconst submitSchema = z\n .object({\n id: z.string().uuid(),\n })\n .strict()\n\ntype SubmitInput = z.infer<typeof submitSchema>\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: SubmitInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = toRecord(withScopedPayload(payload, context.ctx, context.translate))\n const input = submitSchema.parse({ id: scopedPayload.id })\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? submitSchema.parse(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<SubmitInput, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\n// Scope is derived from the authenticated principal, never accepted from the body \u2014\n// mirroring the sibling transition/assign routes. `withScopedPayload` prefers a\n// caller-supplied tenantId/organizationId over the actor's context, so admitting them\n// here would let the request choose its own scope.\nconst submitSchema = z\n .object({\n id: z.string().uuid(),\n })\n .strict()\n\ntype SubmitInput = z.infer<typeof submitSchema>\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: SubmitInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = toRecord(withScopedPayload(payload, context.ctx, context.translate))\n const input = submitSchema.parse({ id: scopedPayload.id })\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? submitSchema.parse(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<SubmitInput, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.submit.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Submit warranty claim',\n methods: {\n POST: {\n summary: 'Submit a draft claim',\n requestBody: { contentType: 'application/json', schema: submitSchema },\n responses: [\n {\n status: 200,\n description: 'Claim submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
9
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
10
10
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
@@ -93,12 +93,12 @@ async function POST(req) {
|
|
|
93
93
|
await guarded.runAfterSuccess();
|
|
94
94
|
return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id });
|
|
95
95
|
} catch (err) {
|
|
96
|
-
|
|
96
|
+
const { translate } = await resolveTranslations();
|
|
97
|
+
if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
|
|
97
98
|
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
98
99
|
if (interceptorRejection) {
|
|
99
100
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
100
101
|
}
|
|
101
|
-
const { translate } = await resolveTranslations();
|
|
102
102
|
if (err instanceof z.ZodError) {
|
|
103
103
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
104
104
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/transition/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { transitionClaimInputSchema, type TransitionClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toTransitionInput(scopedPayload: Record<string, unknown>): TransitionClaimInput {\n return transitionClaimInputSchema.parse({\n id: scopedPayload.id,\n toStatus: scopedPayload.toStatus,\n rejectionReasonCode: scopedPayload.rejectionReasonCode,\n resolutionSummary: scopedPayload.resolutionSummary,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: TransitionClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toTransitionInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toTransitionInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { transitionClaimInputSchema, type TransitionClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toTransitionInput(scopedPayload: Record<string, unknown>): TransitionClaimInput {\n return transitionClaimInputSchema.parse({\n id: scopedPayload.id,\n toStatus: scopedPayload.toStatus,\n rejectionReasonCode: scopedPayload.rejectionReasonCode,\n resolutionSummary: scopedPayload.resolutionSummary,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: TransitionClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toTransitionInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toTransitionInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.transition.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Transition warranty claim',\n methods: {\n POST: {\n summary: 'Move a claim through the lifecycle',\n requestBody: { contentType: 'application/json', schema: transitionClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Claim transitioned',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
9
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
10
10
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
@@ -97,12 +97,12 @@ async function POST(req) {
|
|
|
97
97
|
await guarded.runAfterSuccess();
|
|
98
98
|
return NextResponse.json({ ok: true, claimId: createdClaimId });
|
|
99
99
|
} catch (err) {
|
|
100
|
-
|
|
100
|
+
const { translate } = await resolveTranslations();
|
|
101
|
+
if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
|
|
101
102
|
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
102
103
|
if (interceptorRejection) {
|
|
103
104
|
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
104
105
|
}
|
|
105
|
-
const { translate } = await resolveTranslations();
|
|
106
106
|
if (err instanceof z.ZodError) {
|
|
107
107
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
108
108
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/vendor-recovery/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { vendorRecoveryInputSchema, type VendorRecoveryInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toVendorRecoveryInput(scopedPayload: Record<string, unknown>): VendorRecoveryInput {\n return vendorRecoveryInputSchema.parse({\n claimId: scopedPayload.claimId,\n lineIds: scopedPayload.lineIds,\n vendorName: scopedPayload.vendorName,\n vendorRef: scopedPayload.vendorRef,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: VendorRecoveryInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toVendorRecoveryInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toVendorRecoveryInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<VendorRecoveryInput, { claimId: string }>(\n 'warranty_claims.claim.create_vendor_recovery',\n { input: commandInput, ctx: context.ctx },\n )\n const createdClaimId = result?.claimId\n if (typeof createdClaimId !== 'string') {\n throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.notFound', 'Warranty claim not found.') })\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: createdClaimId })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { vendorRecoveryInputSchema, type VendorRecoveryInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toVendorRecoveryInput(scopedPayload: Record<string, unknown>): VendorRecoveryInput {\n return vendorRecoveryInputSchema.parse({\n claimId: scopedPayload.claimId,\n lineIds: scopedPayload.lineIds,\n vendorName: scopedPayload.vendorName,\n vendorRef: scopedPayload.vendorRef,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: VendorRecoveryInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toVendorRecoveryInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toVendorRecoveryInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<VendorRecoveryInput, { claimId: string }>(\n 'warranty_claims.claim.create_vendor_recovery',\n { input: commandInput, ctx: context.ctx },\n )\n const createdClaimId = result?.claimId\n if (typeof createdClaimId !== 'string') {\n throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.notFound', 'Warranty claim not found.') })\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: createdClaimId })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.vendor-recovery.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Create vendor recovery claim',\n methods: {\n POST: {\n summary: 'Create a linked vendor-recovery claim from resolved source lines',\n requestBody: { contentType: 'application/json', schema: vendorRecoveryInputSchema },\n responses: [\n {\n status: 200,\n description: 'Vendor recovery claim created',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
+
import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
|
|
7
7
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
8
8
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
9
9
|
import { WarrantyClaimLine, WarrantyVendorPolicy } from "../../data/entities.js";
|
|
@@ -120,8 +120,8 @@ async function GET(req) {
|
|
|
120
120
|
}));
|
|
121
121
|
return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } });
|
|
122
122
|
} catch (err) {
|
|
123
|
-
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
124
123
|
const { translate } = await resolveTranslations();
|
|
124
|
+
if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
|
|
125
125
|
if (err instanceof z.ZodError) {
|
|
126
126
|
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
127
127
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimLine, WarrantyVendorPolicy } from '../../data/entities'\nimport { requireScopedClaim, type WarrantyClaimScope } from '../../commands/shared'\nimport { findVendorRecoveryMatches } from '../../lib/vendorPolicyRecovery'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst querySchema = z.object({\n claimId: z.string().uuid(),\n}).strict()\n\nconst suggestionSchema = z.object({\n lineId: z.string().uuid(),\n lineNo: z.number().int(),\n productName: z.string().nullable(),\n sku: z.string().nullable(),\n vendorName: z.string(),\n policyId: z.string().uuid(),\n recoveryRatePct: z.string().nullable(),\n causalFault: z.string().nullable(),\n estimatedRecovery: z.string().nullable(),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n claimId: z.string().uuid(),\n suggestions: z.array(suggestionSchema),\n }),\n})\n\ntype SuggestionsRouteContext = {\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toNullableString(value: number | string | null | undefined): string | null {\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\nfunction toSuggestionVendorName(lineVendorName: string | null | undefined, policyVendorName: string): string {\n const normalized = lineVendorName?.trim()\n return normalized || policyVendorName\n}\n\nasync function resolveSuggestionsContext(req: Request): Promise<SuggestionsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveSuggestionsContext(req)\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))\n if (claim.claimType !== 'warranty' || (claim.status !== 'resolved' && claim.status !== 'closed')) {\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions: [] } })\n }\n\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n claim: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n lineStatus: 'resolved',\n vendorClaimLineId: null,\n deletedAt: null,\n },\n { orderBy: { lineNo: 'ASC' } },\n context.scope,\n )\n const policies = await findWithDecryption(\n context.em,\n WarrantyVendorPolicy,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n isActive: true,\n deletedAt: null,\n },\n { orderBy: { vendorName: 'ASC', updatedAt: 'DESC' } },\n context.scope,\n )\n const matches = findVendorRecoveryMatches({\n claim,\n lines,\n policies,\n requireWarrantyResolved: true,\n })\n const suggestions = matches.map((match) => ({\n lineId: match.line.id,\n lineNo: match.line.lineNo ?? 0,\n productName: match.line.productName ?? null,\n sku: match.line.sku ?? null,\n vendorName: toSuggestionVendorName(match.line.vendorName, match.policy.vendorName),\n policyId: match.policy.id,\n recoveryRatePct: toNullableString(match.policy.recoveryRatePct),\n causalFault: match.causalFault,\n estimatedRecovery: match.estimatedRecovery,\n }))\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimLine, WarrantyVendorPolicy } from '../../data/entities'\nimport { requireScopedClaim, type WarrantyClaimScope } from '../../commands/shared'\nimport { findVendorRecoveryMatches } from '../../lib/vendorPolicyRecovery'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst querySchema = z.object({\n claimId: z.string().uuid(),\n}).strict()\n\nconst suggestionSchema = z.object({\n lineId: z.string().uuid(),\n lineNo: z.number().int(),\n productName: z.string().nullable(),\n sku: z.string().nullable(),\n vendorName: z.string(),\n policyId: z.string().uuid(),\n recoveryRatePct: z.string().nullable(),\n causalFault: z.string().nullable(),\n estimatedRecovery: z.string().nullable(),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n claimId: z.string().uuid(),\n suggestions: z.array(suggestionSchema),\n }),\n})\n\ntype SuggestionsRouteContext = {\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toNullableString(value: number | string | null | undefined): string | null {\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\nfunction toSuggestionVendorName(lineVendorName: string | null | undefined, policyVendorName: string): string {\n const normalized = lineVendorName?.trim()\n return normalized || policyVendorName\n}\n\nasync function resolveSuggestionsContext(req: Request): Promise<SuggestionsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveSuggestionsContext(req)\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))\n if (claim.claimType !== 'warranty' || (claim.status !== 'resolved' && claim.status !== 'closed')) {\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions: [] } })\n }\n\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n claim: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n lineStatus: 'resolved',\n vendorClaimLineId: null,\n deletedAt: null,\n },\n { orderBy: { lineNo: 'ASC' } },\n context.scope,\n )\n const policies = await findWithDecryption(\n context.em,\n WarrantyVendorPolicy,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n isActive: true,\n deletedAt: null,\n },\n { orderBy: { vendorName: 'ASC', updatedAt: 'DESC' } },\n context.scope,\n )\n const matches = findVendorRecoveryMatches({\n claim,\n lines,\n policies,\n requireWarrantyResolved: true,\n })\n const suggestions = matches.map((match) => ({\n lineId: match.line.id,\n lineNo: match.line.lineNo ?? 0,\n productName: match.line.productName ?? null,\n sku: match.line.sku ?? null,\n vendorName: toSuggestionVendorName(match.line.vendorName, match.policy.vendorName),\n policyId: match.policy.id,\n recoveryRatePct: toNullableString(match.policy.recoveryRatePct),\n causalFault: match.causalFault,\n estimatedRecovery: match.estimatedRecovery,\n }))\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('[internal] warranty_claims.vendor-recovery-suggestions.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'List supplier recovery suggestions',\n methods: {\n GET: {\n summary: 'List resolved warranty claim lines eligible for supplier recovery',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Supplier recovery suggestions',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -20,6 +20,7 @@ import { buildCrudExportUrl } from "@open-mercato/ui/backend/utils/crud";
|
|
|
20
20
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
21
21
|
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
22
22
|
import { useCurrentUserId } from "@open-mercato/ui/backend/utils/useCurrentUserId";
|
|
23
|
+
import { useCurrentOrganization } from "@open-mercato/ui/backend/BackendChromeProvider";
|
|
23
24
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
24
25
|
import { emitProgressUpdate } from "@open-mercato/shared/lib/frontend/progressEvents";
|
|
25
26
|
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -292,6 +293,7 @@ function WarrantyClaimsPage() {
|
|
|
292
293
|
const pathname = usePathname();
|
|
293
294
|
const searchParams = useSearchParams();
|
|
294
295
|
const scopeVersion = useOrganizationScopeVersion();
|
|
296
|
+
const activeOrgId = useCurrentOrganization()?.id ?? null;
|
|
295
297
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
296
298
|
const currentUserId = useCurrentUserId();
|
|
297
299
|
const initialUrlStateRef = React.useRef(null);
|
|
@@ -469,7 +471,7 @@ function WarrantyClaimsPage() {
|
|
|
469
471
|
[t]
|
|
470
472
|
);
|
|
471
473
|
const loadAssignableStaffOptions = React.useCallback(async (query) => {
|
|
472
|
-
const page2 = await fetchAssignableStaffMembersPage(query ?? "", { pageSize: 24 });
|
|
474
|
+
const page2 = await fetchAssignableStaffMembersPage(query ?? "", { pageSize: 24, activeOrgId });
|
|
473
475
|
const options = page2.items.map((member) => ({
|
|
474
476
|
value: member.userId,
|
|
475
477
|
label: staffOptionLabel(member)
|
|
@@ -481,7 +483,7 @@ function WarrantyClaimsPage() {
|
|
|
481
483
|
},
|
|
482
484
|
...options
|
|
483
485
|
];
|
|
484
|
-
}, [t]);
|
|
486
|
+
}, [activeOrgId, t]);
|
|
485
487
|
const formatAssigneeFilterValue = React.useCallback((value) => {
|
|
486
488
|
if (value === UNASSIGNED_ASSIGNEE_VALUE) return t("warranty_claims.form.assigneeUserId.unassigned", "Unassigned");
|
|
487
489
|
return assigneeDisplayNames[value] ?? t("warranty_claims.list.assignee.unknownUser", "Unknown user");
|